[gnome-builder] gjs-symbols: Implement code indexer and various improvements



commit e601b5eba12aa1e65f850caaf44c5faab53b6cd0
Author: Patrick Griffis <tingping tingping se>
Date:   Mon Sep 11 19:52:40 2017 -0400

    gjs-symbols: Implement code indexer and various improvements

 plugins/gjs-symbols/gjs_symbols.plugin |    2 +
 plugins/gjs-symbols/gjs_symbols.py     |  137 +++++++++++++++++++++++++++++---
 2 files changed, 126 insertions(+), 13 deletions(-)
---
diff --git a/plugins/gjs-symbols/gjs_symbols.plugin b/plugins/gjs-symbols/gjs_symbols.plugin
index e014098..7384d57 100644
--- a/plugins/gjs-symbols/gjs_symbols.plugin
+++ b/plugins/gjs-symbols/gjs_symbols.plugin
@@ -8,3 +8,5 @@ Copyright=Copyright © 2017 Patrick Griffis
 Builtin=true
 X-Symbol-Resolver-Languages=js
 X-Symbol-Resolver-Languages-Priority=0
+X-Code-Indexer-Languages=js
+X-Code-Indexer-Languages-Priority=0
diff --git a/plugins/gjs-symbols/gjs_symbols.py b/plugins/gjs-symbols/gjs_symbols.py
index e631883..c6b6495 100644
--- a/plugins/gjs-symbols/gjs_symbols.py
+++ b/plugins/gjs-symbols/gjs_symbols.py
@@ -1,4 +1,5 @@
 import gi
+import os
 import json
 import threading
 
@@ -38,11 +39,14 @@ class JsSymbolNode(Ide.SymbolNode):
     def __len__(self):
         return len(self.children)
 
+    def __bool__(self):
+        return True
+
     def __getitem__(self, key):
         return self.children[key]
 
     def __iter__(self):
-        return self.children
+        return iter(self.children)
 
     def __repr__(self):
         return '<JsSymbolNode {} ({})'.format(self.props.name, self.props.kind)
@@ -51,7 +55,8 @@ class JsSymbolNode(Ide.SymbolNode):
 class JsSymbolTree(GObject.Object, Ide.SymbolTree):
     def __init__(self, dict_, file_):
         super().__init__()
-        # with open('dump.json', 'w+') as f:
+        # output = file_.get_path().replace('/', '_') + '.json'
+        # with open(output, 'w+') as f:
         #    f.write(json.dumps(dict_, indent=3))
         self.root_node = self._node_from_dict(dict_, file_)
 
@@ -66,7 +71,7 @@ class JsSymbolTree(GObject.Object, Ide.SymbolTree):
 
         # FIXME: Recursion is bad in Python, I know..
         type_ = dict_['type']
-        if type_ == 'Program':
+        if type_ == 'Program':  # Root node
             children = JsSymbolTree._nodes_from_list(dict_['body'], file_)
             return JsSymbolNode(children, line=line, col=col,
                                 kind=Ide.SymbolKind.PACKAGE,
@@ -77,6 +82,16 @@ class JsSymbolTree(GObject.Object, Ide.SymbolTree):
                                 kind=Ide.SymbolKind.FUNCTION,
                                 name=dict_['id']['name'],
                                 file=file_)
+        elif type_ == 'Property':  # Used for legacy GObject classes
+            if dict_['value']['type'] != 'FunctionExpression':
+                return None
+            name = dict_['key']['name']
+            if name == '_init':
+                return None
+            return JsSymbolNode([], line=line, col=col,
+                                kind=Ide.SymbolKind.METHOD,
+                                name=name,
+                                file=file_)
         elif type_ == 'VariableDeclaration':
             decs = []
             for dec in dict_['declarations']:
@@ -88,6 +103,8 @@ class JsSymbolTree(GObject.Object, Ide.SymbolTree):
 
                 if JsSymbolTree._is_module_import(dec):
                     kind = Ide.SymbolKind.MODULE
+                    # For now these just aren't useful
+                    continue
                 elif JsSymbolTree._is_gobject_class(dec):
                     for arg in dec['init']['arguments']:
                         if arg['type'] == 'ClassExpression':
@@ -97,8 +114,19 @@ class JsSymbolTree(GObject.Object, Ide.SymbolTree):
                             children = JsSymbolTree._nodes_from_list(arg['body'], file_)
                             name = arg['id']['name']
                             break
-                elif dict_.get('kind', None) == 'const':
-                    kind = Ide.SymbolKind.CONSTANT
+                elif JsSymbolTree._is_legacy_gobject_class(dec):
+                    kind = Ide.SymbolKind.CLASS
+                    try:
+                        arg = dec['init']['arguments'][0]
+                    except IndexError:
+                        continue
+
+                    if arg['type'] != 'ObjectExpression':
+                        continue
+
+                    children = JsSymbolTree._nodes_from_list(arg['properties'], file_)
+                # 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
@@ -110,7 +138,7 @@ class JsSymbolTree(GObject.Object, Ide.SymbolTree):
                                 file=file_)
         elif type_ == 'ClassMethod':
             name = dict_['name']['name']
