Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Results keyboard navigation, keyboard shortcuts and fixes #67

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CscopeSublime.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
5 changes: 5 additions & 0 deletions Default.sublime-commands
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
}
]
2 changes: 1 addition & 1 deletion Lookup Results.hidden-tmLanguage
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<array>
<dict>
<key>match</key>
<string>(.*):$</string>
<string>^(((?!scope: ).)*):$</string>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why you changed this please? I'm not aware of a problem with this right now, so I'm not sure what this is trying to fix.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If line with symbol ends with ':' it is catched by this regex (seen below, result treated like filename), fix prevents it.
zrzut ekranu 2018-04-08 17 15 21

<key>captures</key>
<dict>
<key>1</key>
Expand Down
130 changes: 114 additions & 16 deletions cscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused on what this is doing here too. Can you please help me understand what this is doing that it wasn't already doing? Also, I suspect that this will not work with Sublime Text 2. Thus far, while I'm not actively supporting Sublime Text 2, I've been making sure I at least don't break it with changes. Can you verify that this will at least not cause errors in ST2?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I keep results and currently selected results in view settings of every Cscope results.
I checked on ST2 and fixed scrolling to result and disabled class for mouse support.
But. The master branch wasn't working on ST2 :) Minor problems:

  • trailing comma in settings
  • ValueError: zero length fields name in format cause of lack of indices in the format specs

# 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):
Expand Down Expand Up @@ -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))
Expand All @@ -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))

Expand All @@ -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: ).)+):$')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this change please? Why the change in regexp?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If line with symbol ends with ':' it is catched by this regex (seen below), which leads to such error when trying to open results lower than that.

Unable to open file: E:\Dev\C\sandbox\cscope_tmp\    17 [scope: main] case ENUM_TESTCASE_1

filename_re = re.compile(r'([a-zA-Z0-9_\-\.]+):')
linenum_re = re.compile(r'^\s*([0-9]+)')

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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