diff --git a/README.md b/README.md index 4c973f0..9f8ed12 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ This is a [Sublime Text][st] (version 2 and 3) package that provides [st]: http://www.sublimetext.com/ [tern]: http://ternjs.net +**NOTE**: This project is not being actively maintained right now. If +you'd be interested in becoming a maintainer, write me or open an +issue. + In JavaScript files, the package will handle autocompletion. The following keys will be bound (in JavaScript files): @@ -32,7 +36,7 @@ Check out the code in this repository into a subdirectory of your Sublime Text's `Packages` directory. cd /path/to/sublime-text-N/Packages - git clone git://github.com/ternjs/tern_for_sublime.git + git clone https://github.com/ternjs/tern_for_sublime.git Next, make sure [node.js][node] and [npm][npm] are installed (Tern is a JavaScript program), and install the depedencies of the package. @@ -90,6 +94,9 @@ plugins for a project. See the [Tern docs][docs] for details. [docs]: http://ternjs.net/doc/manual.html#configuration +`tern_inhibit_word_completions` (boolean, default to false) +If true, Prevents Sublime Text from adding its word completions to the completion list after all plugins have been processed. This consists of any word in the current document that is longer than 3 characters. + ### Automatically Showing Completions Add `{"selector": "source.js", "characters": "."}` to your diff --git a/Tern.sublime-settings b/Tern.sublime-settings index 4dc2552..6d997e0 100644 --- a/Tern.sublime-settings +++ b/Tern.sublime-settings @@ -4,5 +4,6 @@ "tern_argument_completion": false, // Used to get auto completion for unsaved buffers // By default, this folder is inside Packages/tern_for_sublime/ - "tern_default_project_dir": "default_project_dir" -} \ No newline at end of file + "tern_default_project_dir": "default_project_dir", + "tern_inhibit_word_completions": false +} diff --git a/tern.py b/tern.py index 7c2de71..eaed298 100644 --- a/tern.py +++ b/tern.py @@ -54,8 +54,47 @@ def on_selection_modified_async(self, view): on_selection_modified(view) def on_query_completions(self, view, prefix, _locations): + QUOTES = ("\"", "'") + + loc = _locations[0] + prevCh = view.substr(loc - len(prefix) - 1) + nextCh = view.substr(loc) + prevQuote = prevCh in QUOTES and prevCh + nextQuote = nextCh in QUOTES and nextCh == prevQuote and nextCh + + lineBeforePos = view.substr(sublime.Region(view.line(loc).begin(), loc)) + pathPrefix = lineBeforePos.rsplit(' ', 1)[0] + + pathFirstCh = pathPrefix[0] + pathFirstQuote = pathFirstCh in QUOTES and pathFirstCh + pathLastQuote = nextCh in QUOTES and nextCh == pathFirstQuote and nextCh + + def postfixQuotes(c): + (display, word) = c + if prevQuote and c[1][0:1] in QUOTES: + word = word[1:] + display = display[1:] + + if nextQuote and c[1][-1] in QUOTES: + word = word[0:-1] + display = display.replace("%s%s" % (word, nextQuote), word, 1) + + return (display, word) + + def postfixPathes(c): + (display, word) = c + + if word.startswith(pathPrefix): + word = prefix + word[len(pathPrefix):] + display = prefix + display[len(pathPrefix):] + + if pathLastQuote and c[1][-1] in QUOTES: + word = word[0:-1] + display = display.replace("%s%s" % (word, pathLastQuote), word, 1) + + return (display, word) + sel = sel_start(view.sel()[0]) - if view.score_selector(sel, 'string.quoted') > 0: return None if view.score_selector(sel, 'comment') > 0: return None pfile = get_pfile(view) @@ -66,8 +105,15 @@ def on_query_completions(self, view, prefix, _locations): if not fresh: completions = [c for c in completions if c[1].startswith(prefix)] - return completions + completions = [postfixQuotes(c) for c in completions] + completions = [postfixPathes(c) for c in completions] + + flags = 0; + if get_setting("tern_inhibit_word_completions", False): + flags |= sublime.INHIBIT_WORD_COMPLETIONS + + return (completions, flags) class ProjectFile(object): def __init__(self, name, view, project): @@ -165,12 +211,17 @@ def server_port(project, ignored=None): return (started, False) def start_server(project): + global tern_command if not tern_command: return None if time.time() - project.last_failed < 30: return None env = None if platform.system() == "Darwin": env = os.environ.copy() env["PATH"] += ":/usr/local/bin" + + if not isinstance(tern_command, list): + tern_command = [tern_command] + proc = subprocess.Popen(tern_command + tern_arguments, cwd=project.dir, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=windows) @@ -264,7 +315,10 @@ def f(port, doc): req = opener.open("http://" + localhost + ":" + str(port) + "/", json.dumps(doc).encode("utf-8"), 1) return json.loads(req.read().decode("utf-8")) except urllib.error.URLError as error: - raise Req_Error((hasattr(error, "read") and error.read().decode("utf-8")) or error.reason) + if hasattr(error, "read"): + raise Req_Error(error.read().decode("utf-8")) + else: + raise error return f if python3: @@ -354,16 +408,21 @@ def report_error(message, project): project.disabled = True def completion_icon(type): - if type is None or type == "?": return " (?)" - if type.startswith("fn("): return " (fn)" - if type.startswith("["): return " ([])" - if type == "number": return " (num)" - if type == "string": return " (str)" - if type == "bool": return " (bool)" - return " (obj)" - -def fn_completion_icon(arguments): - return " (fn/"+str(len(arguments))+")" + if type is None or type == "?": return "\t? " + if type.startswith("fn("): return "\tfn " + if type.startswith("["): return "\t[] " + if type == "number": return "\tnum " + if type == "string": return "\tstr " + if type == "bool": return "\tbool " + return "\t{} " + +def fn_completion_icon(arguments, retval): + # return " (fn/"+str(len(arguments))+")" + ret = "" + if retval is not None: + ret = retval + + return "(" + ", ".join(arguments) + ")" + ret + ("\tfn ") # create auto complete string from list arguments def create_arg_str(arguments): @@ -421,14 +480,22 @@ def ensure_completions_cached(pfile, view): for rec in data["completions"]: rec_name = rec.get('name').replace('$', '\\$') rec_type = rec.get("type", None) - if arg_completion_enabled and completion_icon(rec_type) == " (fn)": + if arg_completion_enabled and rec_type is not None and rec_type.startswith("fn("): + retval = parse_function_type(rec).get('retval') + + if retval is None or retval == "()": + retval = "" + elif retval.startswith("{"): + retval = "{}" + elif retval.startswith("["): + retval = "[]" + + if retval != "": + retval = " -> " + retval + arguments = get_arguments(rec_type) fn_name = rec_name + "(" + create_arg_str(arguments) + ")" - completions.append((rec.get("name") + fn_completion_icon(arguments), fn_name)) - - for i in range(len(arguments) - 1, -1, -1): - fn_name = rec_name + "(" + create_arg_str(arguments[0:i]) + ")" - completions_arity.append((rec.get("name") + fn_completion_icon(arguments[0:i]), fn_name)) + completions.append((rec.get("name") + fn_completion_icon(arguments, retval), fn_name)) else: completions.append((rec.get("name") + completion_icon(rec_type), rec_name)) @@ -571,12 +638,12 @@ def run(self, edit, **args): class TernDisableProject(sublime_plugin.TextCommand): def run(self, edit, **args): - pfile = get_pfile(view) + pfile = get_pfile(self.view) pfile.project.disabled = False class TernEnableProject(sublime_plugin.TextCommand): def run(self, edit, **args): - pfile = get_pfile(view) + pfile = get_pfile(self.view) pfile.project.disabled = True # fetch a certain setting from the package settings file and if it doesn't exist check the