diff --git a/CscopeSublime.sublime-settings b/CscopeSublime.sublime-settings index 85ef125..85ae5cd 100644 --- a/CscopeSublime.sublime-settings +++ b/CscopeSublime.sublime-settings @@ -31,4 +31,8 @@ // An optional custom command used to build cscope's database. // This must be in array format. //"database_build_command": [ "cscope-indexer", "-r" ], + + // On default build command, whether to treat folders in project (after the 1st one) as + // additional source directories + "auto_next_source_dirs": false } diff --git a/Default.sublime-commands b/Default.sublime-commands index 16a50be..919c549 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -43,5 +43,10 @@ "caption": "Cscope: Rebuild database", "command": "cscope", "args": { "mode": "database_rebuild" } + }, + { + "caption": "Cscope: Close all opened results", + "command": "cscope", + "args": { "mode": "close_opened_results" } } ] diff --git a/Lookup Results.hidden-tmLanguage b/Lookup Results.hidden-tmLanguage index d811536..2b940ee 100644 --- a/Lookup Results.hidden-tmLanguage +++ b/Lookup Results.hidden-tmLanguage @@ -9,7 +9,7 @@ match - (.*):$ + ^(((?!scope: ).)*):$ captures 1 diff --git a/cscope.py b/cscope.py index 96d82de..0b9bc45 100644 --- a/cscope.py +++ b/cscope.py @@ -26,6 +26,7 @@ 8: "files #including this file" } CSCOPE_CMD_DATABASE_REBUILD = "database_rebuild" +CSCOPE_CMD_CLOSE_OPENED = "close_opened_results" def get_settings(): return sublime.load_settings("CscopeSublime.sublime-settings") @@ -41,6 +42,18 @@ def get_setting(key, default=None, view=None): pass return get_settings().get(key, default) +def update_result_selection(view, selection_index, selection, update_sel=True): + # Set index of new selection, highlight it and move view to it + view.settings().set('cscope_results_sel', selection_index) + if view.get_regions('selection'): + # Do not scroll view on first open (prevents glitch on ST2) + view.show(selection) + view.add_regions('selection', [selection], 'comment', 'dot') + # Update cursor position to new result + if update_sel: + view.sel().clear() + view.sel().add(sublime.Region(selection.a, selection.a)) + class CscopeDatabase(object): def __init__(self, view, executable): @@ -124,11 +137,11 @@ def update_location(self, filename): # If we haven't found an existing database location and root # directory, and the user has a project defined for this, and - # there's only one path in that project, let's just assume that that + # there's one or more paths in that project, let's just assume that first # path is the root of the project. This should be a safe assumption # and should allow us to create an initial database, if one doesn't # exist, in a sane place. - if self.root is None and self.location is None and len(project_info_paths) == 1: + if self.root is None and self.location is None and len(project_info_paths) >= 1: self.root = project_info_paths[0] print('CscopeDatabase: Database not found but setting root: {}' .format(self.root)) @@ -146,6 +159,14 @@ def rebuild(self): if not (cscope_arg_list and isinstance(cscope_arg_list, list)): cscope_arg_list = [self.executable, '-Rbq'] + # If the user has a project with more than one path and we're putting + # the database in the first one, treat other paths as additional source dirs + if get_setting('auto_next_source_dirs') and hasattr(self.view.window(), 'project_data'): + project_info = self.view.window().project_data() + if project_info and 'folders' in project_info and project_info['folders'][0]['path'] == self.root: + for folder in project_info['folders'][1:]: + cscope_arg_list += ["-s", folder['path']] + print('CscopeDatabase: Rebuilding database in directory: {}, using command: {}' .format(self.root, cscope_arg_list)) @@ -170,10 +191,33 @@ class CscopeVisiter(sublime_plugin.TextCommand): def __init__(self, view): self.view = view - def run(self, edit): + def run(self, edit, **args): if self.view.settings().get('syntax') == CSCOPE_SYNTAX_FILE: + # Handle keyboard navigation + if args.get('direction'): + d = args.get('direction') + r = self.view.get_regions('results') + if r == []: + return + view_height = len(r) - 1 + s = self.view.settings().get('cscope_results_sel') + what = { + 'up': -1, + 'down': 1, + 'pageup': -20, + 'pagedown': 20 + } + if d not in what: + return + s = s + what[d] + if s < 0: s = 0 + if s > view_height: s = view_height + + update_result_selection(self.view, s, r[s]) + return + root_re = re.compile(r'In folder (.+)') - filepath_re = re.compile(r'^(.+):$') + filepath_re = re.compile(r'^(((?!scope: ).)+):$') filename_re = re.compile(r'([a-zA-Z0-9_\-\.]+):') linenum_re = re.compile(r'^\s*([0-9]+)') @@ -295,24 +339,35 @@ def __init__(self, view, platform, database, symbol, mode, executable): # switch statement for the different formatted output # of Cscope's matches. def append_match_string(self, match, command_mode, nested): - match_string = "{0}".format(match["file"]) + # 'head': contains file name or separator '..' + # 'main': contains main line with result of lookup + match_string = {} + match_string['main'] = "{0}".format(match["file"]) + result_separator = ("{0:>6}\n").format("..") if command_mode in [0, 4, 6, 8]: if nested: - match_string = ("{0:>6}\n{1:>6} [scope: {2}] {3}").format("..", match["line"], match["scope"], match["instance"]) + match_string['head'] = result_separator + match_string['main'] = ("{0:>6} [scope: {1}] {2}").format(match["line"], match["scope"], match["instance"]) else: - match_string = ("\n{0}:\n{1:>6} [scope: {2}] {3}").format(match["file"].replace(self.database.root, "."), match["line"], match["scope"], match["instance"]) + match_string['head'] = ("\n{0}:\n").format(match["file"].replace(self.database.root, ".")) + match_string['main'] = ("{0:>6} [scope: {1}] {2}").format(match["line"], match["scope"], match["instance"]) elif command_mode == 1: if nested: - match_string = ("{0:>6}\n{1:>6} {2}").format("..", match["line"], match["instance"]) + match_string['head'] = result_separator + match_string['main'] = ("{0:>6} {1}").format(match["line"], match["instance"]) else: - match_string = ("\n{0}:\n{1:>6} {2}").format(match["file"].replace(self.database.root, "."), match["line"], match["instance"]) + match_string['head'] = ("\n{0}:\n").format(match["file"].replace(self.database.root, ".")) + match_string['main'] = ("{0:>6} {1}").format(match["line"], match["instance"]) elif command_mode in [2, 3]: if nested: - match_string = ("{0:>6}\n{1:>6} [function: {2}] {3}").format("..", match["line"], match["function"], match["instance"]) + match_string['head'] = result_separator + match_string['main'] = ("{0:>6} [function: {1}] {2}").format(match["line"], match["function"], match["instance"]) else: - match_string = ("\n{0}:\n{1:>6} [function: {2}] {3}").format(match["file"].replace(self.database.root, "."), match["line"], match["function"], match["instance"]) + match_string['head'] = ("\n{0}:\n").format(match["file"].replace(self.database.root, ".")) + match_string['main'] = ("{0:>6} [function: {1}] {2}").format(match["line"], match["function"], match["instance"]) elif command_mode == 7: - match_string = ("\n{0}:").format(match["file"].replace(self.database.root, ".")) + match_string['head'] = '' + match_string['main'] = ("\n{0}:").format(match["file"].replace(self.database.root, ".")) return match_string @@ -395,9 +450,10 @@ def run_cscope(self, mode, word): def run(self): matches = self.run_cscope(self.mode, self.symbol) self.num_matches = len(matches) - self.output = "In folder " + self.database.root + \ + header = "In folder " + self.database.root + \ "\nFound " + str(len(matches)) + " matches for " + CSCOPE_SEARCH_MODES[self.mode] + \ - ": " + self.symbol + "\n" + 50*"-" + "\n\n" + "\n".join(matches) + ": " + self.symbol + "\n" + 50*"-" + "\n" + self.output = [ header, matches ] class CscopeCommand(sublime_plugin.TextCommand): @@ -505,8 +561,14 @@ def display_results(self, symbol, output): cscope_view.set_syntax_file(CSCOPE_SYNTAX_FILE) cscope_view.set_read_only(True) + cscope_view.settings().set('cscope_results', True) + cscope_view.settings().set('cscope_results_sel', 0) def run(self, edit, mode): + if mode == CSCOPE_CMD_CLOSE_OPENED: + [x.close() for x in self.view.window().views() if x.settings().get('cscope_results')] + return + self.mode = mode self.executable = get_setting("executable", "cscope") @@ -571,7 +633,43 @@ def rebuild_database(self): class DisplayCscopeResultsCommand(sublime_plugin.TextCommand): def run(self, edit): - self.view.insert(edit, CscopeCommand.cscope_output_info['pos'], CscopeCommand.cscope_output_info['text']) + results = [] # results regions + header, matches = CscopeCommand.cscope_output_info['text'] + self.view.insert(edit, CscopeCommand.cscope_output_info['pos'], header) + for m in matches: + # insert header/file name + head = '\n' + m['head'] + start = self.view.size() + self.view.insert(edit, start, head) + # insert line with result + start = self.view.size() + self.view.insert(edit, start, m['main']) + # get and save region of line with result + region = sublime.Region(start, self.view.size()) + results.append(region) + self.view.add_regions('results', results) + if results: update_result_selection(self.view, 0, results[0]) if get_setting("display_outline") == True: symbol_regions = self.view.find_all(CscopeCommand.cscope_output_info['symbol'], sublime.LITERAL) - self.view.add_regions('cscopesublime-outlines', symbol_regions[1:], "text.find-in-files", "", sublime.DRAW_OUTLINED) + self.view.add_regions('cscopesublime-outlines', symbol_regions[1:], "entity.name.filename.find-in-files", "", sublime.DRAW_OUTLINED) + +if sublime.version()[0] >= '3': + class UpdateCscopeResultSelection(sublime_plugin.ViewEventListener): + def __init__(self, *args, **kwargs): + super(UpdateCscopeResultSelection, self).__init__(*args, **kwargs) + self.mouse_point = -1 + + @classmethod + def is_applicable(cls, settings): + return settings.get('cscope_results') + + def on_selection_modified(self): + mouse_point = self.view.sel()[0].a + if mouse_point == self.mouse_point: + return + self.mouse_point = mouse_point + r = self.view.get_regions('results') + for x in r: + if x.contains(mouse_point): + update_result_selection(self.view, r.index(x), x, False) + break