-            if name == 'constructed':
+            if name in ('constructed', '_init'):
                 return None
             return JsSymbolNode([], line=line, col=col,
                                 kind=Ide.SymbolKind.METHOD,
@@ -123,8 +151,11 @@ class JsSymbolTree(GObject.Object, Ide.SymbolTree):
     def _is_module_import(dict_):
         try:
             return dict_['init']['object']['name'] == 'imports'
-        except KeyError:
-            return False
+        except (KeyError, TypeError):
+            try:
+                return dict_['init']['object']['object']['name'] == 'imports'
+            except (KeyError, TypeError):
+                return False
 
     @staticmethod
     def _is_gobject_class(dict_):
@@ -134,6 +165,15 @@ class JsSymbolTree(GObject.Object, Ide.SymbolTree):
         except KeyError:
             return False
 
+    @staticmethod
+    def _is_legacy_gobject_class(dict_):
+        try:
+            callee = dict_['init']['callee']
+            return callee['object']['name'].lower() in ('gobject', 'lang') and callee['property']['name'] == 
'Class'
+        except KeyError:
+            return False
+
+    @staticmethod
     def _nodes_from_list(list_, file_):
         nodes = []
         for i in list_:
@@ -168,9 +208,8 @@ class GjsSymbolProvider(Ide.Object, Ide.SymbolResolver):
     def __init__(self):
         super().__init__()
 
-    def _get_launcher(self, file_):
-        context = self.get_context()
-
+    @staticmethod
+    def _get_launcher(context, file_):
         file_path = file_.get_path()
         script = JS_SCRIPT %file_path
         unsaved_file = context.get_unsaved_files().get_unsaved_file(file_)
@@ -179,7 +218,7 @@ class GjsSymbolProvider(Ide.Object, Ide.SymbolResolver):
         runtime = pipeline.get_configuration().get_runtime()
 
         launcher = runtime.create_launcher()
-        launcher.set_flags(Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE)
+        launcher.set_flags(Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_SILENCE)
         launcher.push_args(('gjs', '-c', script))
         if unsaved_file is not None:
             launcher.push_argv(unsaved_file.get_content().get_data().decode('utf-8'))
@@ -197,7 +236,7 @@ class GjsSymbolProvider(Ide.Object, Ide.SymbolResolver):
 
     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_)
+        launcher = self._get_launcher(self.get_context(), file_)
 
         threading.Thread(target=self._get_tree_thread, args=(task, launcher, file_),
                          name='gjs-symbols-thread').start()
@@ -246,3 +285,75 @@ class GjsSymbolProvider(Ide.Object, Ide.SymbolResolver):
         return None
 
 
+class JsCodeIndexEntries(GObject.Object, Ide.CodeIndexEntries):
+    def __init__(self, entries):
+        super().__init__()
+        self.entries = entries
+        self.entry_iter = None
+
+    def do_get_next_entry(self):
+        if self.entry_iter is None:
+            self.entry_iter = iter(self.entries)
+        try:
+            return next(self.entry_iter)
+        except StopIteration:
+            self.entry_iter = None
+            return None
+
+
+class GjsCodeIndexer(Ide.Object, Ide.CodeIndexer):
+    def __init__(self):
+        super().__init__()
+
+    @staticmethod
+    def _get_node_name(node):
+        prefix = {
+            Ide.SymbolKind.FUNCTION: 'f',
+            Ide.SymbolKind.METHOD: 'f',
+            Ide.SymbolKind.VARIABLE: 'v',
+            Ide.SymbolKind.CONSTANT: 'v',
+            Ide.SymbolKind.CLASS: 'c',
+        }.get(node.props.kind, 'x')
+        return prefix + '\x1F' + node.props.name
+
+    def do_index_file(self, file_, build_flags, cancellable):
+        if 'node_modules' in file_.get_path().split(os.sep):
+            return None  # Avoid indexing these
+
+        launcher = GjsSymbolProvider._get_launcher(self.get_context(), file_)
+        proc = launcher.spawn()
+        success, stdout, stderr = proc.communicate_utf8(None, None)
+
+        if not success:
+            return None
+
+        ide_file = Ide.File(file=file_, context=self.get_context())
+        try:
+            root_node = JsSymbolTree._node_from_dict(json.loads(stdout), ide_file)
+        except (json.JSONDecodeError, UnicodeDecodeError) as e:
+            raise GLib.Error('Failed to decode gjs json: {}'.format(e))
+        except (IndexError, KeyError) as e:
+            raise GLib.Error('Failed to extract information from ast: {}'.format(e))
+
+        entries = []
+        # TODO: Avoid recreating the same data
+        for node in root_node:
+            entry = Ide.CodeIndexEntry(
+                key=node.props.file.get_path() + '|' + node.props.name, # Some unique value..
+                name=self._get_node_name(node),
+                kind=node.props.kind,
+                flags=node.props.flags,
+                begin_line=node.props.line,
+                begin_line_offset=node.props.col,
+            )
+            entries.append(entry)
+        return JsCodeIndexEntries(entries)
+
+    def do_generate_key_async(self, location, cancellable, callback, user_data=None):
+        # print('generate key')
+        task = Gio.Task.new(self, cancellable, callback)
+        task.return_boolean(False)
+
+    def do_generate_key_finish(self, result):
+        if result.propagate_boolean():
+            return ''


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]