Commits

Ali Gholami Rudi  committed 224f4d1

Completing global names
Showing completion proposals in GraphicalEditor

  • Participants
  • Parent commits d0425ef

Comments (0)

Files changed (17)

 include COPYING
-recursive-include docs *.html
+recursive-include docs *.html *.txt
 include *.txt
 
 recursive-include rope *.py
-            rope : A Python IDE
+                         rope : A Python IDE
 
 Overview
 --------
 ----------------
   We've just started. Right now, rope is a basic editor. One of our goals
 was to develop rope using rope and for achieving this we've implemented
-the a basic editor as our first task. We've already started using rope. From
-now on we will move toward the promised features like auto-completion.  
+a basic editor as our first task. We've already started using rope. From
+now on we will move toward the promised features like auto-completion.
 
 
 Keybinding
 C-y            paste
 C-x C-s        save
 C-i            correct line indentation
+C-/            auto-complete
 
 
 Description
 specially in java community. In the agile programing methodologies, like
 Extreme Programing, Refactoring is one of the core practices.
 
-  As Martin Fowler the writer of the "Refactoring: Improving the design of 
-existing code" book says "Refactoring is the process of changing a software
-system in such a way that it does not alter the external behavior of the code
-yet improves its internal structure."
-
   Some IDEs support some basic refactorings like 'PyDev' (which uses bicycle
 repair man). These IDEs have a limited set of refactorings and fail
 when doing refactorings that need to know the type of objects in the
-source code (specially for relatively large projects). rope tries to provide a 
+source code (specially for relatively large projects). rope tries to provide a
 rich set of refactorings. Some of the refactorings require type
 inferencing which is described later.
 
 of objects. But type inferencing python programs is very hard.
 There have been some attempts though not very successful (examples:
 psycho: only str and int types, StarKiller: wasn't released and
-ShedSkin: very limited). They where mostly directed at speeding up
+ShedSkin: good but limited). They where mostly directed at speeding up
 python programs by transforming its code to other typed languages 
 rather than building IDEs. Such algorithms might be helpful.
 
-  There is another approache toward type inferencing. That is the analysis
+  There is another approach toward type inferencing. That is the analysis
 of running programs. This dynamic approach records the types variables are
 assigned to during the program execution. Although this approach is
 a lot easier to implement than the alternative, it is limited. Only the
 
 * Why an IDE and not a standalone library or program?
 
-  As Don Roberts one of the writers of the famous "Refactoring Browser" for
+  As Don Roberts one of the writers of the "Refactoring Browser" for
 smalltalk writes in his doctoral thesis:
   "An early implementation of the Refactoring Browser for Smalltalk was a separate
 tool from the standard Smalltalk development tools. What we found was that no

File docs/done.txt

+> Public release 0.1 : May 8, 2006
+
+
 - Separating indenting and correcting indentation : May 7, 2006
 
 

File docs/platform.txt

 *** Platform ***
-	* Project
-	* File local history
-	* Commanding buffer
-	* Plugin support and extensibility
-	* Team
-	* Global and project specific preferences
-	* Saving states between sessions
-	* Acceleration keys
-	* Profiling
-	* Debugging
-
+    * Project
+    * File local history
+    * Commanding buffer
+    * Plugin support and extensibility
+    * Team
+    * Global and project specific preferences
+    * Saving states between sessions
+    * Acceleration keys
+    * Profiling
+    * Debugging

File docs/platformui.txt

 *** PlatformUI ***
-	* Basic editing
-	* Syntax highlighting
-	* Multiple buffers
-	* Advanced search
-	* Different views
-	* Configuration editor
-	* Having multiple clipboards
-	* Adding status bar
+    * Basic editing
+    * Syntax highlighting
+    * Multiple buffers
+    * Advanced search
+    * Different views
+    * Configuration editor
+    * Having multiple clipboards
+    * Adding status bar

File docs/pythonsource.txt

 *** PythonSource ***
