diff --git a/Default (Linux).sublime-keymap b/Default (Linux).sublime-keymap index 73eadec..49f571c 100644 --- a/Default (Linux).sublime-keymap +++ b/Default (Linux).sublime-keymap @@ -1,13 +1,15 @@ [ { "keys": ["ctrl+shift+g"], "command": "go_guru"}, - { "keys": ["ctrl+alt+shift+g"], "command": "go_guru_show_results"} + { "keys": ["ctrl+alt+shift+g"], "command": "go_guru_show_results"}, + { "keys": ["ctrl+.+ctrl+g"], "command": "go_guru_goto_definition", "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, ] /* - You can also set a key binding for a specific mode by adding a "mode" arg, e.g.: + You can also set a key binding for a specific mode by adding a "mode" arg, e.g.: ... { "keys": ["ctrl+super+c"], "command": "go_guru", "args": {"mode": "callers"} }, { "keys": ["ctrl+super+i"], "command": "go_guru", "args": {"mode": "implements"} }, { "keys": ["ctrl+super+r"], "command": "go_guru", "args": {"mode": "referrers"} }, + { "keys": ["ctrl+.+ctrl+g"], "command": "go_guru", "args": {"mode": "definition", output=false}}, ... please set this in your user keybinds, no here. -*/ \ No newline at end of file +*/ diff --git a/Default (Linux).sublime-mousemap b/Default (Linux).sublime-mousemap new file mode 100644 index 0000000..975628e --- /dev/null +++ b/Default (Linux).sublime-mousemap @@ -0,0 +1,12 @@ +[ + { + "button": "button2", + "modifiers": ["ctrl"], + "press_command": "drag_select", + "command": "go_guru", + "args": { + "mode": "definition", + "output": false + }, + }, +] diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap index 73eadec..2951332 100644 --- a/Default (OSX).sublime-keymap +++ b/Default (OSX).sublime-keymap @@ -1,6 +1,7 @@ [ { "keys": ["ctrl+shift+g"], "command": "go_guru"}, - { "keys": ["ctrl+alt+shift+g"], "command": "go_guru_show_results"} + { "keys": ["ctrl+alt+shift+g"], "command": "go_guru_show_results"}, + { "keys": ["ctrl+.+ctrl+g"], "command": "go_guru_goto_definition", "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, ] /* You can also set a key binding for a specific mode by adding a "mode" arg, e.g.: @@ -8,6 +9,8 @@ { "keys": ["ctrl+super+c"], "command": "go_guru", "args": {"mode": "callers"} }, { "keys": ["ctrl+super+i"], "command": "go_guru", "args": {"mode": "implements"} }, { "keys": ["ctrl+super+r"], "command": "go_guru", "args": {"mode": "referrers"} }, + { "keys": ["ctrl+.+ctrl+g"], "command": "go_guru", "args": {"mode": "definition", output=false}}, + ... please set this in your user keybinds, no here. -*/ \ No newline at end of file +*/ diff --git a/Default (OSX).sublime-mousemap b/Default (OSX).sublime-mousemap new file mode 100644 index 0000000..975628e --- /dev/null +++ b/Default (OSX).sublime-mousemap @@ -0,0 +1,12 @@ +[ + { + "button": "button2", + "modifiers": ["ctrl"], + "press_command": "drag_select", + "command": "go_guru", + "args": { + "mode": "definition", + "output": false + }, + }, +] diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap index 73eadec..be55bd3 100644 --- a/Default (Windows).sublime-keymap +++ b/Default (Windows).sublime-keymap @@ -1,6 +1,7 @@ [ { "keys": ["ctrl+shift+g"], "command": "go_guru"}, - { "keys": ["ctrl+alt+shift+g"], "command": "go_guru_show_results"} + { "keys": ["ctrl+alt+shift+g"], "command": "go_guru_show_results"}, + { "keys": ["ctrl+.+ctrl+g"], "command": "go_guru_goto_definition", "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, ] /* You can also set a key binding for a specific mode by adding a "mode" arg, e.g.: @@ -8,6 +9,7 @@ { "keys": ["ctrl+super+c"], "command": "go_guru", "args": {"mode": "callers"} }, { "keys": ["ctrl+super+i"], "command": "go_guru", "args": {"mode": "implements"} }, { "keys": ["ctrl+super+r"], "command": "go_guru", "args": {"mode": "referrers"} }, + { "keys": ["ctrl+.+ctrl+g"], "command": "go_guru", "args": {"mode": "definition", output=false}}, ... please set this in your user keybinds, no here. -*/ \ No newline at end of file +*/ diff --git a/Default (Windows).sublime-mousemap b/Default (Windows).sublime-mousemap new file mode 100644 index 0000000..975628e --- /dev/null +++ b/Default (Windows).sublime-mousemap @@ -0,0 +1,12 @@ +[ + { + "button": "button2", + "modifiers": ["ctrl"], + "press_command": "drag_select", + "command": "go_guru", + "args": { + "mode": "definition", + "output": false + }, + }, +] diff --git a/Default.sublime-commands b/Default.sublime-commands index ebc1b58..8d00f83 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -42,6 +42,14 @@ "mode": "definition" } }, + { + "caption": "GoGuru: jump to definition", + "command": "go_guru", + "args": { + "output": false, + "mode": "definition" + } + }, { "caption": "GoGuru: describe", "command": "go_guru", @@ -78,24 +86,24 @@ } }, { - "caption": "GoGuru: referrers", + "caption": "GoGuru: whicherrs", "command": "go_guru", "args": { - "mode": "referrers" + "mode": "whicherrs" } }, { - "caption": "GoGuru: what", + "caption": "GoGuru: referrers", "command": "go_guru", "args": { - "mode": "what" + "mode": "referrers" } }, { - "caption": "GoGuru: callgraph", + "caption": "GoGuru: what", "command": "go_guru", "args": { - "mode": "callgraph" + "mode": "what" } }, { @@ -129,5 +137,5 @@ "platform": "OSX" } }, - + ] diff --git a/README.md b/README.md index 5105d22..5cd8257 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ GoGuru is a Golang plugin for [SublimeText](http://www.sublimetext.com/) 3 that Please report any issues or improvements here [https://github.com/alvarolm/GoGuru/issues](https://github.com/alvarolm/GoGuru/issues) -based on previus work from [waigani](http://github.com/waigani/GoOracle). +based on previous work from [waigani](http://github.com/waigani/GoOracle). -the guru tool still is on developent, +the guru tool still is on development, check out the plan, the official git repo and the code review if you want to keep up: * https://docs.google.com/document/d/1UErU12vR7jTedYvKHVNRzGPmXqdMASZ6PfE7B-p6sIg/edit# * https://go.googlesource.com/tools/+log/master/cmd/guru @@ -38,6 +38,8 @@ Select, or place your cursor over, a symbol (function, variable, constant etc) a Select one of the modes and the output will be displayed in a new tab. **double click on the file name in the results to jump directly to it.** +You also can hold the `ctrl` key and `right-click` on a symbol to jump right to the definition. + Install ------- @@ -111,6 +113,7 @@ Default key binding: [ { "keys": ["ctrl+shift+g"], "command": "go_guru"}, { "keys": ["ctrl+alt+shift+g"], "command": "go_guru_show_results"}, + { "keys": ["ctrl+.+ctrl+g"], "command": "go_guru_goto_definition", "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, ] ``` @@ -123,9 +126,27 @@ You can also set a key binding for a specific mode by adding a "mode" arg, e.g.: { "keys": ["ctrl+super+c"], "command": "go_guru", "args": {"mode": "callers"} }, { "keys": ["ctrl+super+i"], "command": "go_guru", "args": {"mode": "implements"} }, { "keys": ["ctrl+super+r"], "command": "go_guru", "args": {"mode": "referrers"} }, + { "keys": ["ctrl+.+ctrl+g"], "command": "go_guru", "args": {"mode": "definition", output=false}}, ... ``` +Default mouse bindings: + +```javascript +[ + { + "button": "button2", + "modifiers": ["ctrl"], + "press_command": "drag_select", + "command": "go_guru", + "args": { + "mode": "definition", + "output": false + }, + }, +] +``` + Dependencies ------------ diff --git a/goGuru.py b/goGuru.py index bfec684..f8347cb 100644 --- a/goGuru.py +++ b/goGuru.py @@ -10,18 +10,28 @@ # TODO: review & clean -import sublime, sublime_plugin, subprocess, time, re, os, subprocess, sys + +import sublime +import sublime_plugin +import re +import os +import subprocess +import sys + def log(*msg): print("GoGuru:", msg[0:]) + def debug(*msg): if get_setting("goguru_debug", False): print("GoGuru [DEBUG]:", msg[0:]) + def error(*msg): print("GoGuru [ERROR]:", msg[0:]) + def plugin_loaded(): # load shellenv def load_shellenv(): @@ -32,13 +42,13 @@ def load_shellenv(): if get_setting("goguru_use_golangconfig", False): try: global golangconfig - import golangconfig + import golangconfig except: error("couldn't import golangconfig:", sys.exc_info()[0]) log("using shellenv instead of golangconfig") - use_golangconfig = False + # use_golangconfig = False load_shellenv() - + else: load_shellenv() @@ -51,16 +61,15 @@ def load_shellenv(): p = subprocess.Popen(["git", "describe", "master", "--tags"], stdout=subprocess.PIPE, cwd=PluginPath) GITVERSION = p.communicate()[0].decode("utf-8").rstrip() if p.returncode != 0: - debug("git return code", p.returncode) - raise Exception("git return code", p.returncode) - + debug("git return code", p.returncode) + raise Exception("git return code", p.returncode) defsettings = os.path.join(PluginPath, 'Default.sublime-settings') - f = open(defsettings,'r') + f = open(defsettings, 'r') filedata = f.read() f.close() - newdata = filedata.replace(get_setting('goguru_version'), GITVERSION+'_') - f = open(defsettings,'w') + newdata = filedata.replace(get_setting('goguru_version'), GITVERSION + '_') + f = open(defsettings, 'w') f.write(newdata) f.close() except: @@ -75,22 +84,28 @@ def load_shellenv(): us.set('goguru_debug', get_setting("goguru_debug", False)) sublime.save_settings("GoGuru.sublime-settings") + class GoGuruCommand(sublime_plugin.TextCommand): + def __init__(self, view): - self.view = view + self.view = view self.mode = 'None' self.env = 'None' self.local_package = 'None' - def run(self, edit, mode=None): + def run(self, edit, mode=None, output=True): + """ + :param output: won't show the show_panel if set to False. It is particularly useful for mouse clicks. + """ + self.output = output try: region = self.view.sel()[0] text = self.view.substr(sublime.Region(0, region.end())) cb_map = self.get_map(text) byte_end = cb_map[sorted(cb_map.keys())[-1]] byte_begin = None - if not region.empty(): - byte_begin = cb_map[region.begin()-1] + if not region.empty(): + byte_begin = cb_map[region.begin() - 1] except: sublime.error_message('GoGuru:\nCouldn\'t get cursor positon, make sure that the Go source file is saved and the cursor is over the identifier (variable, function ...) you want to query.') error("couldn't get cursor positon: ", sys.exc_info()) @@ -104,24 +119,25 @@ def run(self, edit, mode=None): if mode == "godoc": self.view.window().run_command('gs_doc', {'mode': "hint"}) gsdoc = self.view.window().find_output_panel('GsDoc-output-output') - if gsdoc != None: - if not 'no docs found' in gsdoc.substr(gsdoc.line(0)): + if gsdoc is not None: + if 'no docs found' not in gsdoc.substr(gsdoc.line(0)): return + def messageLookingDoc(): self.write_out(None, "'gs_doc' failed,\nsearching documentation with 'goguru mode=godoc'...") - sublime.set_timeout(lambda: messageLookingDoc(), 150) # any other choice besides timeout ? + sublime.set_timeout(lambda: messageLookingDoc(), 150) # any other choice besides timeout ? mode = "describe" elif mode == "godoc_direct": def messageLookingDoc(): self.write_out(None, "'searching documentation with 'goguru mode=godoc_direct'...") - sublime.set_timeout(lambda: messageLookingDoc(), 150) # any other choice besides timeout ? + sublime.set_timeout(lambda: messageLookingDoc(), 150) # any other choice besides timeout ? mode = "describe" self.guru(byte_end, begin_offset=byte_begin, mode=mode, callback=self.guru_complete) return # Get the guru mode from the user. - modes = ["callees","callers","callstack","definition","describe","freevars","implements","peers","pointsto","referrers","what","whicherrs"] - descriptions = [ + modes = ["callees", "callers", "callstack", "definition", "describe", "freevars", "implements", "peers", "pointsto", "referrers", "what", "whicherrs"] + descriptions = [ "callees show possible targets of selected function call", "callers show possible callers of selected function", "callstack show path from callgraph root to selected function", @@ -137,7 +153,7 @@ def messageLookingDoc(): # Call guru cmd with the given mode. def on_done(i): - if i >= 0 : + if i >= 0: self.write_running(modes[i]) self.guru(byte_end, begin_offset=byte_begin, mode=modes[i], callback=self.guru_complete) @@ -158,9 +174,8 @@ def write_running(self, mode): # Run a new command to use the edit object for this view. view.run_command('go_guru_write_running', {'mode': mode}) - - if get_setting("goguru_output", "buffer") == "output_panel": - window.run_command('show_panel', {'panel': "output." + view.name(), 'toggle': False } ) + if get_setting("goguru_output", "buffer") == "output_panel" and self.output: + window.run_command('show_panel', {'panel': "output." + view.name(), 'toggle': False}) else: window.focus_view(view) @@ -168,7 +183,7 @@ def write_out(self, result, err): """ Write the guru output to a new file. """ - def cleanPackageAddr (p): + def cleanPackageAddr(p): return str(p).replace('"', '').replace('(', '').replace(')', '').replace('*', '') window = self.view.window() @@ -176,7 +191,7 @@ def cleanPackageAddr (p): # parse guru describe to query go doc jump = False - if (self.mode == 'godoc' or self.mode == 'godoc_direct') and result: + if (self.mode == 'godoc' or self.mode == 'godoc_direct') and result: parts = result.split() definitionLine = result.split('\n')[1] @@ -184,8 +199,7 @@ def cleanPackageAddr (p): package = '' identifier = '' - subparts = [] - + debug('godoc', 'goType', goType) if goType == 'package': # /home/username/go/src/myProject/utils/global/global.go:104.24-104.29: reference to package "errors" @@ -205,7 +219,7 @@ def cleanPackageAddr (p): # /home/username/go/src/myProject/watchdog/main.go:84.5-84.17: reference to func StartUpClient() else: - package = "-u "+self.local_package + package = "-u " + self.local_package parts[4] = cleanPackageAddr(parts[4]).split(".") identifier = '.'.join(parts[4]) @@ -227,7 +241,7 @@ def cleanPackageAddr (p): # /home/username/go/src/myProject/watchdog/main.go:200.18-200.19: reference to method func (*instanceStats).me() string else: parts[5] = parts[5].split(".") - package = "-u "+self.local_package + package = "-u " + self.local_package identifier = '.'.join(parts[5][1:]) elif goType == 'interface': @@ -245,10 +259,11 @@ def cleanPackageAddr (p): if not jump: cmd = "go doc %(package)s %(identifier)s " % { - "package": package, - "identifier": identifier} - debug("godoc","cmd", cmd) - + "package": package, + "identifier": identifier, + } + debug("godoc", "cmd", cmd) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True, env=self.env) o, e = proc.communicate() @@ -258,10 +273,10 @@ def cleanPackageAddr (p): prettierResult = '' for line in result.splitlines(): if line[0:4] == ' ': - prettierResult += '//'+ line + '\n' + prettierResult += '//' + line + '\n' else: prettierResult += line + '\n' - result = prettierResult+'\n'+definitionLine + result = '\n'.join(prettierResult, definitionLine) err = e.decode('utf-8') # Run a new command to use the edit object for this view. @@ -269,13 +284,21 @@ def cleanPackageAddr (p): 'result': result, 'err': err}) - if get_setting("goguru_output", "buffer") == "output_panel": - window.run_command('show_panel', {'panel': "output." + view.name() }) + if get_setting("goguru_output", "buffer") == "output_panel" and self.output: + window.run_command('show_panel', {'panel': "output." + view.name()}) else: window.focus_view(view) + # the case when clicking doesn't require showing the output + if self.mode == 'definition' and not self.output: + if result: + coordinates = result.split(':')[:3] + new_view = window.open_file(':'.join(coordinates), sublime.ENCODED_POSITION) + group, _ = window.get_view_index(new_view) + if group != -1: + window.focus_group(group) # jump to definition if is set - if self.mode == 'definition': + elif self.mode == 'definition': if get_setting("goguru_jumpto_definition", False): if result: coordinates = result.split(':')[:3] @@ -283,7 +306,6 @@ def cleanPackageAddr (p): group, _ = window.get_view_index(new_view) if group != -1: window.focus_group(group) - def get_map(self, chars): """ Generate a map of character offset to byte offset for the given string 'chars'. @@ -305,7 +327,7 @@ def guru(self, end_offset, begin_offset=None, mode="describe", callback=None): pos = "#" + str(end_offset) if begin_offset is not None: - pos = "#%i,#%i" %(begin_offset, end_offset) + pos = "#%i,#%i" % (begin_offset, end_offset) file_path = self.view.file_name() @@ -322,7 +344,7 @@ def guru(self, end_offset, begin_offset=None, mode="describe", callback=None): toolpath = 'guru' cmd_env = shellenv.get_env(for_subprocess=True)[1] debug("cmd_env", cmd_env) - goguru_env = get_setting("goguru_env", {}) + goguru_env = get_setting("goguru_env", {}) debug("goguru_env", goguru_env) cmd_env.update(goguru_env) @@ -337,20 +359,20 @@ def guru(self, end_offset, begin_offset=None, mode="describe", callback=None): if useCurrentPackage: current_file_path = os.path.realpath(os.path.dirname(file_path)) GOPATH = os.path.realpath(cmd_env["GOPATH"]) - GOPATH = os.path.join(GOPATH,"src") + GOPATH = os.path.join(GOPATH, "src") local_package = os.path.relpath(current_file_path, GOPATH) if sublime.platform() == 'windows': local_package = local_package.replace('\\', '/') debug("GOPATH", GOPATH) debug("local_package", local_package) self.local_package = local_package - guru_scope = guru_scope+','+local_package + guru_scope = guru_scope + ',' + local_package guru_scope = guru_scope.strip() debug("guru_scope", guru_scope) if len(guru_scope) > 0: - guru_scope = "-scope "+guru_scope + guru_scope = "-scope " + guru_scope - guru_tags = "-tags \""+" ".join(get_setting("goguru_tags", ""))+"\"" + guru_tags = "-tags \"" + " ".join(get_setting("goguru_tags", "")) + "\"" guru_json = "" if get_setting("goguru_json", False): @@ -358,13 +380,14 @@ def guru(self, end_offset, begin_offset=None, mode="describe", callback=None): # Build guru cmd. cmd = "%(toolpath)s %(scope)s %(tags)s %(guru_json)s %(mode)s %(file_path)s:%(pos)s" % { - "toolpath": toolpath, - "file_path": file_path, - "pos": pos, - "guru_json": guru_json, - "mode": mode, - "scope": guru_scope, - "tags": guru_tags} + "toolpath": toolpath, + "file_path": file_path, + "pos": pos, + "guru_json": guru_json, + "mode": mode, + "scope": guru_scope, + "tags": guru_tags, + } debug("cmd", cmd) sublime.set_timeout_async(lambda: self.runInThread(cmd, callback, cmd_env), 0) @@ -391,7 +414,7 @@ def run(self, edit, result, err): view.insert(edit, view.size(), err) view.insert(edit, view.size(), "\n\n\n") - + class GoGuruWriteRunningCommand(sublime_plugin.TextCommand): """ Writes the guru output to the current view. @@ -407,48 +430,56 @@ def run(self, edit, mode): class GoGuruShowResultsCommand(sublime_plugin.TextCommand): + def run(self, edit): if get_setting("goguru_output", "buffer") == "output_panel": - self.view.window().run_command('show_panel', {'panel': "output.GoGuru Output" }) + self.view.window().run_command('show_panel', {'panel': "output.GoGuru Output"}) else: output_view = get_output_view(self.view.window()) self.view.window().focus_view(output_view) class GoGuruOpenResultCommand(sublime_plugin.EventListener): + def on_selection_modified(self, view): - if view.name() == "GoGuru Output": - if len(view.sel()) != 1: - return - if view.sel()[0].size() == 0: - return + if view.name() == "GoGuru Output": + if len(view.sel()) != 1: + return + if view.sel()[0].size() == 0: + return - lines = view.lines(view.sel()[0]) - if len(lines) != 1: - return + lines = view.lines(view.sel()[0]) + if len(lines) != 1: + return + + line = view.full_line(lines[0]) + text = view.substr(line) + + # format = get_setting("guru_format") - line = view.full_line(lines[0]) - text = view.substr(line) + # "filename:line:col" pattern for json + m = re.search("\"([^\"]+):([0-9]+):([0-9]+)\"", text) - format = get_setting("guru_format") + # >filename:line:col< pattern for xml + if m is None: + m = re.search(">([^<]+):([0-9]+):([0-9]+)<", text) - # "filename:line:col" pattern for json - m = re.search("\"([^\"]+):([0-9]+):([0-9]+)\"", text) + # filename:line.col-line.col: pattern for plain + if m is None: + m = re.search("^(.+\.go):([0-9]+).([0-9]+)[-: ]", text) - # >filename:line:col< pattern for xml - if m == None: - m = re.search(">([^<]+):([0-9]+):([0-9]+)<", text) + if m: + w = view.window() + new_view = w.open_file(m.group(1) + ':' + m.group(2) + ':' + m.group(3), sublime.ENCODED_POSITION) + group, index = w.get_view_index(new_view) + if group != -1: + w.focus_group(group) - # filename:line.col-line.col: pattern for plain - if m == None: - m = re.search("^(.+\.go):([0-9]+).([0-9]+)[-: ]", text) - - if m: - w = view.window() - new_view = w.open_file(m.group(1) + ':' + m.group(2) + ':' + m.group(3), sublime.ENCODED_POSITION) - group, index = w.get_view_index(new_view) - if group != -1: - w.focus_group(group) + +class GoGuruGotoDefinitionCommand(GoGuruCommand): + + def run(self, edit, mode=None, output=True): + super().run(edit=edit, mode="definition", output=False) def get_output_view(window): @@ -476,14 +507,15 @@ def get_output_view(window): return view + def get_setting(key, default=None): - """ Returns the setting in the following hierarchy: project setting, user setting, + """ Returns the setting in the following hierarchy: project setting, user setting, default setting. If none are set the 'default' value passed in is returned. """ val = None try: - val = sublime.active_window().active_view().settings().get('GoGuru', {}).get(key) + val = sublime.active_window().active_view().settings().get('GoGuru', {}).get(key) except AttributeError: pass @@ -493,4 +525,4 @@ def get_setting(key, default=None): val = sublime.load_settings("Default.sublime-settings").get(key) if not val: val = default - return val \ No newline at end of file + return val