[anjuta] Completion support for GtkBuilder objects



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]