-	* Formatting code
-	* Basic refactorings
-	* Content assist
-	* Advanced refactorings
-	* Type inferencing
-	  * static
-	  * dynamic
-	  * ask user
-	* Auto completion
+    * Formatting code
+    * Basic refactorings
+    * Content assist
+    * Advanced refactorings
+    * Type inferencing
+        * static
+        * dynamic
+        * ask user
+    * Auto completion
 
 >>> Advanced Refactorings; Need type inferencing
 * Rename method
 * Add import
 * Format source
 
->>> Content Assists
+>>> Code Assists
 * Make module/class/function/method
 * Surround with try statement
 * Remove try statement

File docs/stories.txt

 * Open Type @ 2
 
 
+* Configuring keys @ 2
+
+
+* Configuring fonts @ 1
+
+
 * Showing syntactical errors @ 3
 
 
 * Subversion support using pysvn @ 3
 
 
-* Rename local variable @ 2
-
-
 * Rename function @ 2
 
 
 * Find matching parens @ 1
 
 
-* Rename module refactoring @ 2
-
-
 * Last edit location; C-q @ 2
 
 
 
 
 * Enhancing syntax highlighting @ 1
-  * Ignoring comment lines
   * Only highlighting the changed region
   * Extend highlighting region while highlighting if necessay
   * Highlighting after cut, paste, undo and redo
 
 
 * Enhancing auto indentation @ 1
