Permalink
Browse files

add module function search

  • Loading branch information...
1 parent 96fed43 commit 60966f3430bbf17536f6a743985225e73755fc43 @ostinelli committed Oct 12, 2012
View
@@ -1,6 +1,7 @@
*.pyc
*.cache
Current-Project.disasm
+Current-Project.searches
Current-Project.sublime-completions
# OSX
@@ -3,5 +3,6 @@
{ "keys": ["ctrl+shift+f9"], "command": "sublim_erl_dialyzer", "context": [{ "key": "selector", "operator": "equal", "operand": "source.erlang" }] },
{ "keys": ["ctrl+f8"], "command": "sublim_erl_redo" },
{ "keys": ["ctrl+alt+f8"], "command": "sublim_erl_ct_results" },
- { "keys": ["ctrl+alt+l"], "command": "sublim_erl_auto_format" }
+ { "keys": ["ctrl+alt+l"], "command": "sublim_erl_auto_format" },
+ { "keys": ["ctrl+alt+p"], "command": "sublim_erl_function_search" }
]
@@ -3,5 +3,6 @@
{ "keys": ["super+shift+f9"], "command": "sublim_erl_dialyzer", "context": [{ "key": "selector", "operator": "equal", "operand": "source.erlang" }] },
{ "keys": ["super+f8"], "command": "sublim_erl_redo" },
{ "keys": ["super+alt+f8"], "command": "sublim_erl_ct_results" },
- { "keys": ["super+alt+l"], "command": "sublim_erl_auto_format" }
+ { "keys": ["super+alt+l"], "command": "sublim_erl_auto_format" },
+ { "keys": ["super+alt+p"], "command": "sublim_erl_function_search" }
]
@@ -43,6 +43,7 @@ def __init__(self):
def generate_completions(self, starting_dir, dest_file_base):
disasms = {}
completions = []
+ searches = []
# loop directory
rel_dirs = []
for root, dirnames, filenames in os.walk(starting_dir):
@@ -60,9 +61,15 @@ def generate_completions(self, starting_dir, dest_file_base):
f = open(filepath, 'r')
module = f.read()
f.close()
- module_completions = self.get_completions(module)
+ module_completions, line_numbers = self.get_completions(module)
if len(module_completions) > 0:
- disasms[module_name] = module_completions
+ # set disasm
+ disasms[module_name] = sorted(module_completions, key=lambda k: k[0])
+ # set searches
+ for i in range(0, len(module_completions)):
+ function, completion = module_completions[i]
+ searches.append(("%s:%s" % (module_name, function), filepath, line_numbers[i]))
+ # set module completions
completions.append("{ \"trigger\": \"%s\", \"contents\": \"%s\" }" % (module_name, module_name))
# add BIF completions?
@@ -77,6 +84,12 @@ def generate_completions(self, starting_dir, dest_file_base):
# erlang completions
for c in bif_completions['erlang']:
completions.append("{ \"trigger\": \"%s\", \"contents\": \"%s\" }" % (c[0], c[1]))
+ else:
+ # we are generating project disasm -> write to files: searches
+ f_searches = open("%s.searches" % dest_file_base, 'wb')
+ pickle.dump(sorted(searches, key=lambda k: k[0]), f_searches)
+ f_searches.close()
+
# write to files: disasms
f_disasms = open("%s.disasm" % dest_file_base, 'wb')
pickle.dump(disasms, f_disasms)
@@ -86,21 +99,25 @@ def generate_completions(self, starting_dir, dest_file_base):
if len(completions) > 0:
f_completions.write("{ \"scope\": \"source.erlang\", \"completions\": [\n" + ',\n'.join(completions) + "\n]}")
else:
- f_completions.write("")
+ f_completions.write("{}")
f_completions.close()
+
def get_completions(self, module):
# get export portion in code module
all_completions = []
+ all_line_numbers = []
for m in self.regex['export_section'].finditer(module):
export_section = m.groups()[0]
if export_section:
# get list of exports
exports = self.get_code_list_without_comments(export_section)
if len(exports) > 0:
- all_completions.extend(self.generate_module_completions(module, exports))
+ completions, line_numbers = self.generate_module_completions(module, exports)
+ all_completions.extend(completions)
+ all_line_numbers.extend(line_numbers)
# return all_completions
- return sorted(all_completions, key=lambda k: k[0])
+ return (all_completions, all_line_numbers)
def bif_completions(self):
return {
@@ -252,44 +269,52 @@ def bif_completions(self):
def generate_module_completions(self, module, exports):
completions = []
+ line_numbers = []
for export in exports:
# split param count definition
fun = export.split('/')
if len(fun) == 2:
- params = self.generate_params(fun, module)
+ params, lineno = self.generate_params(fun, module)
if params != None:
completions.append((export, '%s%s' % (fun[0].strip(), params)))
- return completions
+ line_numbers.append(lineno)
+ return (completions, line_numbers)
def generate_params(self, fun, module):
# get params count
arity = int(fun[1])
# init
current_params = []
+ lineno = 0
# get params
regex = re.compile(r"%s\((.*)\)" % fun[0], re.MULTILINE)
for m in regex.finditer(module):
params = m.groups()[0]
- # ensure there's no 'when'
- params = params.split(')')[0]
- params = self.split_params(params)
- if len(params) == arity:
- # function definition has the correct arity
- if current_params != []:
- for i in range(0, len(params)):
- if current_params[i] == '*' and self.regex['varname'].search(params[i]):
- # found a valid variable name
- current_params[i] = params[i]
- else:
- current_params = params
+ # if this is a not a spec definition
+ spec_def_pos = module.rfind('-spec', 0, m.start())
+ if spec_def_pos == -1 or len(module[spec_def_pos + 5:m.start()].strip()) > 0:
+ # ensure there's no 'when'
+ params = params.split(')')[0]
+ params = self.split_params(params)
+ if len(params) == arity:
+ # get match line number
+ if lineno == 0: lineno = module.count('\n', 0, m.start()) + 1
+ # function definition has the correct arity
+ if current_params != []:
+ for i in range(0, len(params)):
+ if current_params[i] == '*' and self.regex['varname'].search(params[i]):
+ # found a valid variable name
+ current_params[i] = params[i]
+ else:
+ current_params = params
# ensure current params have variable names
for i in range(0, len(current_params)):
if current_params[i] == '*':
current_params[i] = '${%d:Param%d}' % (i + 1, i + 1)
else:
current_params[i] = '${%d:%s}' % (i + 1, current_params[i])
# return
- return '(' + ', '.join(current_params) + ') $%d' % (len(current_params) + 1)
+ return ('(' + ', '.join(current_params) + ') $%d' % (len(current_params) + 1), lineno)
def split_params(self, params):
# replace content of graffles with *
@@ -359,55 +384,59 @@ def test_generate_params(self):
(('start', '3'),"""
start(One, Two, Three) -> ok.
- """, "(${1:One}, ${2:Two}, ${3:Three}) $4"),
+ """, ("(${1:One}, ${2:Two}, ${3:Three}) $4", 2)),
(('start', '3'),"""
start(One, <<>>, Three) -> ok;
start(One, Two, Three) -> ok.
- """, "(${1:One}, ${2:Two}, ${3:Three}) $4"),
+ """, ("(${1:One}, ${2:Two}, ${3:Three}) $4", 2)),
(('start', '3'),"""
start(One, {Abc, Cde}, Three) -> ok;
start(One, Two, Three) -> ok.
- """, "(${1:One}, ${2:Two}, ${3:Three}) $4"),
+ """, ("(${1:One}, ${2:Two}, ${3:Three}) $4", 2)),
(('start', '3'),"""
start(One, <<Abc:16/binary, Cde/binary>>, Three) -> ok
- """, "(${1:One}, ${2:Param2}, ${3:Three}) $4"),
+ """, ("(${1:One}, ${2:Param2}, ${3:Three}) $4", 2)),
(('start', '3'),"""
start(One, [Abc|R] = Two, Three) -> ok
- """, "(${1:One}, ${2:Two}, ${3:Three}) $4"),
+ """, ("(${1:One}, ${2:Two}, ${3:Three}) $4", 2)),
(('start', '3'),"""
start(One, [Abc|R], Three) -> ok
- """, "(${1:One}, ${2:Param2}, ${3:Three}) $4"),
+ """, ("(${1:One}, ${2:Param2}, ${3:Three}) $4", 2)),
(('start', '3'),"""
start(One, [Abc, R], Three) -> ok
- """, "(${1:One}, ${2:Param2}, ${3:Three}) $4"),
+ """, ("(${1:One}, ${2:Param2}, ${3:Three}) $4", 2)),
(('start', '3'),"""
start(One, Two, Three, Four) -> ok.
start(One, {Abc, Cde} = Two, Three) -> ok;
start(One, <<>>, Three) -> ok.
- """, "(${1:One}, ${2:Two}, ${3:Three}) $4"),
+ """, ("(${1:One}, ${2:Two}, ${3:Three}) $4", 3)),
+ (('start', '0'),"""
+ -spec start() -> ok.
+ start() -> ok;
+ """, ("() $1", 3)),
(('start', '1'),"""
start(#client{name=Name} = Client) -> ok.
- """, "(${1:Client}) $2"),
+ """, ("(${1:Client}) $2", 2)),
(('start', '2'),"""
start(Usr, Opts) when is_binary(Usr), is_list(Opts) -> ok.
- """, "(${1:Usr}, ${2:Opts}) $3"),
+ """, ("(${1:Usr}, ${2:Opts}) $3", 2)),
(('start', '1'),"""
start( << _:3/bytes,Body/binary >> = Data) -> ok.
- """, "(${1:Data}) $2"),
+ """, ("(${1:Data}) $2", 2)),
(('start', '2'),"""
start(Usr, Opts) when is_binary(Usr), is_list(Opts) -> ok.
- """, "(${1:Usr}, ${2:Opts}) $3"),
+ """, ("(${1:Usr}, ${2:Opts}) $3", 2)),
]
for f in range(0, len(fixtures)):
self.assertEqual(self.parser.generate_params(fixtures[f][0], fixtures[f][1]), fixtures[f][2])
@@ -424,13 +453,13 @@ def test_get_completions(self):
four(Four1, <<>>, Four3, Four4) -> ok;
four(Four1, {Four2A, Four2B, <<>>} = Four2, Four3, Four4) -> ok;
""",
- [
- ('four/4', 'four(${1:Four1}, ${2:Four2}, ${3:Four3}, ${4:Four4}) $5'),
+ ([
+ ('zero/0', 'zero() $1'),
('one/1', 'one(${1:One}) $2'),
- ('three/3', 'three(${1:Three1}, ${2:Three2}, ${3:Three3}) $4'),
('two/2', 'two(${1:Two1}, ${2:Two2}) $3'),
- ('zero/0', 'zero() $1'),
- ]),
+ ('three/3', 'three(${1:Three1}, ${2:Three2}, ${3:Three3}) $4'),
+ ('four/4', 'four(${1:Four1}, ${2:Four2}, ${3:Four3}, ${4:Four4}) $5')
+ ], [4, 5, 6, 7, 8])),
("""
-export([zero/0]).
@@ -443,13 +472,13 @@ def test_get_completions(self):
four(Four1, <<>>, Four3, Four4) -> ok;
four(Four1, {Four2A, Four2B, <<>>} = Four2, Four3, Four4) -> ok;
""",
- [
- ('four/4', 'four(${1:Four1}, ${2:Four2}, ${3:Four3}, ${4:Four4}) $5'),
+ ([
+ ('zero/0', 'zero() $1'),
('one/1', 'one(${1:One}) $2'),
- ('three/3', 'three(${1:Three1}, ${2:Three2}, ${3:Three3}) $4'),
('two/2', 'two(${1:Two1}, ${2:Two2}) $3'),
- ('zero/0', 'zero() $1'),
- ])
+ ('three/3', 'three(${1:Three1}, ${2:Three2}, ${3:Three3}) $4'),
+ ('four/4', 'four(${1:Four1}, ${2:Four2}, ${3:Four3}, ${4:Four4}) $5')
+ ], [5, 6, 7, 8, 9]))
]
for f in range(0, len(fixtures)):
self.assertEqual(self.parser.get_completions(fixtures[f][0]), fixtures[f][1])
@@ -0,0 +1,99 @@
+# ==========================================================================================================
+# SublimErl - A Sublime Text 2 Plugin for Erlang Integrated Testing & Code Completion
+#
+# Copyright (C) 2012, Roberto Ostinelli <roberto@ostinelli.net>.
+# All rights reserved.
+#
+# BSD License
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided
+# that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+# following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+# the following disclaimer in the documentation and/or other materials provided with the distribution.
+# * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
+# products derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+# ==========================================================================================================
+
+
+# imports
+import sublime
+import os, time, threading, pickle
+from sublimerl_core import SUBLIMERL, SublimErlTextCommand, SublimErlProjectLoader
+from sublimerl_completion import SUBLIMERL_COMPLETIONS
+
+
+# main autoformat
+class SublimErlFunctionSearch():
+
+ def __init__(self, view):
+ # init
+ self.view = view
+ self.window = view.window()
+ self.search_completions = []
+
+ def show(self):
+ self.set_search_completions()
+ completions = []
+ for name, filepath, lineno in self.search_completions:
+ completions.append(name)
+ sublime.active_window().show_quick_panel(completions, self.on_select)
+
+ def set_search_completions(self):
+ # load file
+ searches_filepath = os.path.join(SUBLIMERL.plugin_path, "completion", "Current-Project.searches")
+ f = open(searches_filepath, 'r')
+ searches = pickle.load(f)
+ f.close()
+ self.search_completions = searches
+
+ def on_select(self, index):
+ # get file and line
+ name, filepath, lineno = self.search_completions[index]
+ self.open_file_and_goto_line(filepath, lineno)
+
+ def open_file_and_goto_line(self, filepath, line):
+ # open file
+ self.new_view = self.window.open_file(filepath)
+ # wait until file is loaded before going to the appropriate line
+ this = self
+ self.check_file_loading()
+ class SublimErlThread(threading.Thread):
+ def run(self):
+ # wait until file has done loading
+ s = 0
+ while this.is_loading and s < 3:
+ time.sleep(0.1)
+ sublime.set_timeout(this.check_file_loading, 0)
+ s += 1
+ # goto line
+ def goto_line():
+ # goto line
+ this.new_view.run_command("goto_line", {"line": line} )
+ # remove unused attrs
+ del this.new_view
+ del this.is_loading
+ if not this.is_loading: sublime.set_timeout(goto_line, 0)
+
+ SublimErlThread().start()
+
+ def check_file_loading(self):
+ self.is_loading = self.new_view.is_loading()
+
+
+# repeat last test
+class SublimErlFunctionSearchCommand(SublimErlTextCommand):
+ def run_command(self, edit):
+ search = SublimErlFunctionSearch(self.view)
+ search.show()
@@ -443,5 +443,4 @@ def run_command(self, edit):
def show_contextual_menu(self):
loader = SublimErlProjectLoader(self.view)
index_path = os.path.abspath(os.path.join(loader.project_root, 'logs', 'index.html'))
- print index_path
return os.path.exists(index_path)

0 comments on commit 60966f3

Please sign in to comment.