Skip to content
Browse files

Merge branch 'development'

  • Loading branch information...
2 parents 79e60a7 + 081fe3b commit 7c6077557ed0a85fa8af3582c0bb7d0ba469abab @stephenfin stephenfin committed Nov 7, 2015
View
2 .gitignore
@@ -1,2 +1,2 @@
-*.pyc
+*.pyc
View
12 .travis.yml
@@ -2,11 +2,11 @@
# http://lint.travis-ci.org/
language: python
python:
- - "2.6" # sublime text 2
- - "3.3" # sublime text 3
+ - "2.6" # sublime text 2
+ - "3.3" # sublime text 3
before_install:
- - sudo apt-get install exuberant-ctags
- - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi
+ - sudo apt-get install exuberant-ctags
+ - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi
script:
- - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then python -m unittest2.__main__ discover; fi
- - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then python -m unittest2.__main__ discover; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi
View
77 CTAGS_README.rst
@@ -1,77 +0,0 @@
-TAG FILE FORMAT
-===============
-
-When not running in etags mode, each entry in the tag file consists of a
-separate line, each looking like this in the most general case::
-
- tag_name<TAB>file_name<TAB>ex_cmd;"<TAB>extension_fields
-
-The fields and separators of these lines are specified as follows:
-
-#. Tag name
-#. Single tab character
-#. Name of the file in which the object associated with the tag is located
-#. Single tab character
-#. EX command used to locate the tag within the file; generally a search
- pattern (either ``/pattern/`` or ``?pattern?``) or line number (see
- ``−−excmd``).
- Tag file format 2 (see ``−−format``) extends this EX command under certain
- circumstances to include a set of extension fields (described below)
- embedded in an EX comment immediately appended to the EX command, which
- leaves it backward-compatible with original ``vi(1)`` implementations.
-
-A few special tags are written into the tag file for internal purposes. These
-tags are composed in such a way that they always sort to the top of the file.
-Therefore, the first two characters of these tags are used a magic number to
-detect a tag file for purposes of determining whether a valid tag file is
-being overwritten rather than a source file. Note that the name of each source
-file will be recorded in the tag file exactly as it appears on the command
-line.
-
-Therefore, if the path you specified on the command line was relative to the
-current directory, then it will be recorded in that same manner in the tag
-file. See, however, the ``−−tag−relative`` option for how this behavior can be
-modified.
-
-Extension fields are tab-separated key-value pairs appended to the end of the
-EX command as a comment, as described above. These key value pairs appear in
-the general form ``key:value``. Their presence in the lines of the tag file
-are controlled by the ``−−fields`` option. The possible keys and the meaning
-of their values are as follows:
-
-access
- Indicates the visibility of this class member, where value is specific to
- the language.
-
-file
- Indicates that the tag has file-limited visibility. This key has no
- corresponding value.
-
-kind
- Indicates the type, or kind, of tag. Its value is either one of the
- corresponding one-letter flags described under the various
- ``−−<LANG>−kinds`` options above, or a full name. It is permitted (and is,
- in fact, the default) for the key portion of this field to be omitted. The
- optional behaviors are controlled with the ``−−fields`` option.
-
-implementation
- When present, this indicates a limited implementation (abstract vs. concrete)
- of a routine or class, where value is specific to the language ("virtual" or
- "pure virtual" for C++; "abstract" for Java).
-
-inherits
- When present, value is a comma-separated list of classes from which this
- class is derived (i.e. inherits from).
-
-signature
- When present, value is a language-dependent representation of the
- signature of a routine. A routine signature in its complete form specifies
- the return type of a routine and its formal argument list. This extension
- field is presently supported only for C-based languages and does not
- include the return type.
-
-In addition, information on the scope of the tag definition may be available,
-with the key portion equal to some language-dependent construct name and its
-value the name declared for that construct in the program. This scope entry
-indicates the scope in which the tag was found. For example, a tag generated
-for a C structure member would have a scope looking like ``struct:myStruct``.
View
153 CTags.sublime-settings
@@ -1,56 +1,167 @@
// Place your settings in the file "User/CTags.sublime-settings", which
// overrides the settings in here.
{
- // Enable debugging
+ // Enable debugging.
+ //
+ // When enabled, this will result in debug output being printed to the
+ // console. This can be useful for debugging issues.
"debug": false,
- // Enable autocomplete
+ // Enable auto-complete.
+ //
+ // When enabled, this turns on a basic "auto-complete" feature, similar to
+ // a very rudimentary "Intellisense(TM)". This is useful for providing
+ // better suggestions than stock Sublime Text could provide.
"autocomplete": false,
+ // Path to ctags executable.
+ //
// Alter this value if your ctags command is not in the PATH, or if using
// a different version of ctags to that in the path (i.e. for OSX).
//
// NOTE: You *should not* place entire commands here. These commands are
- // built automatically using the values below. For example:
- // GOOD: "command": "/usr/bin/ctags"
- // BAD: "command": "ctags -R -f .tags --exclude=some/path"
+ // built automatically using the values below. For example, this is OK:
+ //
+ // "command": "/usr/bin/ctags"
+ //
+ // This, on the other hand, won't work!
+ //
+ // "command": "ctags -R -f .tags --exclude=some/path"
+ //
"command": "",
- // Set to false to disable recursive search for ctag generation. This
- // translates the `-R` parameter
+ // Enable recursive searching of directories when building tag files.
+ //
+ // When enabled, this is equivalent to `-R` parameter. Set to true to
+ // enable recursive search of directories when generating tag files.
"recursive" : true,
- // Default read/write location of the tags file. This translates to the
- // `-f [FILENAME]` parameter
+ // Default read/write location of the tags file.
+ //
+ // This is equivalent to the `-f [FILENAME]` parameter. There is likely no
+ // reason to change this unless you have a large number of existing tags
+ // files you'd like to use that already have a different name. In this
+ // case perhaps consider using the 'extra_tag_files' setting instead.
"tag_file" : ".tags",
- // Additional options to pass to ctags, i.e.
- // ["--exclude=some/path", "--exclude=some/other/path", ...]
+ // Additional tag files names to search.
+ //
+ // These are searched in addition to the file name given in 'tag_file'
+ "extra_tag_files": [".gemtags", "tags"],
+
+ // Additional options to pass to ctags.
+ //
+ // Any addition options you may wish to pass to the ctags executable. For
+ // example:
+ //
+ // ["--exclude=some/path", "--exclude=some/other/path", ...]
"opts" : [],
+ // Tag "kind"s to ignore.
+ //
+ // A ctags tagfile describes a number of different "kind"s, described in
+ // tag FORMAT file found here:
+ //
+ // http://ctags.sourceforge.net/FORMAT
+ //
+ // These can be filtered (i.e. ignored). For example - 'import' statements
+ // should be ignored in Python. These are of kind "i", e.g.
+ //
+ // "type":"^i$"
//
"filters": {
"source.python": {"type":"^i$"}
},
+ // Definition "kind"s to ignore.
//
+ // This is very similar to the 'filters' option. However, this only
+ // applies to the process that is used to find a definition. All filters
+ // placed here will be used when the plugin is searching for a definition
+ // in the file.
"definition_filters": {
"source.php": {"type":"^v$"}
},
+ // Enable the ctags menu in the context menus.
+ "show_context_menus": true,
+
+ // Paths to additional tag files to include in tag search.
//
- "definition_current_first": true,
+ // This is a list of items in the following format:
+ //
+ // [["language", "platform"], "path"]
+ //
+ "extra_tag_paths": [
+ [["source.python", "windows"], "C:\\Python27\\Lib\\tags"]
+ ],
- // Show the ctags menu in the context menus
- "show_context_menus": true,
+ // Enable highlighting of selected symbol.
+ //
+ // When enabled, searched symbols will be highlighted when found. This
+ // can be irritating in some instances, e.g. when in Vintage mode. In
+ // these cases, setting this to false will disable this highlighting.
+ "select_searched_symbol": true,
- // Paths to additional tag files to include in tag search. This is a list
- // of items in the form [["language", "platform"], "path"]
- "extra_tag_paths": [[["source.python", "windows"], "C:\\Python27\\Lib\\tags"]],
+ // Set to false to not open an error dialog while tags are building
+ "display_rebuilding_message": true,
- // Additional tag files to search
- "extra_tag_files": [".gemtags", "tags"],
+ // Rank Manager language syntax regex and character sets
+ //
+ // Ex: Python 'and' ignore exp --> '\sand\s' - it must have whitespace
+ // around it so it is not part of real name: gates.Nand.evaluate()
+ "language_syntax": {
+ "splitters" : [".", "::", "->"],
+ "source.js": {
+ "member_exp": {
+ "chars": "[A-Za-z0-9_$]",
+ "splitters": ["\\."],
+ "open": ["\\{", "\\[", "\\("],
+ "close": ["\\}", "\\]" , "\\)"], //close[i] must match open[i]
+ "ignore": ["&", "\\|", "\\?", ":", "\\!", "'", "=", "\""],
+ "stop": ["\\s", ","],
+ "this": ["this", "me", "self", "that"]
+ },
+ "reference_types": {
+ "__symbol__(\\.call|\\.apply){0,1}\\s*?\\(": ["f", "fa"],
+ "\\.fire\\s*?\\(\\s*?\\[\\'\"]__symbol__\\[\\'\"\\]": [
+ "eventHandler"
+ ]
+ }
+ },
+ "source.python": {
+ //python settings inherit JavaScript, with some overrides
+ "inherit": "source.js",
+ "member_exp": {
+ "ignore": ["\\sand\\s", "\\sor\\s", "\\snot\\s", ":", "\\!",
+ "'", "=", "\""],
+ "this" : ["self"]
+ }
+ },
+ "source.java": {
+ "inherit": "source.js",
+ "member_exp": {
+ "this" : ["this"]
+ }
+ },
+ "source.cs": {
+ "inherit": "source.js",
+ "member_exp": {
+ "this" : ["this"]
+ }
+ }
+ },
- // Set to false so as not to select searched symbol (in Vintage mode)
- "select_searched_symbol": true
+ // Scope Filters
+ //
+ // Tags file may optionally contain tagfield for the scope of the tag. For
+ // example:
+ //
+ // item .\fileHelper.js 420;" vp lineno:420 scope:420:19-422:9
+ //
+ // The re is used to extract 'scope:/beginLine:beginCol-endLine:endCol/'
+ //
+ // Different tags generators may generate this non-standard field in
+ // different formats
+ "scope_re": "(\\d.*?):(\\d.*?)-(\\d.*?):(\\d.*?)"
}
View
26 Context.sublime-menu
@@ -1,15 +1,15 @@
[
- {
- "caption": "-"
- },
- {
- "command": "navigate_to_definition",
- "args": {},
- "caption": "Navigate to Definition"
- },
- {
- "command": "jump_prev",
- "args": {},
- "caption": "Jump Back"
- }
+ {
+ "caption": "-"
+ },
+ {
+ "command": "navigate_to_definition",
+ "args": {},
+ "caption": "Navigate to Definition"
+ },
+ {
+ "command": "jump_prev",
+ "args": {},
+ "caption": "Jump Back"
+ }
]
View
60 Default.sublime-commands
@@ -1,33 +1,33 @@
[
- {
- "caption": "CTags: Rebuild Tags",
- "command": "rebuild_tags"
- },
- {
- "caption": "CTags: Show Symbols (file)",
- "command": "show_symbols",
- "context": [
- {
- "key": "selector",
- "match_all": true,
- "operand": "source -source.css",
- "operator": "equal"
- }
- ]
- },
- {
- "caption": "CTags: Show Symbols (all)",
- "command": "show_symbols",
- "args": {
- "type": "multi"
+ {
+ "caption": "CTags: Rebuild Tags",
+ "command": "rebuild_tags"
},
- "context": [
- {
- "key": "selector",
- "match_all": true,
- "operand": "source -source.css",
- "operator": "equal"
- }
- ]
- }
+ {
+ "caption": "CTags: Show Symbols (file)",
+ "command": "show_symbols",
+ "context": [
+ {
+ "key": "selector",
+ "match_all": true,
+ "operand": "source -source.css",
+ "operator": "equal"
+ }
+ ]
+ },
+ {
+ "caption": "CTags: Show Symbols (all)",
+ "command": "show_symbols",
+ "args": {
+ "type": "multi"
+ },
+ "context": [
+ {
+ "key": "selector",
+ "match_all": true,
+ "operand": "source -source.css",
+ "operator": "equal"
+ }
+ ]
+ }
]
View
138 Default.sublime-keymap
@@ -1,71 +1,71 @@
[
- {
- "command": "navigate_to_definition",
- "keys": ["ctrl+t", "ctrl+t"]
- },
- {
- "command": "navigate_to_definition",
- "keys": ["ctrl+shift+period"]
- },
- {
- "command": "search_for_definition",
- "keys": ["ctrl+t", "ctrl+y"]
- },
- {
- "command": "jump_prev",
- "keys": ["ctrl+t", "ctrl+b"]
- },
- {
- "command": "jump_prev",
- "keys": ["ctrl+shift+comma"]
- },
- {
- "command": "rebuild_tags",
- "keys": ["ctrl+t", "ctrl+r"]
- },
- {
- "command": "show_symbols",
- "context": [
- {
- "key": "selector",
- "match_all": true,
- "operand": "source -source.css",
- "operator": "equal"
- }
- ],
- "keys": ["alt+s"]
- },
- {
- "command": "show_symbols",
- "args": {"type": "multi"},
- "context": [
- {
- "key": "selector",
- "match_all": true,
- "operand": "source -source.css",
- "operator": "equal"
- }
- ],
- "keys": ["alt+shift+s"]
- },
- {
- "command": "show_symbols",
- "args": {"type": "lang"},
- "context": [
- {
- "key": "selector",
- "match_all": true,
- "operand": "source -source.css",
- "operator": "equal"
- }
- ],
- "keys": ["ctrl+alt+shift+s"]
- },
- { // override current default
- "command": "transpose",
- "context": [
- { "key": "num_selections", "operator": "not_equal", "operand": 1 }
- ],
- "keys": ["ctrl+t"]
- }
+ {
+ "command": "navigate_to_definition",
+ "keys": ["ctrl+t", "ctrl+t"]
+ },
+ {
+ "command": "navigate_to_definition",
+ "keys": ["ctrl+shift+period"]
+ },
+ {
+ "command": "search_for_definition",
+ "keys": ["ctrl+t", "ctrl+y"]
+ },
+ {
+ "command": "jump_prev",
+ "keys": ["ctrl+t", "ctrl+b"]
+ },
+ {
+ "command": "jump_prev",
+ "keys": ["ctrl+shift+comma"]
+ },
+ {
+ "command": "rebuild_tags",
+ "keys": ["ctrl+t", "ctrl+r"]
+ },
+ {
+ "command": "show_symbols",
+ "context": [
+ {
+ "key": "selector",
+ "match_all": true,
+ "operand": "source -source.css",
+ "operator": "equal"
+ }
+ ],
+ "keys": ["alt+s"]
+ },
+ {
+ "command": "show_symbols",
+ "args": {"type": "multi"},
+ "context": [
+ {
+ "key": "selector",
+ "match_all": true,
+ "operand": "source -source.css",
+ "operator": "equal"
+ }
+ ],
+ "keys": ["alt+shift+s"]
+ },
+ {
+ "command": "show_symbols",
+ "args": {"type": "lang"},
+ "context": [
+ {
+ "key": "selector",
+ "match_all": true,
+ "operand": "source -source.css",
+ "operator": "equal"
+ }
+ ],
+ "keys": ["ctrl+alt+shift+s"]
+ },
+ { // override current default
+ "command": "transpose",
+ "context": [
+ { "key": "num_selections", "operator": "not_equal", "operand": 1 }
+ ],
+ "keys": ["ctrl+t"]
+ }
]
View
26 Default.sublime-mousemap
@@ -1,15 +1,15 @@
[
- {
- "button": "button1",
- "count": 1,
- "press_command": "drag_select",
- "modifiers": ["ctrl","shift"],
- "command": "navigate_to_definition"
- },
- {
- "button": "button2",
- "count": 1,
- "modifiers": ["ctrl","shift"],
- "command": "jump_prev"
- }
+ {
+ "button": "button1",
+ "count": 1,
+ "press_command": "drag_select",
+ "modifiers": ["ctrl","shift"],
+ "command": "navigate_to_definition"
+ },
+ {
+ "button": "button2",
+ "count": 1,
+ "modifiers": ["ctrl","shift"],
+ "command": "jump_prev"
+ }
]
View
4 Side Bar.sublime-menu
@@ -7,5 +7,7 @@
"files": []
}
},
- { "caption": "-", "id": "ctags_commands" }
+ {
+ "caption": "-", "id": "ctags_commands"
+ }
]
View
182 ctags.py
@@ -1,6 +1,6 @@
-#!/usr/bin/env python
-
-"""A ctags wrapper, parser and sorter"""
+"""
+A ctags wrapper, parser and sorter.
+"""
import codecs
import re
@@ -10,14 +10,14 @@
import bisect
import mmap
-if sys.version_info<(2,7,0):
+if sys.version_info < (2, 7):
from helpers.check_output import check_output
else:
from subprocess import check_output
-"""
-Contants
-"""
+#
+# Contants
+#
TAGS_RE = re.compile(
r'(?P<symbol>[^\t]+)\t'
@@ -37,42 +37,39 @@
'function', 'class', 'struct',
]
-PATH_IGNORE_FIELDS = ('file', 'access', 'signature',
- 'language', 'line', 'inherits')
+PATH_IGNORE_FIELDS = (
+ 'file', 'access', 'signature', 'language', 'line', 'inherits')
TAG_PATH_SPLITTERS = ('/', '.', '::', ':')
+#
+# Functions
+#
-"""
-Functions
-"""
-
-
-"""Helper functions"""
-
+# Helper functions
def splits(string, *splitters):
- """Split a string on a number of splitters.
+ """
+ Split a string on a number of splitters.
:param string: string to split
:param splitters: characters to split string on
:returns: ``string`` split on characters in ``string``"""
if splitters:
split = string.split(splitters[0])
- for s in split:
- for c in splits(s, *splitters[1:]):
- yield c
+ for val in split:
+ for char in splits(val, *splitters[1:]):
+ yield char
else:
if string:
yield string
+# Tag processing functions
-"""Tag processing functions"""
-
-
-def parse_tag_lines(lines, order_by='symbol', tag_class=None, filters=[]):
- """Parse and sort a list of tags.
+def parse_tag_lines(lines, order_by='symbol', tag_class=None, filters=None):
+ """
+ Parse and sort a list of tags.
Parse and sort a list of tags one by using a combination of regexen and
Python functions. The end result is a dictionary containing all 'tags' or
@@ -109,11 +106,12 @@ def parse_tag_lines(lines, order_by='symbol', tag_class=None, filters=[]):
if tag_class is not None: # if 'casting' to a class
tag = tag_class(tag)
- # apply filters, filtering out any matching entries
- for f in filters:
- for k, v in list(f.items()):
- if re.match(v, tag[k]):
- skip = True
+ if filters:
+ # apply filters, filtering out any matching entries
+ for filt in filters:
+ for key, val in list(filt.items()):
+ if re.match(val, tag[key]):
+ skip = True
if skip: # if a filter was matched, ignore line (filter out)
continue
@@ -122,9 +120,9 @@ def parse_tag_lines(lines, order_by='symbol', tag_class=None, filters=[]):
return tags_lookup
-
def post_process_tag(tag):
- """Process 'EX Command'-related elements of a tag.
+ """
+ Process 'EX Command'-related elements of a tag.
Process all 'EX Command'-related elements. The 'Ex Command' element has
previously been split into the 'fields', 'type' and 'ex_command' elements.
@@ -172,9 +170,9 @@ def post_process_tag(tag):
return tag
-
def process_ex_cmd(tag):
- """Process the 'ex_command' element of a tag dictionary.
+ """
+ Process the 'ex_command' element of a tag dictionary.
Process the ex_command string - a line number or regex used to find symbol
declaration - by unescaping the regex where used.
@@ -190,9 +188,9 @@ def process_ex_cmd(tag):
else: # else a regex, so unescape
return re.sub(r"\\(\$|/|\^|\\)", r'\1', ex_cmd[2:-2]) # unescape regex
-
def process_fields(tag):
- """Process the 'field' element of a tag dictionary.
+ """
+ Process the 'field' element of a tag dictionary.
Process the fields string - a comma-separated string of "key-value" pairs
- by generating key-value pairs and appending them to the tag dictionary.
@@ -216,9 +214,9 @@ def process_fields(tag):
return result
-
def create_tag_path(tag):
- """Create a tag path entry for a tag dictionary.
+ """
+ Create a tag path entry for a tag dictionary.
Creates a tag path entry for a tag dictionary from the field key-value
pairs. Uses format::
@@ -262,21 +260,18 @@ def create_tag_path(tag):
return result
+# Tag building/sorting functions
-"""Tag building/sorting functions"""
-
-
-def build_ctags(path, tag_file=None, recursive=False, opts=None, cmd=None,
- env=None):
- """Execute the ``ctags`` command using ``Popen``
+def build_ctags(path, cmd=None, tag_file=None, recursive=False, opts=None):
+ """
+ Execute the ``ctags`` command using ``Popen``.
:param path: path to file or directory (with all files) to generate
ctags for.
- :param tag_file: filename to use for the tag file. Defaults to ``tags``
:param recursive: specify if search should be recursive in directory
given by path. This overrides filename specified by ``path``
+ :param tag_file: filename to use for the tag file. Defaults to ``tags``
:param opts: list of additional options to pass to the ctags executable
- :param env: environment variables to be used when executing ``ctags``
:returns: original ``tag_file`` filename
"""
@@ -318,7 +313,7 @@ def build_ctags(path, tag_file=None, recursive=False, opts=None, cmd=None,
cmd = ' '.join(cmd)
# execute the command
- check_output(cmd, cwd=cwd, shell=True, env=env, stdin=subprocess.PIPE,
+ check_output(cmd, cwd=cwd, shell=True, stdin=subprocess.PIPE,
stderr=subprocess.STDOUT)
if not tag_file: # Exuberant ctags defaults to ``tags`` filename.
@@ -332,9 +327,9 @@ def build_ctags(path, tag_file=None, recursive=False, opts=None, cmd=None,
return tag_file
-
def resort_ctags(tag_file):
- """Rearrange ctags file for speed.
+ """
+ Rearrange ctags file for speed.
Resorts (re-sort) a CTag file in order of file. This improves searching
performance when searching tags by file as a binary search can be used.
@@ -360,33 +355,34 @@ def resort_ctags(tag_file):
"""
keys = {}
- with codecs.open(tag_file, encoding='utf-8', errors='replace') as fh:
- for line in fh:
+ with codecs.open(tag_file, encoding='utf-8', errors='replace') as file_:
+ for line in file_:
keys.setdefault(line.split('\t')[FILENAME], []).append(line)
- with codecs.open(tag_file+'_sorted_by_file', 'w', encoding='utf-8', errors='replace') as fw:
+ with codecs.open(tag_file+'_sorted_by_file', 'w', encoding='utf-8',
+ errors='replace') as file_:
for k in sorted(keys):
for line in keys[k]:
split = line.split('\t')
split[FILENAME] = split[FILENAME].lstrip('.\\')
- fw.write('\t'.join(split))
-
-
-"""
-Models
-"""
+ file_.write('\t'.join(split))
+#
+# Models
+#
class TagElements(dict):
- """Model the entries of a tag file"""
+ """
+ Model the entries of a tag file.
+ """
def __init__(self, *args, **kw):
"""Initialise Tag object"""
dict.__init__(self, *args, **kw)
self.__dict__ = self
-
class Tag(object):
- """Model a tag.
+ """
+ Model a tag.
This exists mainly to enable different types of sorting.
"""
@@ -405,18 +401,25 @@ def __gt__(self, other):
def __getitem__(self, index):
return self.line.split('\t')[index]
+ def __len__(self):
+ return len(self.line.split('\t'))
class TagFile(object):
- """Model a tag file.
+ """
+ Model a tag file.
This doesn't actually hold a entire tag file, due in part to the sheer
size of some tag files (> 100 MB files are possible). Instead, it acts
as a 'wrapper' of sorts around a file, providing functionality like
searching for a retrieving tags, finding tags based on given criteria
(prefix, suffix, exact), getting the directory of a tag and so forth.
"""
+ file_o = None
+ mapped = None
+
def __init__(self, path, column):
- """Initialise object.
+ """
+ Initialise object.
The file indicated by ``path`` must be sorted by values in the column
indicated by ``column``.
@@ -430,7 +433,9 @@ def __init__(self, path, column):
self.column = column
def __getitem__(self, index):
- """Provide sequence-type interface to tag file."""
+ """
+ Provide sequence-type interface to tag file.
+ """
self.mapped.seek(index)
result = self.mapped.readline()
@@ -442,36 +447,49 @@ def __getitem__(self, index):
return Tag(result, self.column)
def __len__(self):
- """Get size of tag file in bytes"""
+ """
+ Get size of tag file in bytes.
+ """
return len(self.mapped)
def __enter__(self):
- """Open file on enter when using ``with`` keyword"""
+ """
+ Open file on enter when using ``with`` keyword.
+ """
self.open()
return self
- def __exit__(self, type, value, traceback):
- """Close file on exit when using ``with`` keyword"""
+ def __exit__(self, type_, value, traceback):
+ """
+ Close file on exit when using ``with`` keyword.
+ """
self.close()
@property
def dir(self):
- """Get directory of tag file"""
+ """
+ Get directory of tag file.
+ """
return os.path.dirname(self.path)
def open(self):
- """Open file"""
+ """
+ Open file.
+ """
self.file_o = codecs.open(self.path, 'r+b', encoding='ascii')
self.mapped = mmap.mmap(self.file_o.fileno(), 0,
access=mmap.ACCESS_READ)
def close(self):
- """Close file"""
+ """
+ Close file.
+ """
self.mapped.close()
self.file_o.close()
def search(self, exact_match=True, *tags):
- """Search for one or more tags in the tag file.
+ """
+ Search for one or more tags in the tag file.
Search a tag file for given tags using a binary search.
@@ -486,20 +504,21 @@ def search(self, exact_match=True, *tags):
return
for key in tags:
- leftIndex = bisect.bisect_left(self, key)
+ left_index = bisect.bisect_left(self, key)
if exact_match:
- result = self[leftIndex]
+ result = self[left_index]
while result.line and result[result.column] == key:
yield(result)
result = Tag(self.mapped.readline().strip(), self.column)
else:
- result = self[leftIndex]
+ result = self[left_index]
while result.line and result[result.column].startswith(key):
yield(result)
result = Tag(self.mapped.readline().strip(), self.column)
def search_by_suffix(self, suffix):
- """Search for one or more tags with the given suffix in the tag file.
+ """
+ Search for one or more tags with the given suffix in the tag file.
Search a tag file for given tags with the given suffix, using a linear
search. Note that this linear search requires the entire file be
@@ -516,7 +535,8 @@ def search_by_suffix(self, suffix):
continue
def tag_class(self):
- """Default class to wrap tag in.
+ """
+ Default class to wrap tag in.
Allows wrapping of a parsed tag dict in a class, so elements can be
accessed as class variables (i.e. ``class.variable``, rather than
@@ -525,13 +545,17 @@ def tag_class(self):
return type('TagElements', (TagElements,), dict(root_dir=self.dir))
def get_tags_dict(self, *tags, **kw):
- """Return the tags from a tag file as a dict"""
+ """
+ Return the tags from a tag file as a dict.
+ """
filters = kw.get('filters', [])
return parse_tag_lines(self.search(True, *tags),
tag_class=self.tag_class(), filters=filters)
def get_tags_dict_by_suffix(self, suffix, **kw):
- """Return the tags with the given suffix of a tag file as a dict"""
+ """
+ Return the tags with the given suffix of a tag file as a dict.
+ """
filters = kw.get('filters', [])
return parse_tag_lines(self.search_by_suffix(suffix),
tag_class=self.tag_class(), filters=filters)
View
305 ctagsplugin.py
@@ -1,10 +1,12 @@
-#!/usr/bin/env python
-
-"""A ctags plugin for Sublime Text 2/3"""
+"""
+A ctags plugin for Sublime Text 2/3.
+"""
import functools
+from functools import reduce
import codecs
import locale
+import sys
import os
import pprint
import re
@@ -20,29 +22,29 @@
import sublime
import sublime_plugin
from sublime import status_message, error_message
-except ImportError: # running tests
- import sys
+ # hack the system path to prevent the following issue in ST3
+ # ImportError: No module named 'ctags'
+ sys.path.append(os.path.dirname(os.path.realpath(__file__)))
+except ImportError: # running tests
from tests.sublime_fake import sublime
from tests.sublime_fake import sublime_plugin
sys.modules['sublime'] = sublime
sys.modules['sublime_plugin'] = sublime_plugin
-if sublime.version().startswith('2'):
- import ctags
- from ctags import (FILENAME, parse_tag_lines, PATH_ORDER, SYMBOL,
- TagElements, TagFile)
- from helpers.edit import Edit
-else: # safe to assume if not ST2 then ST3
- from CTags import ctags
- from CTags.ctags import (FILENAME, parse_tag_lines, PATH_ORDER, SYMBOL,
- TagElements, TagFile)
- from CTags.helpers.edit import Edit
+import ctags
+from ctags import (FILENAME, parse_tag_lines, PATH_ORDER, SYMBOL,
+ TagElements, TagFile)
+from helpers.edit import Edit
-"""
-Contants
-"""
+from helpers.common import *
+from ranking.rank import RankMgr
+from ranking.parse import Parser
+
+#
+# Contants
+#
OBJECT_PUNCTUATORS = {
'class': '.',
@@ -52,44 +54,14 @@
ENTITY_SCOPE = 'entity.name.function, entity.name.type, meta.toc-list'
-RUBY_SPECIAL_ENDINGS = '\?|!'
+RUBY_SPECIAL_ENDINGS = r'\?|!'
ON_LOAD = sublime_plugin.all_callbacks['on_load']
-RE_SPECIAL_CHARS = re.compile(
- '(\\\\|\\*|\\+|\\?|\\||\\{|\\}|\\[|\\]|\\(|\\)|\\^|\\$|\\.|\\#|\\ )')
-
-"""
-Functions
-"""
-
-"""Helper functions"""
-
-
-def get_settings():
- """Load settings.
-
- :returns: dictionary containing settings
- """
- return sublime.load_settings("CTags.sublime-settings")
-
-
-def get_setting(key, default=None):
- """Load individual setting.
-
- :param key: setting key to get value for
- :param default: default value to return if no value found
-
- :returns: value for ``key`` if ``key`` exists, else ``default``
- """
- return get_settings().get(key, default)
-
-setting = get_setting
-
-
-def escape_regex(s):
- return RE_SPECIAL_CHARS.sub(lambda m: '\\%s' % m.group(1), s)
+#
+# Functions
+#
def select(view, region):
@@ -106,8 +78,9 @@ def done_in_main(*args, **kw):
return done_in_main
-
# TODO: allow thread per tag file. That makes more sense.
+
+
def threaded(finish=None, msg='Thread already running'):
def decorator(func):
func.running = 0
@@ -143,7 +116,8 @@ def run():
def on_load(path=None, window=None, encoded_row_col=True, begin_edit=False):
- """Decorator to open or switch to a file.
+ """
+ Decorator to open or switch to a file.
Opens and calls the "decorated function" for the file specified by path,
or the current file if no path is specified. In the case of the former, if
@@ -179,7 +153,6 @@ def wrapped():
# if buffer is still loading, wait for it to complete then proceed
if view.is_loading():
-
class set_on_load():
callbacks = ON_LOAD
@@ -208,7 +181,8 @@ def on_load(self, view):
def find_tags_relative_to(path, tag_file):
- """Find the tagfile relative to a file path.
+ """
+ Find the tagfile relative to a file path.
:param path: path to a file
:param tag_file: name of tag file
@@ -232,7 +206,8 @@ def find_tags_relative_to(path, tag_file):
def get_alternate_tags_paths(view, tags_file):
- """Search for additional tag files.
+ """
+ Search for additional tag files.
Search for additional tag files to use, including those define by a
``search_paths`` file, the ``extra_tag_path`` setting and the
@@ -257,7 +232,9 @@ def get_alternate_tags_paths(view, tags_file):
for (selector, platform), path in setting('extra_tag_paths'):
if view.match_selector(view.sel()[0].begin(), selector):
if sublime.platform() == platform:
- search_paths.append(os.path.join(path, setting('tag_file')))
+ search_paths.append(
+ os.path.join(
+ path, setting('tag_file')))
except Exception as e:
print(e)
@@ -280,14 +257,15 @@ def get_alternate_tags_paths(view, tags_file):
# use list instead of set for keep order
ret = []
- for p in search_paths:
- if p and (p not in ret) and os.path.exists(p):
- ret.append(p)
+ for path in search_paths:
+ if path and (path not in ret) and os.path.exists(path):
+ ret.append(path)
return ret
def get_common_ancestor_folder(path, folders):
- """Get common ancestor for a file and a list of folders.
+ """
+ Get common ancestor for a file and a list of folders.
:param path: path to file
:param folders: list of folder paths
@@ -308,16 +286,15 @@ def get_common_ancestor_folder(path, folders):
return path # return the root directory
-
-"""Scrolling functions"""
+# Scrolling functions
def find_with_scope(view, pattern, scope, start_pos=0, cond=True, flags=0):
max_pos = view.size()
-
while start_pos < max_pos:
- estrs = pattern.split('\ufffd')
- if(len(estrs)>1):pattern = estrs[0]
+ estrs = pattern.split(r'\ufffd')
+ if(len(estrs) > 1):
+ pattern = estrs[0]
f = view.find(pattern, start_pos, flags)
if not f or view.match_selector(f.begin(), scope) is cond:
@@ -365,15 +342,16 @@ def and_then(view):
do_find = True
if tag.ex_command.isdigit():
- look_from = view.text_point(int(tag.ex_command)-1, 0)
+ look_from = view.text_point(int(tag.ex_command) - 1, 0)
else:
look_from = follow_tag_path(view, tag.tag_path, tag.ex_command)
if not look_from:
do_find = False
if do_find:
+ search_symbol = tag.get('def_symbol', tag.symbol)
symbol_region = view.find(
- escape_regex(tag.symbol) + r"(?:[^_]|$)", look_from, 0)
+ escape_regex(search_symbol) + r"(?:[^_]|$)", look_from, 0)
if do_find and symbol_region:
# Using reversed symbol_region so cursor stays in front of the
@@ -389,19 +367,19 @@ def and_then(view):
if hook:
hook(view)
-
-"""Formatting helper functions"""
+# Formatting helper functions
def format_tag_for_quickopen(tag, show_path=True):
- """Format a tag for use in quickopen panel.
+ """
+ Format a tag for use in quickopen panel.
:param tag: tag to display in quickopen
:param show_path: show path to file containing tag in quickopen
:returns: formatted tag
"""
- format = []
+ format_ = []
tag = ctags.TagElements(tag)
f = ''
@@ -411,17 +389,18 @@ def format_tag_for_quickopen(tag, show_path=True):
f += string.Template(
' %($field)s$punct%(symbol)s').substitute(locals())
- format = [f % tag if f else tag.symbol, tag.ex_command]
- format[1] = format[1].strip()
+ format_ = [f % tag if f else tag.symbol, tag.ex_command]
+ format_[1] = format_[1].strip()
if show_path:
- format.insert(1, tag.filename)
+ format_.insert(1, tag.filename)
- return format
+ return format_
def prepare_for_quickpanel(formatter=format_tag_for_quickopen):
- """Prepare list of matching ctags for the quickpanel.
+ """
+ Prepare list of matching ctags for the quickpanel.
:param formatter: formatter function to apply to tag
@@ -438,12 +417,12 @@ def compile_lists(sorter):
return compile_lists
-
-"""File collection helper functions"""
+# File collection helper functions
def get_rel_path_to_source(path, tag_file, multiple=True):
- """Get relative path from tag_file to source file.
+ """
+ Get relative path from tag_file to source file.
:param path: path to a source file
:param tag_file: path to a tag file
@@ -462,26 +441,27 @@ def get_rel_path_to_source(path, tag_file, multiple=True):
def get_current_file_suffix(path):
- """Get file extension
+ """
+ Get file extension
:param path: path to a source file
:returns: file extension for file
"""
- file_prefix, file_suffix = os.path.splitext(path)
+ _, file_suffix = os.path.splitext(path)
return file_suffix
+#
+# Sublime Commands
+#
-"""
-Sublime Commands
-"""
-
-"""JumpPrev Commands"""
+# JumpPrev Commands
class JumpPrev(sublime_plugin.WindowCommand):
- """Provide ``jump_back`` command.
+ """
+ Provide ``jump_back`` command.
Command "jumps back" to the previous code point before a tag was navigated
or "jumped" to.
@@ -505,25 +485,25 @@ def run(self):
file_name, sel = self.buf.pop()
self.jump(file_name, sel)
- def jump(self, fn, sel):
- @on_load(fn, begin_edit=True)
+ 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"""
- fn = view.file_name()
- if fn:
+ name = view.file_name()
+ if name:
sel = [s for s in view.sel()][0]
- cls.buf.append((fn, sel))
-
+ cls.buf.append((name, sel))
-"""CTags commands"""
+# CTags commands
def show_build_panel(view):
- """Handle build ctags command.
+ """
+ Handle build ctags command.
Allows user to select whether tags should be built for the current file,
a given directory or all open directories.
@@ -543,7 +523,7 @@ def show_build_panel(view):
['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
+ # Append options to build for each open folder
display.extend(
[[os.path.split(x)[1], x] for x in view.window().folders()])
@@ -566,7 +546,8 @@ def on_select(i):
def show_tag_panel(view, result, jump_directly):
- """Handle tag navigation command.
+ """
+ Handle tag navigation command.
Jump directly to a tag entry, or show a quick panel with a list of
matching tags
@@ -592,11 +573,12 @@ def on_select(i):
def ctags_goto_command(jump_directly=False):
- """Decorator to goto a ctag entry.
+ """
+ Decorator to goto a ctag entry.
Allow jump to a ctags entry, directly or otherwise
"""
- def wrapper(f):
+ def wrapper(func):
def command(self, edit, **args):
view = self.view
tags_file = find_tags_relative_to(
@@ -606,46 +588,35 @@ def command(self, edit, **args):
status_message('Can\'t find any relevant tags file')
return
- result = f(self, self.view, args, tags_file)
+ result = func(self, self.view, args, tags_file)
show_tag_panel(self.view, result, jump_directly)
return command
return wrapper
def check_if_building(self, **args):
- """Check if ctags are currently being built"""
+ """
+ Check if ctags are currently being built.
+ """
if RebuildTags.build_ctags.func.running:
- error_message('Please wait while tags are built')
+ status_message('Tags not available until built')
+ if setting('display_rebuilding_message'):
+ error_message('Please wait while tags are built')
return False
return True
-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
-
-
-def compile_definition_filters(view):
- filters = []
- for selector, regexes in list(setting('definition_filters', {}).items()):
- if view.match_selector(view.sel() and view.sel()[0].begin() or 0,
- selector):
- filters.append(regexes)
- return filters
-
-
-"""Goto definition under cursor commands"""
-
+# Goto definition under cursor commands
class JumpToDefinition:
- """Provider for NavigateToDefinition and SearchForDefinition commands"""
+ """
+ Provider for NavigateToDefinition and SearchForDefinition commands.
+ """
@staticmethod
- def run(symbol, view, tags_file):
+ def run(symbol, region, sym_line, mbrParts, view, tags_file):
+ # print('JumpToDefinition')
+
tags = {}
for tags_file in get_alternate_tags_paths(view, tags_file):
with TagFile(tags_file, SYMBOL) as tagfile:
@@ -657,29 +628,22 @@ def run(symbol, view, tags_file):
if not tags:
return status_message('Can\'t find "%s"' % symbol)
- def_filters = compile_definition_filters(view)
-
- def pass_def_filter(o):
- for f in def_filters:
- for k, v in list(f.items()):
- if k in o:
- if re.match(v, o[k]):
- return False
- return True
+ rankmgr = RankMgr(region, mbrParts, view, symbol, sym_line)
@prepare_for_quickpanel()
def sorted_tags():
- p_tags = list(filter(pass_def_filter, tags.get(symbol, [])))
+ taglist = tags.get(symbol, [])
+ p_tags = rankmgr.sort_tags(taglist)
if not p_tags:
status_message('Can\'t find "%s"' % symbol)
- p_tags = sorted(p_tags, key=iget('tag_path'))
return p_tags
return sorted_tags
class NavigateToDefinition(sublime_plugin.TextCommand):
- """Provider for the ``navigate_to_definition`` command.
+ """
+ Provider for the ``navigate_to_definition`` command.
Command navigates to the definition for a symbol in the open file(s) or
folder(s).
@@ -701,17 +665,33 @@ def run(self, view, args, tags_file):
# handle special line endings for Ruby
language = view.settings().get('syntax')
- endings = view.substr(sublime.Region(region.end(), region.end()+1))
+ endings = view.substr(
+ sublime.Region(
+ region.end(),
+ region.end() + 1))
if 'Ruby' in language and self.endings.match(endings):
- region = sublime.Region(region.begin(), region.end()+1)
+ region = sublime.Region(region.begin(), region.end() + 1)
symbol = view.substr(region)
- return JumpToDefinition.run(symbol, view, tags_file)
+ sym_line = view.substr(view.line(region))
+ (row, col) = view.rowcol(region.begin())
+ line_to_symbol = sym_line[:col]
+ #print ("line_to_symbol %s" % line_to_symbol)
+ source = get_source(view)
+ arrMbrParts = Parser.extract_member_exp(line_to_symbol, source)
+ return JumpToDefinition.run(
+ symbol,
+ region,
+ sym_line,
+ arrMbrParts,
+ view,
+ tags_file)
class SearchForDefinition(sublime_plugin.WindowCommand):
- """Provider for the ``search_for_definition`` command.
+ """
+ Provider for the ``search_for_definition`` command.
Command searches for definition for a symbol in the open file(s) or
folder(s).
@@ -734,7 +714,7 @@ def on_done(self, symbol):
status_message('Can\'t find any relevant tags file')
return
- result = JumpToDefinition.run(symbol, view, tags_file)
+ result = JumpToDefinition.run(symbol, None, "", [], view, tags_file)
show_tag_panel(view, result, True)
def on_change(self, text):
@@ -743,14 +723,14 @@ def on_change(self, text):
def on_cancel(self):
pass
-
-"""Show Symbol commands"""
+# Show Symbol commands
tags_cache = defaultdict(dict)
class ShowSymbols(sublime_plugin.TextCommand):
- """Provider for the ``show_symbols`` command.
+ """
+ Provider for the ``show_symbols`` command.
Command shows all symbols for the open file(s) or folder(s).
"""
@@ -822,16 +802,17 @@ def sorted_tags():
return sorted_tags
-
-"""Rebuild CTags commands"""
+# Rebuild CTags commands
class RebuildTags(sublime_plugin.TextCommand):
- """Provider for the ``rebuild_tags`` command.
+ """
+ Provider for the ``rebuild_tags`` command.
Command (re)builds tag files for the open file(s) or folder(s), reading
relevant settings from the settings file.
"""
+
def run(self, edit, **args):
"""Handler for ``rebuild_tags`` command"""
paths = []
@@ -858,7 +839,8 @@ def run(self, edit, **args):
@threaded(msg='Already running CTags!')
def build_ctags(self, paths, command, tag_file, recursive, opts):
- """Build tags for the open file or folder(s)
+ """
+ Build tags for the open file or folder(s).
:param paths: paths to build ctags for
:param command: ctags command
@@ -898,31 +880,35 @@ def tags_built(tag_file):
str_err = ' '.join(
e.output.decode('windows-1252').splitlines())
else:
- str_err = e.output.decode(locale.getpreferredencoding()).rstrip()
+ 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.")
+ error_message(
+ "An unknown error occured.\nCheck the console for info.")
raise e
tags_built(result)
GetAllCTagsList.ctags_list = [] # clear the cached ctags list
-
-"""Autocomplete commands"""
+# Autocomplete commands
class GetAllCTagsList():
+ """
+ Cache all the ctags list.
+ """
ctags_list = []
- """cache all the ctags list"""
def __init__(self, list):
self.ctags_list = list
class CTagsAutoComplete(sublime_plugin.EventListener):
+
def on_query_completions(self, view, prefix, locations):
if setting('autocomplete'):
prefix = prefix.strip().lower()
@@ -951,7 +937,8 @@ def on_query_completions(self, view, prefix, locations):
else:
prefix = "\\"
- f = os.popen("awk \"{ print "+prefix+"$1 }\" \"" + tags_path + "\"")
+ f = os.popen(
+ "awk \"{ print " + prefix + "$1 }\" \"" + tags_path + "\"")
for i in f.readlines():
tags.append([i.strip()])
@@ -962,13 +949,11 @@ def on_query_completions(self, view, prefix, locations):
GetAllCTagsList.ctags_list = tags
results = [sublist for sublist in GetAllCTagsList.ctags_list
if sublist[0].lower().startswith(prefix)]
- results = list(set(results).union(set(sub_results)))
- results.sort()
+ results = sorted(set(results).union(set(sub_results)))
return results
-
-"""Test CTags commands"""
+# Test CTags commands
class TestCtags(sublime_plugin.TextCommand):
View
41 helpers/check_output.py
@@ -1,27 +1,28 @@
-#!/usr/bin/env python
+"""
+Backport version of 'subprocess.check_output' for Python 2.6.x
-# Based on source from here: https://gist.github.com/edufelipe/1027906
-
-"""Backport version of 'subprocess.check_output' for Python 2.6.x"""
+Based on source from here: https://gist.github.com/edufelipe/1027906
+"""
import subprocess
def check_output(*popenargs, **kwargs):
- r"""Run command with arguments and return its output as a byte string.
+ r"""
+ Run command with arguments and return its output as a byte string.
- Backported from Python 2.7 as it's implemented as pure python on stdlib.
+ Backported from Python 2.7 as it's implemented as pure python on stdlib.
- >>> check_output(['/usr/bin/python', '--version'])
- Python 2.6.2
- """
- process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
- output, unused_err = process.communicate()
- retcode = process.poll()
- if retcode:
- cmd = kwargs.get("args")
- if cmd is None:
- cmd = popenargs[0]
- error = subprocess.CalledProcessError(retcode, cmd)
- error.output = output
- raise error
- return output
+ >>> check_output(['/usr/bin/python', '--version'])
+ Python 2.6.2
+ """
+ process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
+ output, _ = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
+ error = subprocess.CalledProcessError(retcode, cmd)
+ error.output = output
+ raise error
+ return output
View
135 helpers/common.py
@@ -0,0 +1,135 @@
+"""
+common utilities used by all ctags modules
+"""
+import re
+import sys
+import os
+
+# Helper functions
+
+try:
+ import sublime
+ import sublime_plugin
+ from sublime import status_message, error_message
+
+ # hack the system path to prevent the following issue in ST3
+ # ImportError: No module named 'ctags'
+ sys.path.append(os.path.dirname(os.path.realpath(__file__)))
+except ImportError: # running tests
+ from tests.sublime_fake import sublime
+ from tests.sublime_fake import sublime_plugin
+
+ sys.modules['sublime'] = sublime
+ sys.modules['sublime_plugin'] = sublime_plugin
+
+
+def get_settings():
+ """
+ Load settings.
+
+ :returns: dictionary containing settings
+ """
+ return sublime.load_settings("CTags.sublime-settings")
+
+
+def get_setting(key, default=None):
+ """
+ Load individual setting.
+
+ :param key: setting key to get value for
+ :param default: default value to return if no value found
+
+ :returns: value for ``key`` if ``key`` exists, else ``default``
+ """
+ return get_settings().get(key, default)
+
+setting = get_setting
+
+
+def concat_re(reList, escape=False, wrapCapture=False):
+ """
+ concat list of regex into a single regex, used by re.split
+ wrapCapture - if true --> adds () around the result regex --> split will keep the splitters in its output array.
+ """
+ ret = "|".join((re.escape(spl) if escape else spl) for spl in reList)
+ if (wrapCapture):
+ ret = "(" + ret + ")"
+ return ret
+
+
+def dict_extend(dct, base):
+ if not dct:
+ dct = {}
+ if base:
+ deriv = base
+ deriv = merge_two_dicts_deep(deriv, dct)
+ else:
+ deriv = dct
+ return deriv
+
+
+def merge_two_dicts_shallow(x, y):
+ """
+ Given two dicts, merge them into a new dict as a shallow copy.
+ y members overwrite x members with the same keys.
+ """
+ z = x.copy()
+ z.update(y)
+ return z
+
+
+def merge_two_dicts_deep(a, b, path=None):
+ "Merges b into a including sub-dictionaries - recursive"
+ if path is None:
+ path = []
+ for key in b:
+ if key in a:
+ if isinstance(a[key], dict) and isinstance(b[key], dict):
+ merge_two_dicts_deep(a[key], b[key], path + [str(key)])
+ elif a[key] == b[key]:
+ pass # same leaf value
+ else:
+ a[key] = b[key]
+ else:
+ a[key] = b[key]
+ return a
+
+RE_SPECIAL_CHARS = re.compile(
+ '(\\\\|\\*|\\+|\\?|\\||\\{|\\}|\\[|\\]|\\(|\\)|\\^|\\$|\\.|\\#|\\ )')
+
+
+def escape_regex(s):
+ return RE_SPECIAL_CHARS.sub(lambda m: '\\%s' % m.group(1), s)
+
+
+def get_source(view):
+ """
+ return the language used in current caret or selection location
+ """
+ scope_name = view.scope_name(
+ view.sel()[0].begin()) # ex: 'source.python meta.function-call.python '
+ source = re.split(' ', scope_name)[0] # ex: 'source.python'
+ return source
+
+
+def get_lang_setting(source):
+ """
+ given source (ex: 'source.python') --> return its language_syntax settings.
+ A language can inherit its settings from another language, overidding as needed.
+ """
+ lang = setting('language_syntax').get(source)
+ if lang is not None:
+ base = setting('language_syntax').get(lang.get('inherit'))
+ lang = dict_extend(lang, base)
+ else:
+ lang = {}
+ return lang
+
+
+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
View
17 helpers/edit.py
@@ -1,8 +1,8 @@
-#!/usr/bin/env python
+"""
+Buffer editing for both ST2 and ST3 that 'just works'.
-# Copyright, SublimeXiki project <https://github.com/lunixbochs/SublimeXiki>
-
-"""Buffer editing for both ST2 and ST3 that 'just works'"""
+Copyright, SublimeXiki project <https://github.com/lunixbochs/SublimeXiki>
+"""
import inspect
import sublime
@@ -13,23 +13,20 @@
except AttributeError:
sublime.edit_storage = {}
-
def run_callback(func, *args, **kwargs):
spec = inspect.getfullargspec(func)
if spec.args or spec.varargs:
func(*args, **kwargs)
else:
func()
-
class EditFuture:
def __init__(self, func):
self.func = func
def resolve(self, view, edit):
return self.func(view, edit)
-
class EditStep:
def __init__(self, cmd, *args):
self.cmd = cmd
@@ -57,7 +54,6 @@ def resolve_args(self, view, edit):
args.append(arg)
return args
-
class Edit:
def __init__(self, view):
self.view = view
@@ -67,7 +63,7 @@ def __nonzero__(self):
return bool(self.steps)
@classmethod
- def future(self, func):
+ def future(cls, func):
return EditFuture(func)
def step(self, cmd, *args):
@@ -98,7 +94,7 @@ def run(self, view, edit):
def __enter__(self):
return self
- def __exit__(self, type, value, traceback):
+ def __exit__(self, type_, value, traceback):
view = self.view
if sublime.version().startswith('2'):
edit = view.begin_edit()
@@ -109,7 +105,6 @@ def __exit__(self, type, value, traceback):
sublime.edit_storage[key] = self.run
view.run_command('apply_edit', {'key': key})
-
class apply_edit(sublime_plugin.TextCommand):
def run(self, edit, key):
sublime.edit_storage.pop(key)(self.view, edit)
View
1 messages.json
@@ -10,4 +10,5 @@
"0.3.7": "messages/0.3.7.md",
"0.3.8": "messages/0.3.8.md",
"0.3.9": "messages/0.3.9.md"
+ "0.4.0": "messages/0.4.0.md"
}
View
2 messages/0.3.9.md
@@ -1,4 +1,4 @@
-Changes in 0.3.8
+Changes in 0.3.9
================
- Add build ctags options to sidebar
View
21 messages/0.4.0.md
@@ -0,0 +1,21 @@
+Changes in 0.4.0
+================
+
+- Add ranking manager
+- Documentation improvements in settings
+- Bug Fixes
+
+Fixes
+=====
+
+N/A
+
+Resolves
+========
+
+N/A
+
+*******************************************************************************
+
+For more detailed information about these changes, run ``git v0.3.9..v0.4.0``
+on the Git repository found [here](https://github.com/SublimeText/CTags).
View
0 ranking/__init__.py
No changes.
View
94 ranking/parse.py
@@ -0,0 +1,94 @@
+import re
+from helpers.common import *
+#import spdb
+# spdb.start()
+
+
+class Parser:
+ """
+ Parses tag references and tag definitions. Used for ranking
+ """
+ @staticmethod
+ def extract_member_exp(line_to_symbol, source):
+ """
+ Extract receiver object e.g. receiver.mtd()
+ Strip away brackets and operators.
+ TODO:HIGH: Add base lang defs + Python/Ruby/C++/Java/C#/PHP overrides (should be very similar)
+ TODO: comment and string support (eat as may contain brackets. add them to context - js['prop1']['prop-of-prop1'])
+ """
+ lang = get_lang_setting(source)
+ if not lang:
+ return [line_to_symbol]
+
+ # Get per-language syntax regex of brackets, splitters etc.
+ mbr_exp = lang.get('member_exp')
+ if mbr_exp is None:
+ return [line_to_symbol]
+
+ lstStop = mbr_exp.get('stop', [])
+ if (not lstStop):
+ print('warning!: language has member_exp setting but it is ineffective: Must have "stop" key with array of regex to stop search backward from identifier')
+ return [line_to_symbol]
+
+ lstClose = mbr_exp.get('close', [])
+ reClose = concat_re(lstClose)
+ lstOpen = mbr_exp.get('open', [])
+ reOpen = concat_re(lstOpen)
+ lstIgnore = mbr_exp.get('ignore', [])
+ reIgnore = concat_re(lstIgnore)
+ if len(lstOpen) != len(lstClose):
+ print('warning!: extract_member_exp: settings lstOpen must match lstClose')
+ matchOpenClose = dict(zip(lstOpen, lstClose))
+ # Construct | regex from all open and close strings with capture (..)
+ splex = concat_re(lstOpen + lstClose + lstIgnore + lstStop)
+
+ reStop = concat_re(lstStop)
+ splex = "({0}|{1})".format(splex, reIgnore)
+ splat = re.split(splex, line_to_symbol)
+ #print('splat=%s' % splat)
+ # Stack iter reverse(splat) for detecting unbalanced e.g 'func(obj.yyy'
+ # while skipping balanced brackets in getSlow(a && b).mtd()
+ stack = []
+ lstMbr = []
+ insideExp = False
+ for cur in reversed(splat):
+ # Scan backwards from the symbol: If alpha-numeric - keep it. If
+ # Closing bracket e.g ] or ) or } --> push into stack
+ if re.match(reClose, cur):
+ stack.append(cur)
+ insideExp = True
+ # If opening bracket --> match it from top-of-stack: If stack empty
+ # - stop else If match pop-and-continue else stop scanning +
+ # warning
+ elif re.match(reOpen, cur):
+ # '(' with no matching ')' --> func(obj.yyy case --> return obj.yyy
+ if len(stack) == 0:
+ break
+ tokClose = stack.pop()
+ tokCloseCur = matchOpenClose.get(cur)
+ if tokClose != tokCloseCur:
+ print(
+ 'non-matching brackets at the same nesting level: %s %s' %
+ (tokCloseCur, tokClose))
+ break
+ insideExp = False
+ # If white space --> stop. Do not stop for whitespace inside
+ # open-close brackets nested expression
+ elif re.match(reStop, cur):
+ if not insideExp:
+ break
+ elif re.match(reIgnore, cur):
+ pass
+ else:
+ lstMbr[0:0] = cur
+
+ strMbrExp = "".join(lstMbr)
+
+ lstSplit = mbr_exp.get('splitters', [])
+ reSplit = concat_re(lstSplit)
+ # Split member deref per-lang (-> and :: in PHP and C++) - use base if
+ # not found
+ arrMbrParts = list(filter(None, re.split(reSplit, strMbrExp)))
+ # print('arrMbrParts=%s' % arrMbrParts)
+
+ return arrMbrParts
View
231 ranking/rank.py
@@ -0,0 +1,231 @@
+"""
+Rank and Filter support for ctags plugin for Sublime Text 2/3.
+"""
+
+from functools import reduce
+import sys
+import os
+import re
+import string
+
+
+from helpers.common import *
+
+
+def compile_definition_filters(view):
+ filters = []
+ for selector, regexes in list(
+ get_setting('definition_filters', {}).items()):
+ if view.match_selector(view.sel() and view.sel()[0].begin() or 0,
+ selector):
+ filters.append(regexes)
+ return filters
+
+
+def get_grams(str):
+ """
+ Return a set of tri-grams (each tri-gram is a tuple) given a string:
+ Ex: 'Dekel' --> {('d', 'e', 'k'), ('k', 'e', 'l'), ('e', 'k', 'e')}
+ """
+ lstr = str.lower()
+ return set(zip(lstr, lstr[1:], lstr[2:]))
+
+
+class RankMgr:
+ """
+ For each matched Tag, calculates the rank score or filter it out. The remaining matches are sorted by decending score.
+ """
+
+ def __init__(self, region, mbrParts, view, symbol, sym_line):
+ self.region = region
+ self.mbrParts = mbrParts
+ self.view = view
+ # Used by Rank by Definition Types
+ self.symbol = symbol
+ self.sym_line = sym_line
+
+ self.lang = get_lang_setting(get_source(view))
+ self.mbr_exp = self.lang.get('member_exp', {})
+
+ self.def_filters = compile_definition_filters(view)
+
+ self.fname_abs = view.file_name().lower() if not(
+ view.file_name() is None) else None
+
+ mbrGrams = [get_grams(part) for part in mbrParts]
+ self.setMbrGrams = (
+ reduce(
+ lambda s,
+ t: s.union(t),
+ mbrGrams) if mbrGrams else set())
+
+ def pass_def_filter(self, o):
+ for f in self.def_filters:
+ for k, v in list(f.items()):
+ if k in o: