[gnome-builder] Create GJS Symbol Resolver plugin
- From: Patrick Griffis <pgriffis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] Create GJS Symbol Resolver plugin
- Date: Mon, 11 Sep 2017 23:43:03 +0000 (UTC)
commit d3ca92a02bf0d48e8355c4aaeecd1fe7c8589339
Author: Patrick Griffis <tingping tingping se>
Date: Mon Sep 11 19:09:01 2017 -0400
Create GJS Symbol Resolver plugin
This currently only implements the symbol tree interface.
meson_options.txt | 1 +
plugins/gjs-symbols/gjs_symbols.plugin | 10 ++
plugins/gjs-symbols/gjs_symbols.py | 248 ++++++++++++++++++++++++++++++++
plugins/gjs-symbols/meson.build | 13 ++
plugins/meson.build | 2 +
5 files changed, 274 insertions(+), 0 deletions(-)
---
diff --git a/meson_options.txt b/meson_options.txt
index 6cf20b6..e2dcacc 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -45,6 +45,7 @@ option('with_gcc', type: 'boolean')
option('with_gdb', type: 'boolean')
option('with_gettext', type: 'boolean')
option('with_git', type: 'boolean')
+option('with_gjs_symbols', type: 'boolean')
option('with_gnome_code_assistance', type: 'boolean')
option('with_history', type: 'boolean')
option('with_html_completion', type: 'boolean')
diff --git a/plugins/gjs-symbols/gjs_symbols.plugin b/plugins/gjs-symbols/gjs_symbols.plugin
new file mode 100644
index 0000000..e014098
--- /dev/null
+++ b/plugins/gjs-symbols/gjs_symbols.plugin
@@ -0,0 +1,10 @@
+[Plugin]
+Module=gjs_symbols
+Loader=python3
+Name=GJS Symbol Resolver
+Description=Provides a symbol resolver for JavaScript using GJS.
+Authors=Patrick Griffis <tingping tingping se>
+Copyright=Copyright © 2017 Patrick Griffis
+Builtin=true
+X-Symbol-Resolver-Languages=js
+X-Symbol-Resolver-Languages-Priority=0
diff --git a/plugins/gjs-symbols/gjs_symbols.py b/plugins/gjs-symbols/gjs_symbols.py
new file mode 100644
index 0000000..e631883
--- /dev/null
+++ b/plugins/gjs-symbols/gjs_symbols.py
@@ -0,0 +1,248 @@
+import gi
+import json
+import threading
+
+gi.require_versions({
+ 'Ide': '1.0',
+})
+
+from gi.repository import (
+ GLib,
+ GObject,
+ Gio,
+ Ide,
+)
+
+
+SYMBOL_PARAM_FLAGS=flags = GObject.ParamFlags.CONSTRUCT_ONLY | GObject.ParamFlags.READWRITE
+
+
+class JsSymbolNode(Ide.SymbolNode):
+ file = GObject.Property(type=Ide.File, flags=SYMBOL_PARAM_FLAGS)
+ line = GObject.Property(type=int, flags=SYMBOL_PARAM_FLAGS)
+ col = GObject.Property(type=int, flags=SYMBOL_PARAM_FLAGS)
+
+ def __init__(self, children, **kwargs):
+ super().__init__(**kwargs)
+ assert self.file is not None
+ self.children = children
+
+ def do_get_location_async(self, cancellable, callback, user_data=None):
+ task = Gio.Task.new(self, cancellable, callback)
+ task.return_boolean(True)
+
+ def do_get_location_finish(self, result):
+ if result.propagate_boolean():
+ return Ide.SourceLocation.new(self.file, self.line, self.col, 0)
+
+ def __len__(self):
+ return len(self.children)
+
+ def __getitem__(self, key):
+ return self.children[key]
+
+ def __iter__(self):
+ return self.children
+
+ def __repr__(self):
+ return '<JsSymbolNode {} ({})'.format(self.props.name, self.props.kind)
+
+
+class JsSymbolTree(GObject.Object, Ide.SymbolTree):
+ def __init__(self, dict_, file_):
+ super().__init__()
+ # with open('dump.json', 'w+') as f:
+ # f.write(json.dumps(dict_, indent=3))
+ self.root_node = self._node_from_dict(dict_, file_)
+
+ # For now lets try to extract only a small number of useful symbols:
+ # - global properties, functions, classes, and gobject classes
+ # - methods to those classes
+ # This will be expanded upon as time goes on.
+ @staticmethod
+ def _node_from_dict(dict_, file_):
+ line = max(dict_['loc']['start']['line'] - 1, 0)
+ col = dict_['loc']['start']['column']
+
+ # FIXME: Recursion is bad in Python, I know..
+ type_ = dict_['type']
+ if type_ == 'Program':
+ children = JsSymbolTree._nodes_from_list(dict_['body'], file_)
+ return JsSymbolNode(children, line=line, col=col,
+ kind=Ide.SymbolKind.PACKAGE,
+ name=dict_['loc']['source'],
+ file=file_)
+ elif type_ == 'FunctionDeclaration':
+ return JsSymbolNode([], line=line, col=col,
+ kind=Ide.SymbolKind.FUNCTION,
+ name=dict_['id']['name'],
+ file=file_)
+ elif type_ == 'VariableDeclaration':
+ decs = []
+ for dec in dict_['declarations']:
+ line = max(dec['id']['loc']['start']['line'] - 1, 0)
+ col = dec['id']['loc']['start']['column']
+ name = dec['id']['name']
+ kind = Ide.SymbolKind.VARIABLE
+ children = []
+
+ if JsSymbolTree._is_module_import(dec):
+ kind = Ide.SymbolKind.MODULE
+ elif JsSymbolTree._is_gobject_class(dec):
+ for arg in dec['init']['arguments']:
+ if arg['type'] == 'ClassExpression':
+ line = max(arg['id']['loc']['start']['line'] - 1, 0)
+ col = arg['id']['loc']['start']['column']
+ kind = Ide.SymbolKind.CLASS
+ children = JsSymbolTree._nodes_from_list(arg['body'], file_)
+ name = arg['id']['name']
+ break
+ elif dict_.get('kind', None) == 'const':
+ kind = Ide.SymbolKind.CONSTANT
+ decs.append(JsSymbolNode(children, line=line, col=col,
+ kind=kind, name=name, file=file_))
+ return decs
+ elif type_ == 'ClassStatement':
+ children = JsSymbolTree._nodes_from_list(dict_['body'], file_)
+ return JsSymbolNode(children, line=line, col=col,
+ kind=Ide.SymbolKind.CLASS,
+ name=dict_['id']['name'],
+ file=file_)
+ elif type_ == 'ClassMethod':
+ name = dict_['name']['name']
+ if name == 'constructed':
+ return None
+ return JsSymbolNode([], line=line, col=col,
+ kind=Ide.SymbolKind.METHOD,
+ name=name,
+ file=file_)
+ else:
+ return None
+
+ @staticmethod
+ def _is_module_import(dict_):
+ try:
+ return dict_['init']['object']['name'] == 'imports'
+ except KeyError:
+ return False
+
+ @staticmethod
+ def _is_gobject_class(dict_):
+ try:
+ callee = dict_['init']['callee']
+ return callee['object']['name'].lower() == 'gobject' and callee['property']['name'] ==
'registerClass'
+ except KeyError:
+ return False
+
+ def _nodes_from_list(list_, file_):
+ nodes = []
+ for i in list_:
+ node = JsSymbolTree._node_from_dict(i, file_)
+ if node is not None:
+ if isinstance(node, list):
+ nodes += node
+ else:
+ nodes.append(node)
+ return nodes
+
+ def do_get_n_children(self, node):
+ return len(node) if node is not None else len(self.root_node)
+
+ def do_get_nth_child(self, node, nth):
+ return node[nth] if node is not None else self.root_node[nth]
+
+
+JS_SCRIPT = \
+"""var data;
+if (ARGV[0] === '--file') {
+ const GLib = imports.gi.GLib;
+ var ret = GLib.file_get_contents(ARGV[1]);
+ data = ret[1];
+} else {
+ data = ARGV[0];
+}
+print(JSON.stringify(Reflect.parse(data, {source: '%s'})));""".replace('\n', ' ')
+
+
+class GjsSymbolProvider(Ide.Object, Ide.SymbolResolver):
+ def __init__(self):
+ super().__init__()
+
+ def _get_launcher(self, file_):
+ context = self.get_context()
+
+ file_path = file_.get_path()
+ script = JS_SCRIPT %file_path
+ unsaved_file = context.get_unsaved_files().get_unsaved_file(file_)
+
+ pipeline = context.get_build_manager().get_pipeline()
+ runtime = pipeline.get_configuration().get_runtime()
+
+ launcher = runtime.create_launcher()
+ launcher.set_flags(Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE)
+ launcher.push_args(('gjs', '-c', script))
+ if unsaved_file is not None:
+ launcher.push_argv(unsaved_file.get_content().get_data().decode('utf-8'))
+ else:
+ launcher.push_args(('--file', file_path))
+ return launcher
+
+ def do_lookup_symbol_async(self, location, cancellable, callback, user_data=None):
+ task = Gio.Task.new(self, cancellable, callback)
+ task.return_boolean(False) # Not implemented
+
+ def do_lookup_symbol_finish(self, result):
+ result.propagate_boolean()
+ return None
+
+ def do_get_symbol_tree_async(self, file_, buffer_, cancellable, callback, user_data=None):
+ task = Gio.Task.new(self, cancellable, callback)
+ launcher = self._get_launcher(file_)
+
+ threading.Thread(target=self._get_tree_thread, args=(task, launcher, file_),
+ name='gjs-symbols-thread').start()
+
+ def _get_tree_thread(self, task, launcher, file_):
+ try:
+ proc = launcher.spawn()
+ success, stdout, stderr = proc.communicate_utf8(None, None)
+
+ if not success:
+ task.return_boolean(False)
+ return
+
+ ide_file = Ide.File(file=file_, context=self.get_context())
+ task.symbol_tree = JsSymbolTree(json.loads(stdout), ide_file)
+ except GLib.Error as err:
+ task.return_error(err)
+ except (json.JSONDecodeError, UnicodeDecodeError) as e:
+ task.return_error(GLib.Error('Failed to decode gjs json: {}'.format(e)))
+ except (IndexError, KeyError) as e:
+ task.return_error(GLib.Error('Failed to extract information from ast: {}'.format(e)))
+ else:
+ task.return_boolean(True)
+
+ def do_get_symbol_tree_finish(self, result):
+ if result.propagate_boolean():
+ return result.symbol_tree
+
+ def do_load(self):
+ pass
+
+ def do_find_references_async(self, location, cancellable, callback, user_data=None):
+ task = Gio.Task.new(self, cancellable, callback)
+ task.return_boolean(False) # Not implemented
+
+ def do_find_references_finish(self, result):
+ result.propagate_boolean()
+ return None
+
+ def do_find_nearest_scope_async(self, location, cancellable, callback, user_data=None):
+ task = Gio.Task.new(self, cancellable, callback)
+ task.return_boolean(False) # Not implemented
+
+ def do_find_nearest_scope_finish(self, result):
+ result.propagate_boolean()
+ return None
+
+
diff --git a/plugins/gjs-symbols/meson.build b/plugins/gjs-symbols/meson.build
new file mode 100644
index 0000000..9a8759a
--- /dev/null
+++ b/plugins/gjs-symbols/meson.build
@@ -0,0 +1,13 @@
+if get_option('with_gjs_symbols')
+
+install_data('gjs_symbols.py', install_dir: plugindir)
+
+configure_file(
+ input: 'gjs_symbols.plugin',
+ output: 'gjs_symbols.plugin',
+ configuration: configuration_data(),
+ install: true,
+ install_dir: plugindir,
+)
+
+endif
diff --git a/plugins/meson.build b/plugins/meson.build
index 320fb02..54da7e1 100644
--- a/plugins/meson.build
+++ b/plugins/meson.build
@@ -34,6 +34,7 @@ subdir('gcc')
subdir('gdb')
subdir('gettext')
subdir('git')
+subdir('gjs-symbols')
subdir('gnome-code-assistance')
subdir('history')
subdir('html-completion')
@@ -90,6 +91,7 @@ status += [
'GDB ................... : @0@'.format(get_option('with_gdb')),
'Gettext ............... : @0@'.format(get_option('with_gettext')),
'Git ................... : @0@'.format(get_option('with_git')),
+ 'GJS Symbol Resolver ... : @0@'.format(get_option('with_gjs_symbols')),
'GNOME Code Assistance . : @0@'.format(get_option('with_gnome_code_assistance')),
'History ............... : @0@'.format(get_option('with_history')),
'HTML Completion ....... : @0@'.format(get_option('with_html_completion')),
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]