[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [titoBouzout] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".gitignore",
    "content": "push.bat\n*.pyc\npackage-metadata.json"
  },
  {
    "path": "Main.sublime-menu",
    "content": "[\n    {\n        \"mnemonic\": \"n\",\n        \"caption\": \"Preferences\",\n        \"id\": \"preferences\",\n        \"children\": [\n            {\n                \"mnemonic\": \"P\",\n                \"caption\": \"Package Settings\",\n                \"id\": \"package-settings\",\n                \"children\": [\n                    {\n                        \"caption\": \"Word Count\",\n                        \"children\": [\n                            {\n                                \"caption\": \"Settings\",\n                                \"command\": \"edit_settings\",\n                                \"args\": {\n                                    \"base_file\": \"${packages}/WordCount/WordCount.sublime-settings\",\n                                    \"default\": \"{\\n$0\\n}\\n\"\n                                }\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    }\n]\n"
  },
  {
    "path": "WordCount.py",
    "content": "import sublime, sublime_plugin, re\nimport time\nimport threading\nfrom math import ceil as ceil\nfrom os.path import basename\n\nPref = {}\ns = {}\nwsd = {'modified':True, 'selection':True, 'syntax':'plain text','changes':-1,'status':-1}\n\ndef plugin_loaded():\n\tglobal s, Pref\n\ts = sublime.load_settings('WordCount.sublime-settings')\n\tPref = Pref()\n\tPref.load();\n\ts.clear_on_change('reload')\n\ts.add_on_change('reload', lambda:Pref.load())\n\n\tif not 'running_word_count_loop' in globals():\n\t\tglobal running_word_count_loop\n\t\trunning_word_count_loop = True\n\t\tt = threading.Thread(target=word_count_loop)\n\t\tt.start()\n\nclass Pref:\n\tdef load(self):\n\t\tPref.view                   = False\n\t\tPref.elapsed_time           = 0.4\n\t\tPref.running                = False\n\n\t\tPref.wrdRx                  = re.compile(s.get('word_regexp', \"^[^\\w]?`*\\w+[^\\w]*$\"), re.U)\n\t\tPref.wrdRx                  = Pref.wrdRx.match\n\t\tPref.splitRx                = s.get('word_split', None)\n\t\tif Pref.splitRx:\n\t\t\tPref.splitRx            = re.compile(Pref.splitRx, re.U)\n\t\t\tPref.splitRx            = Pref.splitRx.findall\n\n\t\tPref.enable_live_count      = s.get('enable_live_count', True)\n\t\tPref.enable_readtime        = s.get('enable_readtime', False)\n\t\tPref.enable_line_word_count = s.get('enable_line_word_count', False)\n\t\tPref.enable_line_char_count = s.get('enable_line_char_count', False)\n\t\tPref.enable_count_lines     = s.get('enable_count_lines', False)\n\t\tPref.enable_count_chars     = s.get('enable_count_chars', False)\n\t\tPref.enable_count_pages     = s.get('enable_count_pages', True)\n\n\t\tPref.words_per_page         = s.get('words_per_page', 300)\n\t\tPref.page_count_mode_count_words = s.get('page_count_mode_count_words', True)\n\t\tPref.char_ignore_whitespace = s.get('char_ignore_whitespace', True)\n\t\tPref.readtime_wpm           = s.get('readtime_wpm', 200)\n\t\tPref.whitelist              = [x.lower() for x in s.get('whitelist_syntaxes', []) or []]\n\t\tPref.blacklist              = [x.lower() for x in s.get('blacklist_syntaxes', []) or []]\n\t\tPref.strip                  = s.get('strip', [])\n\n\t\tfor window in sublime.windows():\n\t\t\tfor view in window.views():\n\t\t\t\tview.erase_status('WordCount');\n\t\t\t\tview.settings().erase('WordCount')\n\nclass WordCount(sublime_plugin.EventListener):\n\n\tdef should_run_with_syntax(self, view):\n\t\tvs =  view.settings()\n\n\t\tsyntax = vs.get('syntax')\n\t\tsyntax = basename(syntax).split('.')[0].lower() if syntax != None else \"plain text\"\n\n\t\tws = vs.get('WordCount', wsd)\n\t\tws['syntax'] = syntax\n\t\tvs.set('WordCount', ws)\n\n\t\tif len(Pref.blacklist) > 0:\n\t\t\tfor white in Pref.blacklist:\n\t\t\t\tif white == syntax:\n\t\t\t\t\tview.erase_status('WordCount');\n\t\t\t\t\treturn False\n\t\tif len(Pref.whitelist) > 0:\n\t\t\tfor white in Pref.whitelist:\n\t\t\t\tif white == syntax:\n\t\t\t\t\treturn True\n\t\t\tview.erase_status('WordCount');\n\t\t\treturn False\n\t\treturn True\n\n\tdef on_activated_async(self, view):\n\t\tself.asap(view)\n\n\tdef on_post_save_async(self, view):\n\t\tself.asap(view)\n\n\tdef on_modified_async(self, view):\n\t\tvs = view.settings()\n\t\tws = vs.get('WordCount', wsd)\n\t\tws['modified'] = True\n\t\tvs.set('WordCount', ws)\n\n\tdef on_selection_modified_async(self, view):\n\t\tvs = view.settings()\n\t\tws = vs.get('WordCount', wsd)\n\t\tws['selection'] =  True\n\t\tvs.set('WordCount', ws)\n\n\tdef on_close(self, view):\n\t\tPref.view = False\n\n\tdef asap(self, view):\n\t\tPref.view = view\n\t\tPref.elapsed_time = 0.4\n\t\tsublime.set_timeout(lambda:WordCount().run(True), 0)\n\n\tdef run(self, asap = False):\n\t\tif not Pref.view:\n\t\t\tself.guess_view()\n\t\telse:\n\t\t\tview = Pref.view\n\t\t\tvs = view.settings()\n\t\t\tws = vs.get('WordCount', wsd)\n\t\t\tif vs.get('is_widget') or not ws: # (if not ws)WTF, happens when closing a view\n\t\t\t\tself.guess_view()\n\t\t\telse:\n\t\t\t\tif (ws['modified'] or ws['selection']) and (Pref.running == False or asap) and self.should_run_with_syntax(view):\n\t\t\t\t\tsel = view.sel()\n\t\t\t\t\tif sel:\n\t\t\t\t\t\tif len(sel) == 1 and sel[0].empty():\n\t\t\t\t\t\t\tif not Pref.enable_live_count or view.size() > 10485760:\n\t\t\t\t\t\t\t\tview.erase_status('WordCount')\n\t\t\t\t\t\t\telif view.change_count() != ws['changes']:\n\t\t\t\t\t\t\t\tws['changes'] = view.change_count()\n\t\t\t\t\t\t\t\t#  print('running:'+str(view.change_count()))\n\t\t\t\t\t\t\t\tWordCountThread(view, [view.substr(sublime.Region(0, view.size()))], view.substr(view.line(view.sel()[0].end())), False).start()\n\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\t# print('running from cache:'+str(view.change_count()))\n\t\t\t\t\t\t\t\tview.set_status('WordCount', self.makePlural('Word', ws['count'] ))\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\t\tWordCountThread(view, [view.substr(sublime.Region(s.begin(), s.end())) for s in sel], view.substr(view.line(view.sel()[0].end())), True).start()\n\t\t\t\t\t\t\texcept:\n\t\t\t\t\t\t\t\tpass\n\t\t\t\t\t\tws['modified'] = False\n\t\t\t\t\t\tws['selection'] = False\n\t\t\t\t\t\tvs.set('WordCount', ws)\n\n\n\tdef guess_view(self):\n\t\tif sublime.active_window() and sublime.active_window().active_view():\n\t\t\tPref.view = sublime.active_window().active_view()\n\n\tdef display(self, view, on_selection, word_count, char_count, word_count_line, char_count_line):\n\n\t\tm = int(word_count / Pref.readtime_wpm)\n\t\ts = int(word_count % Pref.readtime_wpm / (Pref.readtime_wpm / 60))\n\n\t\tstatus = []\n\n\t\tif word_count:\n\t\t\tstatus.append(self.makePlural('Word', word_count))\n\n\t\tif Pref.enable_count_chars and char_count > 0:\n\t\t\tstatus.append(self.makePlural('Char', char_count))\n\n\t\tif Pref.enable_line_word_count and word_count_line > 1:\n\t\t\tstatus.append( \"%d Words in Line\" % (word_count_line))\n\n\t\tif Pref.enable_line_char_count and char_count_line > 1:\n\t\t\tstatus.append(\"%d Chars in Line\" % (char_count_line))\n\n\t\tif Pref.enable_count_lines:\n\t\t\tlines = (view.rowcol(view.size())[0] + 1)\n\t\t\tif lines > 1:\n\t\t\t\tstatus.append('%d Lines' % (view.rowcol(view.size())[0] + 1))\n\n\t\tif Pref.enable_count_pages and word_count > 0:\n\t\t\tif not Pref.page_count_mode_count_words or Pref.words_per_page < 1:\n\t\t\t\tvisible = view.visible_region()\n\t\t\t\trows_per_page = (view.rowcol(visible.end())[0]) - (view.rowcol(visible.begin())[0])\n\t\t\t\tpages = ceil((view.rowcol(view.size()-1)[0] + 1 ) /  rows_per_page)\n\t\t\t\tcurrent_line = view.rowcol(view.sel()[0].begin())[0]+1\n\t\t\t\tcurrent_page = ceil(current_line / rows_per_page)\n\t\t\telse:\n\t\t\t\tpages = ceil(word_count / Pref.words_per_page)\n\t\t\t\trows = view.rowcol(view.size()-1)[0] + 1\n\t\t\t\tcurrent_line = view.rowcol(view.sel()[0].begin())[0]+1\n\t\t\t\tcurrent_page = ceil((current_line / Pref.words_per_page) / (rows / Pref.words_per_page))\n\n\t\t\tif pages > 1:\n\t\t\t\tif current_page != 0:\n\t\t\t\t\tstatus.append('Page '+str(current_page)+'/'+str(pages))\n\t\t\t\telse:\n\t\t\t\t\tstatus.append('Page '+str(current_page)+'/'+str(pages))\n\n\t\tif Pref.enable_readtime and s >= 1:\n\t\t\tstatus.append(\"~%dm %ds reading time\" % (m, s))\n\n\t\tview.set_status('WordCount', ', '.join(status))\n\n\tdef makePlural(self, word, count):\n\t\treturn \"%s %s%s\" % (count, word, (\"s\" if count != 1 else \"\"))\n\nclass WordCountThread(threading.Thread):\n\n\tdef __init__(self, view, content, content_line, on_selection):\n\t\tthreading.Thread.__init__(self)\n\t\tself.view = view\n\t\tself.content = content\n\t\tself.content_line = content_line\n\t\tself.on_selection = on_selection\n\n\t\tself.char_count = 0\n\t\tself.word_count_line = 0\n\t\tself.chars_in_line = 0\n\n\t\tws = view.settings().get('WordCount', wsd)\n\t\tself.syntax = ws['syntax']\n\n\tdef run(self):\n\t\t# print ('running:'+str(time.time()))\n\t\tPref.running         = True\n\n\t\tif self.syntax and self.syntax in Pref.strip:\n\t\t\tfor item in Pref.strip[self.syntax]:\n\t\t\t\tfor k in range(len(self.content)):\n\t\t\t\t\tself.content[k] = re.sub(item, '', self.content[k])\n\t\t\t\tself.content_line = re.sub(item, '', self.content_line)\n\n\t\tself.word_count      = sum([self.count(region) for region in self.content])\n\n\t\tif Pref.enable_count_chars:\n\t\t\tif Pref.char_ignore_whitespace:\n\t\t\t\tself.char_count  = sum([len(''.join(region.split())) for region in self.content])\n\t\t\telse:\n\t\t\t\tself.char_count  = sum([len(region) for region in self.content])\n\n\t\tif Pref.enable_line_word_count:\n\t\t\tself.word_count_line = self.count(self.content_line)\n\n\t\tif Pref.enable_line_char_count:\n\t\t\tif Pref.char_ignore_whitespace:\n\t\t\t\tself.chars_in_line = len(''.join(self.content_line.split()))\n\t\t\telse:\n\t\t\t\tself.chars_in_line = len(self.content_line)\n\n\t\tif not self.on_selection:\n\t\t\tvs = self.view.settings()\n\t\t\tws = vs.get('WordCount', wsd)\n\t\t\tws['count'] = self.word_count\n\t\t\tvs.set('WordCount', ws)\n\n\t\tsublime.set_timeout(lambda:self.on_done(), 0)\n\n\tdef on_done(self):\n\t\ttry:\n\t\t\tWordCount().display(self.view, self.on_selection, self.word_count, self.char_count, self.word_count_line, self.chars_in_line)\n\t\texcept:\n\t\t\tpass\n\t\tPref.running = False\n\n\tdef count(self, content):\n\n\t\t# begin = time.time()\n\n\t\t#=====1\n\t\t# wrdRx = Pref.wrdRx\n\t\t# \"\"\"counts by counting all the start-of-word characters\"\"\"\n\t\t# # regex to find word characters\n\t\t# matchingWrd = False\n\t\t# words = 0\n\t\t# space_symbols = [' ', '\\r', '\\n']\n\t\t# for ch in content:\n\t\t# # \t# test if this char is a word char\n\t\t# \tisWrd = ch not in space_symbols\n\t\t# \tif isWrd and not matchingWrd:\n\t\t# \t\twords = words + 1\n\t\t# \t\tmatchingWrd = True\n\t\t# \tif not isWrd:\n\t\t# \t\tmatchingWrd = False\n\n\t\t#=====2\n\t\twrdRx = Pref.wrdRx\n\t\tsplitRx = Pref.splitRx\n\t\tif splitRx:\n\t\t\twords = len([1 for x in splitRx(content) if False == x.isdigit() and wrdRx(x)])\n\t\telse:\n\t\t\twords = len([1 for x in content.replace(\"'\", '').replace('—', ' ').replace('–', ' ').replace('-', ' ').split() if False == x.isdigit() and wrdRx(x)])\n\n\t\t# Pref.elapsed_time = end = time.time() - begin;\n\t\t# print ('Benchmark: '+str(end))\n\n\t\treturn words\n\ndef word_count_loop():\n\tword_count = WordCount().run\n\twhile True:\n\t\t# sleep time is adaptive, if takes more than 0.4 to calculate the word count\n\t\t# sleep_time becomes elapsed_time*3\n\t\tif Pref.running == False:\n\t\t\tsublime.set_timeout(lambda:word_count(), 0)\n\t\ttime.sleep((Pref.elapsed_time*3 if Pref.elapsed_time > 0.4 else 0.4))"
  },
  {
    "path": "WordCount.sublime-settings",
    "content": "{\n\t\"enable_live_count\": true,\n\n\t\"enable_readtime\": false,\n\t\"readtime_wpm\": 200,\n\t\"char_ignore_whitespace\": true,\n\n\t\"enable_line_word_count\": false,\n\t\"enable_line_char_count\": false,\n\n\t\"enable_count_chars\": false,\n\t\"enable_count_lines\": false,\n\n\t\"enable_count_pages\": true,\n\t\"words_per_page\": 300,\n\t\"page_count_mode_count_words\": true,\n\n\t\"whitelist_syntaxes\": [],\n\t\"blacklist_syntaxes\": [\"CSS\", \"SQL\", \"JavaScript\", \"Python\", \"PHP\", \"JSON\"],\n\n\t/* please use lowercase for the syntax names in the following section: */\n\t\"strip\": {\n\t\t\"php\": [\n\t\t\t\"<[^>]*>\"\n\t\t],\n\t\t\"html\": [\n\t\t\t\"<[^>]*>\"\n\t\t]\n\t}\n}"
  },
  {
    "path": "license.txt",
    "content": "\"None are so hopelessly enslaved as those who falsely believe they are free.\"\n                                              Johann Wolfgang von Goethe\n\nCopyright (C) 2012 Tito Bouzout <tito.bouzout@gmail.com>\n\nThis license apply to all the files inside this program unless noted\ndifferent for some files or portions of code inside these files.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation. http://www.gnu.org/licenses/gpl.html\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see http://www.gnu.org/licenses/gpl.html"
  },
  {
    "path": "readme.md",
    "content": "\n\n## Description\n\nProvides a real-time Word Count and character count in the status-bar for Sublime Text. See: http://www.sublimetext.com/\n\nCount words and/or characters on document or in selections. By default, whitespace is not included in the character count.\n\nThe minimal word length is 1 and does not count digits.\n\nAn estimated reading time is now appended to the end of the word count.\n\n## Installation\n\nZipped file:\n1. Locate the Packages directory for Sublime Text.\n2. Download the zipped contents of this repository into Packages.\n3. Unzip the zipped file. It will create a new directory in Packages.\n4. Re-start Sublime Text.\n\nCloned files:\n1. Locate the Packages directory for Sublime Text.\n2. In the Packages directory, create a sub-directory called WordCount.\n3. Clone the contents of this repository to your new WordCount folder.\n4. Re-start Sublime Text.\n\n## Preferences\nLocated under Sublime Text>Preferences>Package Settings>Settings — User\n(You probably need to copy the default settings from the uneditable Sublime Text>Preferences>Package Settings>Settings — **Default**)\n\n - `enable_live_count` : true\n\n\t\tAllows to control if the live word counter is enabled. Otherwise will be enabled for selections only.\n\n - `enable_readtime` : false\n\n\t\tAllows you to control if the estimated reading time is enabled.\n\t\tReading time is only displayed when there is a time > 1s.\n\n - `readtime_wpm` : 200\n\n\t\tSets the WPM to calculate the Estimated Reading Time for the file.\n\n - `whitelist_syntaxes` : []\n\n\t\tAn array of syntax names that WordCount should run on.\n\t\tExample: [\"Plain text\", \"Markdown\"]\n\t\tIf the array is empty, like it is by default, WordCount will run on any syntax.\n\n - `blacklist_syntaxes` : []\n\n\t\tAn array of syntax names that WordCount should not run on.\n\t\tExample: [\"Plain text\", \"Markdown\"]\n\t\tIf the array is empty, like it is by default, WordCount will run on any syntax.\n\n - `char_ignore_whitespace` : true\n\n\t\tWhether to skip whitespace for the character count.\n\n - `enable_line_word_count` : false\n\n\t\tDisplay the count of words found on current line.\n\n - `enable_line_char_count` : false\n\n\t\tDisplay the count of characters found on current line.\n\n - `enable_count_lines` : false\n\n\t\tDisplay the number of lines in file\n\n - `enable_count_chars` : false\n\n\t\tDisplay the number of characters in file\n\n - `enable_count_pages` : true\n\n\t\tDisplay the number of pages in file\n\n - `page_count_mode_count_words` : true\n\n\t\tSets the page count mode to words per page\n\n - `words_per_page` : 300\n\n\t\tSets the number of words per page used to calculate number of pages\n\n - `word_regexp` : \"\"\n\n\t\tWord Regular expression. Defaults empty, an internal regular expression is used. If the portion of text matches this RegExp then the word is counted.\n\n - `word_split` : \"\"\n\n\t\tSplit portions of text to test later as words with a Regular expression. Defaults to String.split() with no arguments, means that content will trim() and empty values (all whitespaces) are not used. In case of containing some value different than empty, the return of \"re.findall\" will be used.\n\n - `split`: {}\n\n\t\tRemove regex patterns by syntax. Use lowercase for the syntax names.\n\n\t\tExample to ignore all tags, including comments, from HTML:\n\n\t\t```\n\t\t\"strip\": {\n\t\t\t\"html\": [\n\t\t\t\t\"<[^>]*>\"\n\t\t\t]\n\t\t}\n\t\t```\n\n## Inspiration\n\n - The main loop inspired by sublimelint https://github.com/lunixbochs/sublimelint\n - The count inspired by the original WordCount plugin http://code.google.com/p/sublime-text-community-packages/source/browse/#svn%2Ftrunk%2FWordCount committed by mindfiresoftware\n\n## Contributors\n\n - Liam Cain\n - Lee Grey\n - Hawken Rives\n - Yaw Anokwa\n - James Brooks\n - Antony Male\n - Alex Galonsky\n - RikkiMongoose\n - ChrisJefferson\n - Harry Ng. (From [Word Count Tool](http://wordcounttools.com/))\n - MangleKuo\n - Nick Cody\n - Amanda Neumann\n"
  }
]