+  * Ignoring comment lines
   * Implicit line joining: Indenting lines whose previous line contains a ([{'''
   * Indenting a range of file
   * Removing extra spaces
 
 
+* Rename local variable @ 2
+
+
+* Rename module refactoring @ 2
+
+
 * Formating Code @ 2
 
 
-* Writing ASTs @ 4
-
-
 * Better multi-sequence key event handling @ 2
 
 
-* Auto-complete variable names @ 3
-
-
 * Adding TextIterator or LineOriented to the Editor class @ 1
 
 
   * Showing modified file status
 
 
+* Writing ASTs @ 4
+
+
+* Auto-complete variable names @ 3
+
+
+* Making ASTs @ 4
+
+
 > Public release 0.2pre : May 20, 2006
 
 
 * Show searching status in the status bar @ 1
 
 
-* Making ASTs @ 4
-
-
-* Auto-complete module names @ 2
-
-
-> Public release 0.1 : May 6, 2006
+* Auto-complete module names @ 3
 
 
 --- Remaining Stories ---
-? Using SF SVN; Making a script for committing to SF
+? Change program goals and description; principles.html
+  ? Go toward refactoring and ... library rather than an IDE
+? roadmap.html
+? Should workingon.txt be under version control?
+? Functional tests
 ? Separate domain and presentation everywhere
 * Make tests less dependant
-? Changing used graphical interface
 ? Project resource consistency; What if it is deleted after being created.

File docs/workingon.txt

-- Tab should insert 4 spaces
+*** Auto-complete imports names @ 3 ***
 
+- Test only reporting only completions with the given starting
+- Showing completions in GraphicalEditor
 
-? SF repository; Subversion or CVS? Synchronizing local SVN with SF?
-? Functional tests
-? Should workingon.txt be under version control?
+* Extract listbox; duplicates in editor and core
+? Proposing a name at most once? Use dicts instead of lists
+* Test not proposing local assigns as global proposals
+* Test not proposing the variables in current line
+* What if current line has errors
+? The connection between ASTs and Type Hierarchies
+? Should Projects have ContentAssist objects?
+
+* Better font selection on windows
+? Think about a library for functional testing
+? SF repository; SVN or CVS? How to synchronize local SVN with SF?

File rope/codeassist.py

+import compiler
+
+class CompletionProposal(object):
+    global_variable = 'global_variable'
+
+    def __init__(self, completion, kind):
+        self.completion = completion
+        self.kind = kind
+
+
+class _GlobalVisitor(object):
+    
+    def __init__(self, starting):
+        self.starting = starting
+        self.result = []
+
+    def visitAssName(self, node):
+        if node.name.startswith(self.starting):
+            self.result.append(CompletionProposal(node.name, 'global_variable'))
+
+
+class ICodeAssist(object):
+    def complete_code(self, source, offset):
+        pass
+
+
+class NoAssist(ICodeAssist):
+    def complete_code(self, source_code, offset):
+        return []
+
+
+class CodeAssist(ICodeAssist):
+    def complete_code(self, source_code, offset):
+        if offset > len(source_code):
+            return []
+        starting = ''
+        current_offset = offset - 1
+        while current_offset >= 0 and (source_code[current_offset].isalnum() or
+                                       source_code[current_offset] == '_'):
+            starting = source_code[current_offset] + starting
+            current_offset -= 1;
+        result = []
+        code_ast = compiler.parse(source_code)
+        visitor = _GlobalVisitor(starting)
+        compiler.walk(code_ast, visitor)
+        result.extend(visitor.result)
+        return result

File rope/core.py

             if editor.get_file() == file_:
                 self.buttons[editor].invoke()
                 return editor
-        editor = FileEditor(file_, GraphicalEditor(self.editor_frame))
+        editor = FileEditor(Core.get_core().get_open_project(), file_,
+                            GraphicalEditor(self.editor_frame))
         self.editors.append(editor)
         title = Radiobutton(self.editor_list, text=file_.get_name(),
                             variable=self.active_file_path,
         def do_create_module(source_folder, module_name):
             new_module = self.project.create_module(source_folder,
                                                     module_name)
-            self.editor_manager.get_file_editor(new_module)
+            self.editor_manager.get_resource_editor(new_module)
         self._create_resource_dialog(do_create_module, 'Module', 'Source Folder')
         if event:
             return 'break'
         def do_create_package(source_folder, package_name):
             new_package = self.project.create_package(source_folder,
                                                       package_name)
-            self.editor_manager.get_file_editor(new_module)
+            self.editor_manager.get_resource_editor(new_module)
         self._create_resource_dialog(do_create_package, 'Package', 'Source Folder')
         if event:
             return 'break'
     def _create_new_file_dialog(self, event=None):
         def do_create_file(parent_folder, file_name):
             new_file = parent_folder.create_file(file_name)
-            self.editor_manager.get_file_editor(new_file)
+            self.editor_manager.get_resource_editor(new_file)
         self._create_resource_dialog(do_create_file, 'File', 'Parent Folder')
         if event:
             return 'break'

File rope/editor.py

 import rope.highlight
 import rope.searching
 import rope.indenter
+import rope.codeassist
 
 
 class TextEditor(object):
         self._initialize_highlighting()
         self.highlighting = rope.highlight.NoHighlighting()
         self.indenter = rope.indenter.NormalIndenter(self)
+        self.code_assist = rope.codeassist.NoAssist()
 
     def _initialize_highlighting(self):
         def colorize(event=None):
                        lambda event: self._search_event(False))
         self.text.bind('<Any-KeyPress>', self._search_handler)
         self.text.bind('<BackSpace>', backspace, '+')
+        self.text.bind('<Alt-slash>', lambda event: self._show_completion_window());
 
 
+    def _show_completion_window(self):
+        toplevel = Toplevel()
+        frame = Frame(toplevel)
+        label = Label(frame, text='Hello World')
+        proposals = Listbox(frame, selectmode=SINGLE, width=23, height=7)
+        scrollbar = Scrollbar(frame, orient=VERTICAL)
+        scrollbar['command'] = proposals.yview
+        proposals.config(yscrollcommand=scrollbar.set)
+        result = self.code_assist.complete_code(self.get_text(), self.get_current_offset())
+        for proposal in result:
+            proposals.insert(END, proposal.completion)
+        if result:
+            proposals.selection_set(0)
+        self.text.see('insert')
+        local_x, local_y, cx, cy = self.text.bbox("insert")
+        x = local_x + self.text.winfo_rootx() + 2
+        y = local_y + cy + self.text.winfo_rooty()
+        #        toplevel.wm_geometry('+%d+%d' % (x, y))
+        #        toplevel.wm_overrideredirect(1)
+        def open_selected():
+            selection = proposals.curselection()
+            if selection:
+                selected = proposals.get(selection[0])
+                # TODO: insert the completion
+                toplevel.destroy()
+        def cancel():
+            toplevel.destroy()
+        proposals.bind('<Return>', lambda event: open_selected())
+        proposals.bind('<Escape>', lambda event: cancel())
+        proposals.bind('<FocusOut>', lambda event: cancel())
+        def select_prev(event):
+            selection = proposals.curselection()
+            if selection:
+                active = int(selection[0])
+                if active - 1 >= 0:
+                    proposals.select_clear(0, END)
+                    proposals.selection_set(active - 1)
+                    proposals.see(active - 1)
+                    proposals.activate(active - 1)
+                    proposals.see(active - 1)
+        proposals.bind('<Control-p>', select_prev)
+        def select_next(event):
+            selection = proposals.curselection()
+            if selection:
+                active = int(selection[0])
+                if active + 1 < proposals.size():
+                    proposals.select_clear(0, END)
+                    proposals.selection_set(active + 1)
+                    proposals.see(active + 1)
+                    proposals.activate(active + 1)
+                    proposals.see(active + 1)
+        proposals.bind('<Control-n>', select_next)
+        label.grid(row=0, column=0, columnspan=2)
+        proposals.grid(row=1, column=0, sticky=N+E+W+S)
+        scrollbar.grid(row=1, column=1, sticky=N+E+W+S)
+        frame.grid(sticky=N+E+W+S)
+        proposals.focus_set()
+        toplevel.grab_set()
+
     def get_text(self):
         return self.text.get('1.0', 'end-1c')
 
     def set_indenter(self, text_indenter):
         self.indenter = text_indenter
 
+    def set_code_assist(self, code_assist):
+        self.code_assist = code_assist
+
     def get_indenter(self):
         return self.indenter
 
     def get_current_line_number(self):
         return self._get_line_from_index(INSERT)
 
+    def get_current_offset(self):
+        result = self._get_column_from_index(INSERT)
+        current_line = self._get_line_from_index(INSERT)
+        current_pos = '1.0 lineend'
+        for x in range(current_line - 1):
+            result += self._get_column_from_index(current_pos) + 1
+            current_pos = self.text.index(current_pos + ' +1l lineend')
+        return result
+
 
 class GraphicalTextIndex(TextIndex):
     '''An immutable class for pointing to a position in a text'''

File rope/fileeditor.py

 import rope.indenter
 
 class FileEditor(object):
-    def __init__(self, file, editor):
+    def __init__(self, project, file, editor):
         self.file = file
         self.editor = editor
+        self.project = project
         if self.file.get_name().endswith('.py'):
             self.editor.set_highlighting(rope.highlight.PythonHighlighting(self.editor))
             self.editor.set_indenter(rope.indenter.PythonCodeIndenter(self.editor))
+            self.editor.set_code_assist(self.project.get_code_assist())
         self.editor.set_text(self.file.read())
 
     def save(self):

File rope/project.py

 import subprocess
 
 import rope.core
+import rope.codeassist
 
 class Project(object):
     '''A Project containing files and folders'''
             os.mkdir(self.root)
         elif not os.path.isdir(self.root):
             raise rope.core.RopeException('Project root exists and is not a directory')
+        self.code_assist = rope.codeassist.CodeAssist()
 
     def get_root_folder(self):
         return Folder(self, '')
         created_package.create_file('__init__.py')
         return created_package
 
+    def get_code_assist(self):
+        return self.code_assist
+
     @staticmethod
     def remove_recursively(file):
         for root, dirs, files in os.walk(file, topdown=False):

File ropetest/codeassisttest.py

+import unittest
+
+from rope.codeassist import CodeAssist
+
+class CodeAssistTest(unittest.TestCase):
+    def setUp(self):
+        super(CodeAssistTest, self).setUp()
+        self.assist = CodeAssist()
+        
+    def tearDown(self):
+        super(CodeAssistTest, self).tearDown()
+
+    def test_simple_assist(self):
+        self.assist.complete_code('', 0)
+
+    def assert_proposal_in_result(self, completion, kind, result):
+        for proposal in result:
+            if proposal.completion == completion and proposal.kind == kind:
+                return
+        self.fail('Completion %s not proposed' % completion)
+
+    def assert_proposal_not_in_result(self, completion, kind, result):
+        for proposal in result:
+            if proposal.completion == completion and proposal.kind == kind:
+                self.fail('Completion %s was proposed' % completion)
+
+    def test_completing_global_variables(self):
+        code = 'my_global = 10\nt = my'
+        result = self.assist.complete_code(code, len(code))
+        self.assert_proposal_in_result('my_global', 'global_variable', result)
+
+    def test_not_proposing_unmatched_vars(self):
+        code = 'my_global = 10\nt = you'
+        result = self.assist.complete_code(code, len(code))
+        self.assert_proposal_not_in_result('my_global', 'global_variable', result)
+
+    def test_not_proposing_unmatched_vars_with_underlined_starting(self):
+        code = 'my_global = 10\nt = you_'
+        result = self.assist.complete_code(code, len(code))
+        self.assert_proposal_not_in_result('my_global', 'global_variable', result)
+
+
+if __name__ == '__main__':
+    unittest.main()

File ropetest/editortest.py

         self.editor.undo()
         self.assertEquals('sample text', self.editor.get_text())
 
+    def test_get_current_offset(self):
+        self.editor.set_text('sample text')
+        self.editor.set_insert(self.editor.get_start())
+        self.assertEquals(0, self.editor.get_current_offset())
+        self.editor.set_insert(self.editor.get_end())
+        self.assertEquals(11, self.editor.get_current_offset())
+
+    def test_get_current_offset_multiline(self):
+        self.editor.set_text('sample text\n another text \n and yet another')
+        self.editor.set_insert(self.editor.get_index(20))
+        self.assertEquals(20, self.editor.get_current_offset())
+        self.editor.set_insert(self.editor.get_index(30))
+        self.assertEquals(30, self.editor.get_current_offset())
+        self.editor.set_insert(self.editor.get_index(40))
+        self.assertEquals(40, self.editor.get_current_offset())
+
 if __name__ == '__main__':
     unittest.main()
 

File ropetest/fileeditortest.py

         self.projectMaker = SampleProjectMaker()
         self.fileName = self.projectMaker.getSampleFileName()
         self.project = Project(self.projectMaker.getRoot())
-        self.editor = FileEditor(self.project.get_resource(self.fileName), self.text)
+        self.editor = FileEditor(self.project, self.project.get_resource(self.fileName), self.text)
     
     def tearDown(self):
         self.projectMaker.removeAll()
 import ropetest.highlighttest
 import ropetest.searchingtest
 import ropetest.indentertest
+import ropetest.codeassisttest
+
 
 if __name__ == '__main__':
     result = unittest.TestSuite()
     result.addTests(unittest.makeSuite(ropetest.projecttest.TestPythonFileRunner))
     result.addTests(unittest.makeSuite(ropetest.highlighttest.HighlightTest))
     result.addTests(unittest.makeSuite(ropetest.indentertest.PythonCodeIndenterTest))
+    result.addTests(unittest.makeSuite(ropetest.codeassisttest.CodeAssistTest))
     runner = unittest.TextTestRunner()
     runner.run(result)