[anjuta] Completion support for GtkBuilder objects
- From: Johannes Schmid <jhs src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [anjuta] Completion support for GtkBuilder objects
- Date: Mon, 13 Jun 2011 16:22:37 +0000 (UTC)
commit c07aaad06aeb4e4e75fd5d2e76978a113f43c254
Author: Jason Siefken <siefkenj gmail com>
Date: Sun Jun 5 00:22:32 2011 -0700
Completion support for GtkBuilder objects
Adds support for autocompletion of objects in a
gtkBuilder ui file upon typing get_object('
.../anjuta-python-autocomplete.py | 179 +++++++++++++-------
plugins/language-support-python/plugin.c | 1 +
plugins/language-support-python/python-assist.c | 90 ++++++++--
plugins/language-support-python/python-assist.h | 1 +
4 files changed, 190 insertions(+), 81 deletions(-)
---
diff --git a/plugins/language-support-python/anjuta-python-autocomplete.py b/plugins/language-support-python/anjuta-python-autocomplete.py
index 34c0d92..7cefa2f 100755
--- a/plugins/language-support-python/anjuta-python-autocomplete.py
+++ b/plugins/language-support-python/anjuta-python-autocomplete.py
@@ -1,72 +1,127 @@
import getopt
import sys
+from collections import namedtuple
from rope.base.project import Project
from rope.contrib import codeassist
from rope.contrib import autoimport
-import os
-
-def pathsplit(p, rest=[]):
- (h,t) = os.path.split(p)
- if len(h) < 1: return [t]+rest
- if len(t) < 1: return [h]+rest
- return pathsplit(h,[t]+rest)
-
-def commonpath(l1, l2, common=[]):
- if len(l1) < 1: return (common, l1, l2)
- if len(l2) < 1: return (common, l1, l2)
- if l1[0] != l2[0]: return (common, l1, l2)
- return commonpath(l1[1:], l2[1:], common+[l1[0]])
-
-def relpath(p1, p2):
- (common,l1,l2) = commonpath(pathsplit(p1), pathsplit(p2))
- p = []
- if len(l1) > 0:
- p = [ '../' * len(l1) ]
- p = p + l2
- return os.path.join( *p )
-
-
-options, remainder = getopt.getopt(sys.argv[1:], 'o:p:s:r:f:')
-
-for opt, arg in options:
- if opt in ('-o', '--option'):
- option_arg = arg
- elif opt in ('-p', '--project'):
- project_arg = arg
- elif opt == '-s':
- source_code_arg = arg
- elif opt == '-r':
- res_arg = arg
- elif opt == '-f':
- offset_arg = arg
-
-option = option_arg;
-projectpath = project_arg
-if projectpath.startswith("file://"):
- projectpath = projectpath.replace("file://", "")
-
-proj = Project(projectpath)
-proj.pycore._init_python_files()
-
-input = open(source_code_arg, 'r')
-source_code = input.read()
-respath = relpath(projectpath, res_arg)
-res = proj.get_resource(respath)
-
-position = int(offset_arg)
-
-try:
- if option == "autocomplete":
- proposals = codeassist.code_assist(proj, source_code, position, resource=res, maxfixes=10)
+import os, re
+
+BUILDER_EXTENSION = '.ui'
+
+CompletionItem = namedtuple('CompletionItem', 'name info type scope location')
+def new_completion_item(**i):
+ return CompletionItem('_','_','_','_','_')._replace(**i)
+
+class BuilderComplete(object):
+ def __init__(self, project_path, resource_path, source_code, code_point, project_files):
+ self.code_point = code_point
+ self.src_dir = os.path.join(project_path, os.path.dirname(resource_path))
+ self.source_code = source_code
+ #grab all of the ui files from the project source files
+ self.builder_files = [f for f in project_files if f.endswith(BUILDER_EXTENSION) and os.path.exists(f)]
+
+ #suggest completions whenever someone types get_object(' in some form
+ self.should_autocompelte_re = re.compile(r'get_object\s*\(\s*[\'"](\w*)$')
+ def get_proposals(self, skip_verify=False):
+ """ return possible completions based on objects in ui files.
+ skip_verify=False will check to make sure preceeding the
+ current cursor position is something of the form 'get_object("' """
+ starting_word = ""
+ if skip_verify is False:
+ curr_line = self.source_code[:self.code_point].split('\n')[-1]
+ reg_search = self.should_autocompelte_re.search(curr_line)
+ if not reg_search:
+ return []
+ starting_word = reg_search.groups()[0]
+
+ possible_completions = self.get_all_builder_objects(self.builder_files)
+ #return only the ones with valid start
+ return [item for item in possible_completions if item.name.startswith(starting_word)]
+
+ def get_all_builder_objects(self, file_list):
+ """ go through every file in file_list and extract all <object/>
+ tags and return their ids """
+ from xml.dom import minidom
+ ret = []
+ for f in file_list:
+ md = minidom.parse(f)
+ for e in md.getElementsByTagName('object'):
+ item = new_completion_item(name=e.getAttribute('id'), scope='external', location=f, type='builder_object')
+ ret.append(item)
+ return ret
+
+class RopeComplete(object):
+ def __init__(self, project_path, source_code, resource_path, code_point):
+ self.project = Project(project_path)
+ self.project.pycore._init_python_files()
+
+ self.resource = self.project.get_resource(resource_path)
+ self.source_code = source_code
+ self.code_point = code_point
+
+ def __del__(self):
+ self.project.close()
+
+ def get_proposals(self):
+ ret = []
+ proposals = codeassist.code_assist(self.project, self.source_code, self.code_point, resource=self.resource, maxfixes=10)
proposals = codeassist.sorted_proposals(proposals)
for proposal in proposals:
- print proposal
+ ret.append(new_completion_item(name=proposal.name, scope=proposal.scope, type=proposal.type))
+
+ return ret
+
+def parse_arguments(args):
+ """ Returns a dictionary containing all the parsed args
+ and a string containing the source_code """
+ ret = {}
+
+ options, remainder = getopt.getopt(args, 'o:p:s:r:f:b:')
+ for opt, arg in options:
+ if opt in ('-o', '--option'):
+ option_arg = arg
+ elif opt in ('-p', '--project'):
+ project_arg = arg
+ elif opt == '-s':
+ source_code_arg = arg
+ elif opt == '-r':
+ res_arg = arg
+ elif opt == '-f':
+ offset_arg = arg
+ elif opt == '-b':
+ builder_files_arg = arg
+
+ ret['option'] = option_arg;
+ ret['project_path'] = str.replace(project_arg, 'file://', '') if project_arg.startswith('file://') else project_arg
+ ret['resource_path'] = os.path.relpath(res_arg, ret['project_path'])
+ ret['project_files'] = builder_files_arg.split('|')
+ ret['position'] = int(offset_arg)
+
+ input = open(source_code_arg, 'r')
+ ret['source_code'] = input.read()
+
+
+ return ret
+
+if __name__ == '__main__':
+ try:
+ args = parse_arguments(sys.argv[1:])
+
+ suggestions = []
+ if args['option'] == 'autocomplete':
+ #get any completions Rope offers us
+ comp = RopeComplete(args['project_path'], args['source_code'], args['resource_path'], args['position'])
+ suggestions.extend(comp.get_proposals())
+ #see if we've typed get_object(' and if so, offer completions based upon the builder ui files in the project
+ comp = BuilderComplete(args['project_path'], args['resource_path'], args['source_code'], args['position'], args['project_files'])
+ suggestions.extend(comp.get_proposals())
+ elif args['option'] == 'calltip':
+ proposals = codeassist.get_doc(proj, source_code, position, resource=res, maxfixes=10)
+
+ for s in suggestions:
+ print "|{0}|{1}|{2}|{3}|{4}|".format(s.name, s.scope, s.type, s.location, s.info)
+ except:
+ pass
- elif option == "calltip":
- proposals = codeassist.get_doc(proj, source_code, position, resource=res, maxfixes=10)
- print proposals
-except:
- pass
-proj.close()
diff --git a/plugins/language-support-python/plugin.c b/plugins/language-support-python/plugin.c
index b7f535e..7007af9 100644
--- a/plugins/language-support-python/plugin.c
+++ b/plugins/language-support-python/plugin.c
@@ -321,6 +321,7 @@ install_support (PythonPlugin *lang_plugin)
lang_plugin->assist = python_assist_new (iassist,
sym_manager,
docman,
+ plugin,
lang_plugin->settings,
editor_filename,
project_root);
diff --git a/plugins/language-support-python/python-assist.c b/plugins/language-support-python/python-assist.c
index 829270c..aeb07da 100644
--- a/plugins/language-support-python/python-assist.c
+++ b/plugins/language-support-python/python-assist.c
@@ -29,6 +29,7 @@
#include <libanjuta/anjuta-debug.h>
#include <libanjuta/anjuta-launcher.h>
#include <libanjuta/interfaces/ianjuta-file.h>
+#include <libanjuta/interfaces/ianjuta-editor.h>
#include <libanjuta/interfaces/ianjuta-editor-cell.h>
#include <libanjuta/interfaces/ianjuta-editor-selection.h>
#include <libanjuta/interfaces/ianjuta-editor-tip.h>
@@ -37,6 +38,7 @@
#include <libanjuta/interfaces/ianjuta-symbol.h>
#include <libanjuta/interfaces/ianjuta-document-manager.h>
#include <libanjuta/interfaces/ianjuta-project-manager.h>
+#include <libanjuta/anjuta-plugin.h>
#include "python-assist.h"
#include "python-utils.h"
@@ -51,6 +53,9 @@
#define AUTOCOMPLETE_SCRIPT SCRIPTS_DIR"/anjuta-python-autocomplete.py"
+#define AUTOCOMPLETE_REGEX_IN_GET_OBJECT "get_object\\s*\\(\\s*['\"]\\w*$"
+#define FILE_LIST_DELIMITER "|"
+
static void python_assist_iface_init(IAnjutaProviderIface* iface);
@@ -64,6 +69,7 @@ G_DEFINE_TYPE_WITH_CODE (PythonAssist,
typedef struct
{
gchar *name;
+ gchar *info;
gboolean is_func;
IAnjutaSymbolType type;
} PythonAssistTag;
@@ -77,6 +83,7 @@ struct _PythonAssistPriv {
IAnjutaEditor* editor;
AnjutaLauncher* launcher;
AnjutaLauncher* calltip_launcher;
+ AnjutaPlugin* plugin;
const gchar* project_root;
const gchar* editor_filename;
@@ -140,8 +147,8 @@ is_scope_context_character (gchar ch)
static gchar*
python_assist_get_scope_context (IAnjutaEditor* editor,
- const gchar *scope_operator,
- IAnjutaIterable *iter)
+ const gchar *scope_operator,
+ IAnjutaIterable *iter)
{
IAnjutaIterable* end;
gchar ch, *scope_chars = NULL;
@@ -298,7 +305,7 @@ static void
python_assist_update_autocomplete (PythonAssist *assist)
{
GList *node, *suggestions = NULL;
- GList* completion_list = g_completion_complete (assist->priv->completion_cache, assist->priv->pre_word, NULL);
+ GList *completion_list = g_completion_complete (assist->priv->completion_cache, assist->priv->pre_word, NULL);
for (node = completion_list; node != NULL; node = g_list_next (node))
{
@@ -309,12 +316,14 @@ python_assist_update_autocomplete (PythonAssist *assist)
proposal->label = g_strdup_printf ("%s()", tag->name);
else
proposal->label = g_strdup(tag->name);
-
+
+ if (tag->info)
+ proposal->info = g_strdup(tag->info);
proposal->data = tag;
suggestions = g_list_prepend (suggestions, proposal);
}
suggestions = g_list_reverse (suggestions);
- /* Hide is the only suggetions is exactly the typed word */
+ /* Hide if the only suggetions is exactly the typed word */
if (!(g_list_length (suggestions) == 1 &&
g_str_equal (((PythonAssistTag*)(suggestions->data))->name, assist->priv->pre_word)))
{
@@ -399,7 +408,7 @@ on_autocomplete_finished (AnjutaLauncher* launcher,
GStrv cur_comp;
GList* suggestions = NULL;
GError *err = NULL;
- GRegex* regex = g_regex_new ("(\\w+) \\((\\w+), (\\w+)\\)",
+ GRegex* regex = g_regex_new ("\\|(.+)\\|(.+)\\|(.+)\\|(.+)\\|(.+)\\|",
0, 0, &err);
if (err)
{
@@ -416,20 +425,36 @@ on_autocomplete_finished (AnjutaLauncher* launcher,
g_regex_match (regex, *cur_comp, 0, &match_info);
if (g_match_info_matches (match_info) &&
- g_match_info_get_match_count (match_info) == 4)
+ g_match_info_get_match_count (match_info) == 6)
{
gchar* type = g_match_info_fetch (match_info, 3);
+ gchar* location = g_match_info_fetch (match_info, 4);
+ gchar* info = g_match_info_fetch (match_info, 5);
tag = g_new0 (PythonAssistTag, 1);
tag->name = g_match_info_fetch (match_info, 1);
+ /* info will be set to "_" if there is no relevant info */
+ tag->info = NULL;
+ if (!g_str_equal(info, "_"))
+ tag->info = g_strdup(info);
+
+
if (g_str_equal(type, "function") || g_str_equal (type, "builtin"))
{
tag->type = IANJUTA_SYMBOL_TYPE_FUNCTION;
tag->is_func = TRUE;
}
+ else if (g_str_equal(type, "builder_object"))
+ {
+ tag->type = IANJUTA_SYMBOL_TYPE_EXTERNVAR;
+ if (!g_str_equal(location, "_"))
+ tag->info = g_strdup(location);
+ }
else
tag->type = IANJUTA_SYMBOL_TYPE_VARIABLE;
g_free (type);
+ g_free (info);
+ g_free (location);
if (!g_list_find_custom (suggestions, tag, completion_compare))
{
@@ -465,6 +490,8 @@ python_assist_create_word_completion_cache (PythonAssist *assist, IAnjutaIterabl
const gchar *project = assist->priv->project_root;
gchar *interpreter_path;
gchar *ropecommand;
+ GString *builder_file_paths = g_string_new("");
+ GList *project_files_list, *node;
gchar *source = ianjuta_editor_get_text_all (editor, NULL);
gchar *tmp_file;
@@ -482,13 +509,33 @@ python_assist_create_word_completion_cache (PythonAssist *assist, IAnjutaIterabl
if (!tmp_file)
return FALSE;
+
+ /* Get a list of all the builder files in the project */
+ IAnjutaProjectManager *manager = anjuta_shell_get_interface (ANJUTA_PLUGIN (assist->priv->plugin)->shell,
+ IAnjutaProjectManager,
+ NULL);
+ project_files_list = ianjuta_project_manager_get_elements (IANJUTA_PROJECT_MANAGER (manager),
+ ANJUTA_PROJECT_SOURCE,
+ NULL);
+ for (node = project_files_list; node != NULL; node = g_list_next (node))
+ {
+ gchar *file_path = g_file_get_path (node->data);
+ builder_file_paths = g_string_append (builder_file_paths, FILE_LIST_DELIMITER);
+ builder_file_paths = g_string_append (builder_file_paths, file_path);
+ g_free (file_path);
+ g_object_unref (node->data);
+ }
+ g_list_free (project_files_list);
- ropecommand = g_strdup_printf("%s %s -o autocomplete -p \"%s\" -r \"%s\" -s \"%s\" -f %d",
+ ropecommand = g_strdup_printf("%s %s -o autocomplete -p \"%s\" -r \"%s\" -s \"%s\" -f %d -b \"%s\"",
interpreter_path, AUTOCOMPLETE_SCRIPT, project,
- cur_filename, tmp_file, offset);
+ cur_filename, tmp_file, offset, builder_file_paths->str);
+ g_string_free (builder_file_paths, TRUE);
g_free (tmp_file);
+ DEBUG_PRINT("%s", ropecommand);
+
/* Exec command and wait for results */
assist->priv->launcher = anjuta_launcher_new ();
g_signal_connect (assist->priv->launcher, "child-exited",
@@ -763,8 +810,9 @@ python_assist_none (IAnjutaProvider* self,
NULL, TRUE, NULL);
}
+/* returns TRUE if a '.', "'", or '"' preceeds the cursor position */
static gint
-python_assist_dot (IAnjutaEditor* editor,
+python_assist_completion_trigger_char (IAnjutaEditor* editor,
IAnjutaIterable* cursor)
{
IAnjutaIterable* iter = ianjuta_iterable_clone (cursor, NULL);
@@ -774,12 +822,11 @@ python_assist_dot (IAnjutaEditor* editor,
{
gchar c = ianjuta_editor_cell_get_char (IANJUTA_EDITOR_CELL (iter),
0, NULL);
- retval = (c == '.');
+ retval = ((c == '.') || (c == '\'') || (c == '"'));
}
g_object_unref (iter);
return retval;
}
-
static void
python_assist_populate (IAnjutaProvider* self, IAnjutaIterable* cursor, GError** e)
@@ -787,7 +834,7 @@ python_assist_populate (IAnjutaProvider* self, IAnjutaIterable* cursor, GError**
PythonAssist* assist = PYTHON_ASSIST (self);
IAnjutaIterable* start_iter = NULL;
gchar* pre_word;
- gboolean dot;
+ gboolean completion_trigger_char;
/* Check for calltip */
if (assist->priv->itip &&
@@ -808,8 +855,7 @@ python_assist_populate (IAnjutaProvider* self, IAnjutaIterable* cursor, GError**
/* Check if this is a valid text region for completion */
IAnjutaEditorAttribute attrib = ianjuta_editor_cell_get_attribute (IANJUTA_EDITOR_CELL(cursor),
NULL);
- if (attrib == IANJUTA_EDITOR_STRING ||
- attrib == IANJUTA_EDITOR_COMMENT)
+ if (attrib == IANJUTA_EDITOR_COMMENT)
{
python_assist_none (self, assist);
return;
@@ -841,10 +887,14 @@ python_assist_populate (IAnjutaProvider* self, IAnjutaIterable* cursor, GError**
DEBUG_PRINT ("Cancelling autocomplete");
python_assist_destroy_completion_cache (assist);
}
- dot = python_assist_dot (IANJUTA_EDITOR (assist->priv->iassist),
+
+ /* Autocompletion should not be triggered if we haven't started typing a word unless
+ * we just typed . or ' or "
+ */
+ completion_trigger_char = python_assist_completion_trigger_char (IANJUTA_EDITOR (assist->priv->iassist),
cursor);
- if (((pre_word && strlen (pre_word) >= 3) || dot) &&
- python_assist_create_word_completion_cache (assist, cursor))
+ if ( (( (pre_word && strlen (pre_word) >= 3) || completion_trigger_char ) &&
+ python_assist_create_word_completion_cache (assist, cursor)) )
{
DEBUG_PRINT ("New autocomplete for %s", pre_word);
if (assist->priv->start_iter)
@@ -1003,6 +1053,7 @@ PythonAssist *
python_assist_new (IAnjutaEditorAssist *iassist,
IAnjutaSymbolManager *isymbol_manager,
IAnjutaDocumentManager *idocument_manager,
+ AnjutaPlugin *plugin,
GSettings* settings,
const gchar *editor_filename,
const gchar *project_root)
@@ -1013,7 +1064,8 @@ python_assist_new (IAnjutaEditorAssist *iassist,
assist->priv->editor_filename = editor_filename;
assist->priv->settings = settings;
assist->priv->project_root = project_root;
- assist->priv->editor=(IAnjutaEditor*)iassist;
+ assist->priv->editor = (IAnjutaEditor*)iassist;
+ assist->priv->plugin = plugin;
python_assist_install (assist, IANJUTA_EDITOR (iassist));
return assist;
}
diff --git a/plugins/language-support-python/python-assist.h b/plugins/language-support-python/python-assist.h
index b201d77..78f5612 100644
--- a/plugins/language-support-python/python-assist.h
+++ b/plugins/language-support-python/python-assist.h
@@ -66,6 +66,7 @@ GType python_assist_get_type (void) G_GNUC_CONST;
PythonAssist *python_assist_new (IAnjutaEditorAssist *assist,
IAnjutaSymbolManager *isymbol_manager,
IAnjutaDocumentManager *idocument_manager,
+ AnjutaPlugin *plugin,
GSettings* settings,
const gchar *editor_filename,
const gchar *project_root);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]