diff --git a/Context.sublime-menu b/Context.sublime-menu index 0bd17ef..e825417 100644 --- a/Context.sublime-menu +++ b/Context.sublime-menu @@ -6,10 +6,5 @@ "caption": "Navigate to Definition", "command": "navigate_to_definition", "args": {} - }, - { - "caption": "Jump Back", - "command": "jump_prev", - "args": {} } ] diff --git a/Default.sublime-keymap b/Default.sublime-keymap index 1086006..c2db4c2 100644 --- a/Default.sublime-keymap +++ b/Default.sublime-keymap @@ -12,11 +12,11 @@ "keys": ["ctrl+t", "ctrl+y"] }, { - "command": "jump_prev", + "command": "jump_back", "keys": ["ctrl+t", "ctrl+b"] }, { - "command": "jump_prev", + "command": "jump_back", "keys": ["ctrl+shift+comma"] }, { diff --git a/Default.sublime-mousemap b/Default.sublime-mousemap index a069a4b..ccaa81b 100644 --- a/Default.sublime-mousemap +++ b/Default.sublime-mousemap @@ -6,10 +6,10 @@ "modifiers": ["ctrl", "shift"], "command": "navigate_to_definition" }, - { - "button": "button2", - "count": 1, - "modifiers": ["ctrl", "shift"], - "command": "jump_prev" - } + // { + // "button": "button2", + // "count": 1, + // "modifiers": ["ctrl", "shift"], + // "command": "jump_back" + // } ] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2579112 --- /dev/null +++ b/README.md @@ -0,0 +1,208 @@ +# CTags + +![CI](https://github.com/SublimeText/CTags/actions/workflows/ci.yaml/badge.svg) + +This [Sublime Text][] package provides support for working with tags generated +by [Exuberant CTags][] or [Universal CTags][]. + +`ctags` command is searched for on the system PATH. It works by doing a binary +search of a memory-mapped tags file, so it will work efficiently with very large +(50MB+) tags files if needed. + + +## Installation + +### Package Control + +The easiest way to install is using [Package Control][]. It's listed as `CTags`. + +1. Open `Command Palette` using menu item `Tools → Command Palette...` +2. Choose `Package Control: Install Package` +3. Find `CTags` and hit `Enter` + +### Manual Download + +1. [Download the `.zip`][release] +2. Unzip and rename folder to `CTags` +3. Copy folder into `Packages` directory, + which can be found using the menu item `Preferences → Browse Packages...` + +### Using Git + +Go to your Sublime Text Packages directory and clone the repository +using the command below:: + +```sh +git clone https://github.com/SublimeText/CTags +``` + + +## Additional Setup Steps + +### Linux + +To install ctags use your package manager. + +* For Debian-based systems (Ubuntu, Mint, etc.):: + + ```sh + sudo apt-get install exuberant-ctags + ``` + + or + + ```sh + sudo apt-get install universal-ctags + ``` + +* For Red Hat-based systems (Red Hat, Fedora, CentOS):: + + ```sh + sudo yum install ctags + ``` + +### MacOS + +The default `ctags` executable in OSX does not support recursive directory +search (i.e. `ctags -R`). To get a proper copy of ctags, use one of the +following options: + +* Using [Homebrew][] + + ```sh + brew install ctags + ``` + +* Using [MacPorts][] + + ```sh + port install ctags + ``` + +Ensure that the `PATH` is updated so the correct version is run: + +* If `which ctags` doesn't point at ctags in `/usr/local/bin`, make sure + you add `/usr/local/bin` to your `PATH` ahead of the folder + `which ctags` reported. +* Alternatively, add the path to the new `ctags` executable to the settings, + under `command`. If you have Xcode / Apple Developer Tools installed this + path will likely be `/usr/local/bin/ctags`. + +### Windows + +* Download [Exuberant CTags binary][] or [Universal CTags binary][] + +* Extract `ctags.exe` from the downloaded zip to + `C:\Program Files\Sublime Text` or any folder within your PATH so that + Sublime Text can run it. + +* Alternatively, extract to any folder and add the path to this folder to + the `command` setting. + + +## Usage + +This uses tag files created by the `ctags -R -f .tags` command by default +(although this can be overridden in settings). + +The plugin will try to find a `.tags` file in the same directory as the +current view, walking up directories until it finds one. If it can't find one +it will offer to build one (in the directory of the current view) + +If a symbol can't be found in a tags file, it will search in additional +locations that are specified in the `CTags.sublime-settings` file (see +below). + +If you are a Rubyist, you can build a Ruby Gem's tags with the following +script: + +```ruby +require 'bundler' +paths = Bundler.load.specs.map(&:full_gem_path) +system("ctags -R -f .gemtags #{paths.join(' ')}") +``` + + +## Settings + +To open CTags.sublime-settings + +1. Open `Command Palette` using menu item `Tools → Command Palette...` +2. Choose `Preferences: CTags Settings` and hit `Enter` + +--- + +* `filters` will allow you to set scope specific filters against a field of + the tag. In the excerpt above, imports tags like `from a import b` are + filtered: + + ``` + '(?P[^\t]+)\t' + '(?P[^\t]+)\t' + '(?P.*?);"\t' + '(?P[^\t\r\n]+)' + '(?:\t(?P.*))?' + ``` + +* `extra_tag_paths` is a list of extra places to look for keyed by +* `(selector, platform)`. Note the `platform` is tested against + `sublime.platform()` so any values that function returns are valid. +* `extra_tag_files` is a list of extra files relative to the original file +* `command` is the path to the version of ctags to use, for example:: + + ```jsonc + "command" : "/usr/local/bin/ctags" + ``` + + or: + + ```jsonc + "command" : "C:\\Users\\\\Downloads\\CTags\\ctag.exe" + ``` + +The rest of the options are fairly self explanatory. + +### Hide .tags files from side bar + +By default, Sublime will include ctags files in your project, which causes +them to show up in the file tree and search results. To disable this behaviour +you should add a `file_exclude_patterns` entry to your +`Preferences.sublime-settings` or your project file. For example: + +```jsonc +"file_exclude_patterns": [".tags", ".tags_sorted_by_file", ".gemtags"] +``` + + +## Support + +If there are any problems or you have a suggestion, [open an issue][issues], and +we will receive a notification. + + +## Commands Listing + +| Command | Key Binding | Alt Binding | Mouse Binding +|--- |--- |--- |--- +| rebuild_ctags | ctrl+t, ctrl+r | | +| navigate_to_definition | ctrl+t, ctrl+t | ctrl+> | ctrl+shift+left_click +| jump_back | ctrl+t, ctrl+b | ctrl+< | ctrl+shift+right_click +| show_symbols | alt+s | | +| show_symbols (all files) | alt+shift+s | | +| show_symbols (suffix) | ctrl+alt+shift+s | | + + +[issues]: https://github.com/SublimeText/CTags/issues +[release]: https://github.com/SublimeText/CTags/releases/latest + +[Sublime Text]: http://sublimetext.com/ +[Package Control]: http://packagecontrol.io/ + +[Exuberant CTags]: http://ctags.sourceforge.net/ +[Exuberant CTags binary]: http://prdownloads.sourceforge.net/ctags/ctags58.zip + +[Universal CTags]: https://github.com/universal-ctags/ctags +[Universal CTags binary]: https://github.com/universal-ctags/ctags-win32/releases/latest + +[Homebrew]: https://brew.sh/ +[MacPorts]: https://www.macports.org/ diff --git a/README.rst b/README.rst deleted file mode 100644 index 136561a..0000000 --- a/README.rst +++ /dev/null @@ -1,185 +0,0 @@ -===== -CTags -===== - -.. image:: https://github.com/SublimeText/CTags/actions/workflows/ci.yaml/badge.svg - -About -===== - -This `Sublime Text`_ package provides support for working with tags -generated by `Exuberant CTags`_ - -.. _Sublime Text: http://sublimetext.com/ -.. _Exuberant CTags: http://ctags.sourceforge.net/ - -The ctags command is searched for on the system PATH. It works by doing a -binary search of a memory-mapped tags file, so it will work efficiently with -very large (50MB+) tags files if needed. - -See this `forum thread`_ for a bit of historical background on the Sublime Text plugin. - -.. _forum thread: http://www.sublimetext.com/forum/viewtopic.php?f=5&t=144 - -Installation -============ - -The easiest way to install this plugin, is to use the `Package Control`_ . - -.. _Package Control: http://packagecontrol.io/ - -Alternatively, the plugin can be installed manually using one of the following -methods. - -Using Git ---------- -Go to your Sublime Text Packages directory and clone the repository -using the command below:: - - $ git clone https://github.com/SublimeText/CTags - -Manual Download ---------------- - -* Download the files using the .zip download option -* Unzip the files (and rename the folder to CTags if needed) -* Copy the folder to your Sublime Text Packages directory - -Additional Setup Steps -====================== - -OS X ----- - -The default ``ctags`` executable in OSX does not support recursive directory -search (i.e. ``ctags -R``). To get a proper copy of ctags, use one of the -following options: - -* Using `Homebrew`_:: - - brew install ctags - -* Using `MacPorts`_:: - - port install ctags - -.. _`Homebrew`: http://mxcl.github.com/homebrew/ -.. _`MacPorts`: http://www.macports.org/ - -Ensure that the ``PATH`` is updated so the correct version is run: - -* If ``which ctags`` doesn't point at ctags in ``/usr/local/bin``, make sure - you add ``/usr/local/bin`` to your ``PATH`` ahead of the folder - ``which ctags`` reported. -* Alternatively, add the path to the new ``ctags`` executable to the settings, - under ``command``. If you have Xcode / Apple Developer Tools installed this - path will likely be ``/usr/local/bin/ctags``. - -Linux ------ - -To install ctags use your package manager. - -* For Debian-based systems (Ubuntu, Mint, etc.):: - - sudo apt-get install exuberant-ctags - -* For Red Hat-based systems (Red Hat, Fedora, CentOS):: - - sudo yum install ctags - -And so forth - -Windows -------- - -* Download the `CTags binary`_ from the `Exuberant CTags`_ site. -* Extract ``ctags.exe`` from the downloaded zip to - ``C:\Program Files\Sublime Text`` or any folder within your PATH so that - Sublime Text can run it. -* Alternatively, extract to any folder and add the path to this folder to - the ``command`` setting. - -.. _CTags binary: http://prdownloads.sourceforge.net/ctags/ctags58.zip - -Usage -===== - -This uses tag files created by the ``ctags -R -f .tags`` command by default -(although this can be overriden in settings). - -The plugin will try to find a ``.tags`` file in the same directory as the -current view, walking up directories until it finds one. If it can't find one -it will offer to build one (in the directory of the current view) - -If a symbol can't be found in a tags file, it will search in additional -locations that are specified in the ``CTags.sublime-settings`` file (see -below). - -If you are a Rubyist, you can build a Ruby Gem's tags with the following -script:: - - require 'bundler' - paths = Bundler.load.specs.map(&:full_gem_path) - system("ctags -R -f .gemtags #{paths.join(' ')}") - -Settings -======== - -By default, Sublime will include ctags files in your project, which causes -them to show up in the file tree and search results. To disable this behaviour -you should add a ``file_exclude_patterns`` entry to your -``Preferences.sublime-settings`` or your project file. For example:: - - "file_exclude_patterns": [".tags", ".tags_sorted_by_file", ".gemtags"] - -In addition to this setting, there's a ``CTags.sublime-settings`` file, which -can be edited like any other ``.sublime-settings`` file - -* ``filters`` will allow you to set scope specific filters against a field of - the tag. In the excerpt above, imports tags like ``from a import b`` are - filtered:: - - '(?P[^\t]+)\t' - '(?P[^\t]+)\t' - '(?P.*?);"\t' - '(?P[^\t\r\n]+)' - '(?:\t(?P.*))?' - -* ``extra_tag_paths`` is a list of extra places to look for keyed by -* ``(selector, platform)``. Note the ``platform`` is tested against - ``sublime.platform()`` so any values that function returns are valid. -* ``extra_tag_files`` is a list of extra files relative to the original file -* ``command`` is the path to the version of ctags to use, for example:: - - "command" : "/usr/local/bin/ctags" - - or:: - - "command" : "C:\\Users\\\\Downloads\\CTags\\ctag.exe" - -The rest of the options are fairly self explanatory. - -Support -======= - -If there are any problems or you have a suggestion, `open an issue`_, and we -will receive a notification. - -.. _open an issue: https://github.com/SublimeText/CTags/issues - -Thanks :) - -Commands Listing -================ - -============================== ================ =========== ====================== -Command Key Binding Alt Binding Mouse Binding -============================== ================ =========== ====================== -rebuild_ctags ctrl+t, ctrl+r -navigate_to_definition ctrl+t, ctrl+t ctrl+> ctrl+shift+left_click -jump_prev ctrl+t, ctrl+b ctrl+< ctrl+shift+right_click -show_symbols alt+s -show_symbols (all files) alt+shift+s -show_symbols (suffix) ctrl+alt+shift+s -============================== ================ =========== ====================== diff --git a/plugin.py b/plugin.py index 1be5085..204bba4 100644 --- a/plugin.py +++ b/plugin.py @@ -23,7 +23,6 @@ # Publish Commands and EventListeners from .plugins.cmds import ( CTagsAutoComplete, - JumpPrev, NavigateToDefinition, RebuildTags, SearchForDefinition, diff --git a/plugins/activity_indicator.py b/plugins/activity_indicator.py new file mode 100644 index 0000000..62fd1c2 --- /dev/null +++ b/plugins/activity_indicator.py @@ -0,0 +1,123 @@ +import sublime +from threading import RLock + + +class ActivityIndicator: + """ + An animated text-based indicator to show that some activity is in progress. + + The `target` argument should be a :class:`sublime.View` or :class:`sublime.Window`. + The indicator will be shown in the status bar of that view or window. + If `label` is provided, then it will be shown next to the animation. + + :class:`ActivityIndicator` can be used as a context manager. + """ + + def __init__(self, label=None): + self.label = label + self.interval = 120 + self._lock = RLock() + self._running = False + self._ticks = 0 + self._view = None + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop() + + def clear(self): + if self._view: + self._view.erase_status("_ctags_activity") + self._view = None + + def start(self): + """ + Start displaying the indicator and animate it. + + :raise RuntimeError: if the indicator is already running. + """ + + with self._lock: + if self._running: + raise RuntimeError("Timer is already running") + self._running = True + self._ticks = 0 + self.update(self.render_indicator_text()) + sublime.set_timeout(self.tick, self.interval) + + def stop(self): + """ + Stop displaying the indicator. + + If the indicator is not running, do nothing. + """ + + with self._lock: + if self._running: + self._running = False + self.clear() + + def finish(self, message): + """ + Stop the indicator and display a final status message + + :param message: + The final status message to display + """ + + def clear(): + with self._lock: + self.clear() + + if self._running: + with self._lock: + self._running = False + self.update(message) + + sublime.set_timeout(clear, 4000) + + def tick(self): + """ + Invoke status bar update with specified interval. + """ + + if self._running: + self._ticks += 1 + self.update(self.render_indicator_text()) + sublime.set_timeout(self.tick, self.interval) + + def update(self, text): + """ + Update activity indicator and label in status bar. + + :param text: + The text to display in the status bar + """ + + view = sublime.active_window().active_view() + if view and view != self._view: + if self._view: + self._view.erase_status("_ctags_activity") + self._view = view + if self._view: + self._view.set_status("_ctags_activity", text) + + def render_indicator_text(self): + """ + Render activity indicator and label. + + :returns: + The activity indicator string to display in the status bar + """ + + text = "⣷⣯⣟⡿⢿⣻⣽⣾"[self._ticks % 8] + if self.label: + text += " " + self.label + return text + + def set_label(self, label): + with self._lock: + self.label = label diff --git a/plugins/cmds.py b/plugins/cmds.py index 38a8071..a0bb9d5 100644 --- a/plugins/cmds.py +++ b/plugins/cmds.py @@ -1,4 +1,3 @@ -import codecs import functools import locale import os @@ -8,7 +7,7 @@ import subprocess import threading -from collections import defaultdict, deque +from collections import defaultdict from itertools import chain from operator import itemgetter as iget @@ -16,6 +15,8 @@ import sublime_plugin from sublime import status_message, error_message +from .activity_indicator import ActivityIndicator + from .ctags import ( FILENAME, PATH_ORDER, @@ -226,9 +227,7 @@ def get_alternate_tags_paths(view, tags_file): # read and add additional tag file paths from file if os.path.exists(tags_paths): - search_paths.extend( - codecs.open(tags_paths, encoding="utf-8").read().split("\n") - ) + search_paths.extend(open(tags_paths, encoding="utf-8").read().split("\n")) # read and add additional tag file paths from 'extra_tag_paths' setting try: @@ -296,11 +295,11 @@ def find_with_scope(view, pattern, scope, start_pos=0, cond=True, flags=0): f = view.find(pattern, start_pos, flags) if not f or view.match_selector(f.begin(), scope) is cond: - break + return f else: start_pos = f.end() - return f + return None def find_source(view, pattern, start_at, flags=sublime.LITERAL): @@ -350,6 +349,8 @@ def and_then(view): symbol_region = view.find( escape_regex(search_symbol) + r"(?:[^_]|$)", look_from, 0 ) + else: + symbol_region = None if do_find and symbol_region: # Using reversed symbol_region so cursor stays in front of the @@ -451,106 +452,9 @@ def get_current_file_suffix(path): return file_suffix -# -# Sublime Commands -# - -# JumpPrev Commands - - -class JumpPrev(sublime_plugin.WindowCommand): - """ - Provide ``jump_back`` command. - - Command "jumps back" to the previous code point before a tag was navigated - or "jumped" to. - - This is functionality supported natively by ST3 but not by ST2. It is - therefore included for legacy purposes. - """ - - buf = deque(maxlen=100) # virtually a "ring buffer" - - def is_enabled(self): - # disable if nothing in the buffer - return len(self.buf) > 0 - - def is_visible(self): - return setting("show_context_menus") - - def run(self): - if not self.buf: - return status_message("JumpPrev buffer empty") - - file_name, sel = self.buf.pop() - self.jump(file_name, sel) - - def jump(self, path, sel): - @on_load(path, begin_edit=True) - def and_then(view): - select(view, sel) - - @classmethod - def append(cls, view): - """Append a code point to the list""" - name = view.file_name() - if name: - sel = [s for s in view.sel()][0] - cls.buf.append((name, sel)) - - # CTags commands -def show_build_panel(view): - """ - Handle build ctags command. - - Allows user to select whether tags should be built for the current file, - a given directory or all open directories. - """ - display = [] - - if view.file_name() is not None: - if not setting("recursive"): - display.append(["Open File", view.file_name()]) - else: - display.append(["Open File's Directory", os.path.dirname(view.file_name())]) - - if len(view.window().folders()) > 0: - # append option to build for all open folders - display.append( - [ - "All Open Folders", - "; ".join( - [ - "'{0}'".format(os.path.split(x)[1]) - for x in view.window().folders() - ] - ), - ] - ) - # Append options to build for each open folder - display.extend([[os.path.split(x)[1], x] for x in view.window().folders()]) - - def on_select(i): - if i != -1: - if display[i][0] == "All Open Folders": - paths = view.window().folders() - else: - paths = display[i][1:] - - command = setting("command") - recursive = setting("recursive") - tag_file = setting("tag_file") - opts = read_opts(view) - - rebuild_tags = RebuildTags(False) - rebuild_tags.build_ctags(paths, command, tag_file, recursive, opts) - - view.window().show_quick_panel(display, on_select) - - def show_tag_panel(view, result, jump_directly): """ Handle tag navigation command. @@ -565,7 +469,6 @@ def show_tag_panel(view, result, jump_directly): def on_select(i): if i != -1: - JumpPrev.append(view) # Work around bug in ST3 where the quick panel keeps focus after # selecting an entry. # See https://github.com/SublimeText/Issues/issues/39 @@ -608,8 +511,6 @@ def check_if_building(self, **args): """ if RebuildTags.build_ctags.func.running: status_message("Tags not available until built") - if setting("display_rebuilding_message"): - error_message("Please wait while tags are built") return False return True @@ -635,7 +536,6 @@ def run(symbol, region, sym_line, mbrParts, view, tags_file): if not tags: # append to allow jump back to work - JumpPrev.append(view) view.window().run_command("goto_definition") return status_message('Can\'t find "%s"' % symbol) @@ -858,7 +758,56 @@ def run(self, dirs=None, files=None): status_message("Cannot build CTags: No file or folder open.") else: - show_build_panel(view) + self.show_build_panel(view) + + def show_build_panel(self, view): + """ + Handle build ctags command. + + Allows user to select whether tags should be built for the current file, + a given directory or all open directories. + """ + display = [] + + if view.file_name() is not None: + if not setting("recursive"): + display.append(["Open File", view.file_name()]) + else: + display.append( + ["Open File's Directory", os.path.dirname(view.file_name())] + ) + + if len(view.window().folders()) > 0: + # append option to build for all open folders + display.append( + [ + "All Open Folders", + "; ".join( + [ + "'{0}'".format(os.path.split(x)[1]) + for x in view.window().folders() + ] + ), + ] + ) + # Append options to build for each open folder + display.extend([[os.path.split(x)[1], x] for x in view.window().folders()]) + + def on_select(i): + if i != -1: + if display[i][0] == "All Open Folders": + paths = view.window().folders() + else: + paths = display[i][1:] + + command = setting("command") + recursive = setting("recursive") + tag_file = setting("tag_file") + opts = read_opts(view) + + self.build_ctags(paths, command, tag_file, recursive, opts) + + view.window().show_quick_panel(display, on_select) @threaded(msg="Already running CTags!") def build_ctags(self, paths, command, tag_file, recursive, opts): @@ -875,49 +824,43 @@ def build_ctags(self, paths, command, tag_file, recursive, opts): :returns: None """ + with ActivityIndicator("CTags: Rebuilding tags...") as progress: + for i, path in enumerate(paths, start=1): + if len(paths) > 1: + progress.update( + "CTags: Rebuilding tags [%d/%d]..." % (i, len(paths)) + ) - def tags_building(tag_file): - """Display 'Building CTags' message in all views""" - print(("Building CTags for %s: Please be patient" % tag_file)) - in_main( - lambda: status_message( - "Building CTags for {0}: Please be" " patient".format(tag_file) - ) - )() - - def tags_built(tag_file): - """Display 'Finished Building CTags' message in all views""" - print(("Finished building %s" % tag_file)) - in_main(lambda: status_message("Finished building {0}".format(tag_file)))() - in_main(lambda: tags_cache[os.path.dirname(tag_file)].clear())() - - for path in paths: - tags_building(path) - - try: - result = build_ctags( - path=path, - tag_file=tag_file, - recursive=recursive, - opts=opts, - cmd=command, - ) - except IOError as e: - error_message(e.strerror) - return - except subprocess.CalledProcessError as e: - if sublime.platform() == "windows": - str_err = " ".join(e.output.decode("windows-1252").splitlines()) - else: - str_err = e.output.decode(locale.getpreferredencoding()).rstrip() + try: + result = build_ctags( + path=path, + tag_file=tag_file, + recursive=recursive, + opts=opts, + cmd=command, + ) + except IOError as e: + error_message(e.strerror) + return + except subprocess.CalledProcessError as e: + if sublime.platform() == "windows": + str_err = " ".join(e.output.decode("windows-1252").splitlines()) + else: + str_err = e.output.decode( + locale.getpreferredencoding() + ).rstrip() + + error_message(str_err) + return + except Exception as e: + error_message( + "An unknown error occured.\nCheck the console for info." + ) + raise e - error_message(str_err) - return - except Exception as e: - error_message("An unknown error occured.\nCheck the console for info.") - raise e + in_main(lambda: tags_cache[os.path.dirname(result)].clear())() - tags_built(result) + progress.finish("Finished building tags!") if tag_file in ctags_completions: del ctags_completions[tag_file] # clear the cached ctags list @@ -988,7 +931,7 @@ def __next__(self): def co_routine(self, view): tag_file = find_tags_relative_to(view.file_name(), setting("tag_file")) - with codecs.open(tag_file, encoding="utf-8") as tf: + with open(tag_file, encoding="utf-8") as tf: tags = parse_tag_lines(tf, tag_class=TagElements) print("Starting Test") diff --git a/plugins/ctags.py b/plugins/ctags.py index f5deb81..bafa69c 100644 --- a/plugins/ctags.py +++ b/plugins/ctags.py @@ -3,7 +3,6 @@ """ import bisect -import codecs import mmap import os import re @@ -363,7 +362,7 @@ def resort_ctags(tag_file): """ groups = {} - with codecs.open(tag_file, encoding="utf-8", errors="replace") as file_: + with open(tag_file, encoding="utf-8", errors="replace") as file_: for line in file_: # meta data not needed in sorted files if line.startswith("!_TAG"): @@ -375,7 +374,7 @@ def resort_ctags(tag_file): if len(split) > FILENAME: groups.setdefault(split[FILENAME], []).append(line) - with codecs.open( + with open( tag_file + "_sorted_by_file", "w", encoding="utf-8", errors="replace" ) as file_: for group in sorted(groups): @@ -445,9 +444,6 @@ class TagFile(object): (prefix, suffix, exact), getting the directory of a tag and so forth. """ - file_o = None - mapped = None - def __init__(self, path, column): """ Initialise object. @@ -462,16 +458,21 @@ def __init__(self, path, column): """ self.path = path self.column = column + self.file = None + self.mmap = None def __getitem__(self, index): """ Provide sequence-type interface to tag file. """ - self.mapped.seek(index) - result = self.mapped.readline() + if not self.mmap: + raise RuntimeError("No tag file open.") + + self.mmap.seek(index) + result = self.mmap.readline() if index != 0: # handle first line - result = self.mapped.readline() # get a complete line + result = self.mmap.readline() # get a complete line result = result.strip() if not result: @@ -483,7 +484,10 @@ def __len__(self): """ Get size of tag file in bytes. """ - return len(self.mapped) + if not self.mmap: + raise RuntimeError("No tag file open.") + + return len(self.mmap) def __enter__(self): """ @@ -509,15 +513,20 @@ def open(self): """ Open file. """ - self.file_o = codecs.open(self.path, "r+b", encoding="utf-8") - self.mapped = mmap.mmap(self.file_o.fileno(), 0, access=mmap.ACCESS_READ) + self.file = open(self.path, "r", encoding="utf-8") + self.mmap = mmap.mmap(self.file.fileno(), 0, access=mmap.ACCESS_READ) def close(self): """ Close file. """ - self.mapped.close() - self.file_o.close() + if not self.mmap or not self.file: + raise RuntimeError("No tag file open.") + + self.mmap.close() + self.mmap = None + self.file.close() + self.file = None def search(self, exact_match=True, *tags): """ @@ -529,9 +538,12 @@ def search(self, exact_match=True, *tags): :returns: matching tags """ + if not self.mmap: + raise RuntimeError("No tag file open.") + if not tags: - while self.mapped.tell() < self.mapped.size(): - result = Tag(self.mapped.readline().strip(), self.column) + while self.mmap.tell() < self.mmap.size(): + result = Tag(self.mmap.readline().strip(), self.column) if result.line: yield result return @@ -542,12 +554,12 @@ def search(self, exact_match=True, *tags): result = self[left_index] while result.line and result[result.column] == key: yield result - result = Tag(self.mapped.readline().strip(), self.column) + result = Tag(self.mmap.readline().strip(), self.column) else: result = self[left_index] while result.line and result[result.column].startswith(key): yield result - result = Tag(self.mapped.readline().strip(), self.column) + result = Tag(self.mmap.readline().strip(), self.column) def search_by_suffix(self, suffix): """ @@ -561,7 +573,10 @@ def search_by_suffix(self, suffix): :returns: matching tags """ - for line in self.file_o: + if not self.file: + raise RuntimeError("No tag file open.") + + for line in self.file: tag = Tag(line, self.column) if tag.key.endswith(suffix): yield tag diff --git a/plugins/tests/test_ctags.py b/plugins/tests/test_ctags.py index 64a0f30..7e4fc5d 100644 --- a/plugins/tests/test_ctags.py +++ b/plugins/tests/test_ctags.py @@ -4,7 +4,6 @@ Unit tests for 'ctags.py'. """ -import codecs import os import tempfile import unittest @@ -217,7 +216,7 @@ def test_build_ctags__single_file(self): tag_file = ctags.build_ctags(path=path) - with codecs.open(tag_file, encoding="utf-8") as output: + with open(tag_file, encoding="utf-8") as output: try: content = output.readlines() filename = os.path.basename(path) @@ -239,7 +238,7 @@ def test_build_ctags__custom_tag_file(self): tag_file = ctags.build_ctags(path=path, tag_file="my_tag_file") - with codecs.open(tag_file, encoding="utf-8") as output: + with open(tag_file, encoding="utf-8") as output: try: content = output.readlines() filename = os.path.basename(path) @@ -263,7 +262,7 @@ def test_build_ctags__additional_options(self): path=path, tag_file="my_tag_file", opts="--language-force=java" ) - with codecs.open(tag_file, encoding="utf-8") as output: + with open(tag_file, encoding="utf-8") as output: try: content = output.readlines() # there should be nothing in the file but headers (due to the @@ -375,7 +374,7 @@ def test_parse_tag_lines__python(self): tag_file = ctags.build_ctags(path=path, opts=["--python-kinds=-i"]) - with codecs.open(tag_file, encoding="utf-8") as output: + with open(tag_file, encoding="utf-8") as output: try: content = output.readlines() filename = os.path.basename(path) @@ -471,7 +470,7 @@ def test_parse_tag_lines__c(self): tag_file = ctags.build_ctags(path=path) - with codecs.open(tag_file, encoding="utf-8") as output: + with open(tag_file, encoding="utf-8") as output: try: content = output.readlines() filename = os.path.basename(path) diff --git a/plugins/utils.py b/plugins/utils.py index 5d969e4..c5eed66 100644 --- a/plugins/utils.py +++ b/plugins/utils.py @@ -2,11 +2,7 @@ common utilities used by all ctags modules """ import re - -# Helper functions - import sublime -import sublime_plugin def get_settings(): @@ -117,8 +113,9 @@ def get_lang_setting(source): def compile_filters(view): - filters = [] - for selector, regexes in list(setting("filters", {}).items()): - if view.match_selector(view.sel() and view.sel()[0].begin() or 0, selector): - filters.append(regexes) - return filters + pt = view.sel() and view.sel()[0].begin() or 0 + return [ + regexes + for selector, regexes in setting("filters", {}).items() + if view.match_selector(pt, selector) + ]