Anonymous avatar Anonymous committed e4a32d3

Moving everything to trunk

Comments (0)

Files changed (29)

+recursive-include docs *.html
+include *.txt
+
+recursive-include rope *.py
+recursive-include ropetest *.py
+include *.py
+rope: A Python IDE
+> Public release 0.1pre : April 22, 2006
+
+
+- Extending syntax highlighted elements : April 22, 2006
+
+
+- Auto indentation; C-i : April 20, 2006
+
+
+- Basic searching; C-s : April 12, 2006
+
+
+> SF registration : April 10, 2006
+
+
+- Multiple buffers : April 8, 2006
+  The editor should have a notebook view.
+
+
+- Enhancing dialogs : April 7, 2006
+  Using tkMessageBox, tkFileDialog, tkSimpleDialog, ScrolledText
+
+
+- Running modules : April 6, 2006
+  You should add the required directories to the python path.
+
+
+- Guessing source folders in the project : April 5, 2006
+
+
+- Finding a file in a project : April 4, 2006
+
+
+- Highlighting keywords : March 21, 2006
+  Only python files(*.py) should be highlighted.

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
+

docs/platformui.txt

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

docs/pythonsource.txt

+*** PythonSource ***
+	* Formatting code
+	* Basic refactorings
+	* Content assist
+	* Advanced refactorings
+	* Type inferencing
+	  * static
+	  * dynamic
+	  * ask user
+	* Auto completion
+
+>>> Advanced Refactorings; Need type inferencing
+* Rename method
+* Remove method
+* Move method
+* Rename attribute
+* Remove instace variable
+* Rename instance variable
+* Pull up instance variable
+* Push down instance variable
+* Move instance variable
+* Encapsulate attribute
+* Inline method
+* Change method signature
+
+>>> Advanced Algorithms
+* Finding similar pieces of code while extracting methods
+* Finding similar statements while extracting local variables
+
+>>> Help coding
+* Override/implement methods
+* Make getters and setters
+* Organize imports
+* Add import
+* Format source
+
+>>> Content Assists
+* Make module/class/function/method
+* Surround with try statement
+* Remove try statement
+* Assign statement to local variable
+* Fix by adding self to variable
+* Open Type
+* Auto Completion
+* Go to definition
+* Show pydoc
+
+>>> Auto Completion
+* Complete variable name
+* Complete method name by using type inferencing
+* Complete function keyword argument
+* Complete module and class name
+
+>>> Basic Refactorings
+* Rename module
+* Move class to another module
+* Move class to a new module
+* Rename class
+* Rename function
+* Add class
+* Remove class
+* Extract local variable
+* Extract method
+* Add method
+* Add instace variable
+* Extract constant
+* Extract interface
+* Extract subclass
+* Extract superclass
+* Callapse hierarchy
+* Extract class
+* Inline class
+* Inline function
+* Change function signature
+* Pull up/Push down method
+* Convert local variable to attribute
+* Introduce factory
+
+* Writing ASTs
+* Building ASTs
+rope; A Python IDE
+
+--- Stories ---
+
+* Organize imports @ 2
+
+
+* Add import @ 2
+
+
+* Auto-complete class and function names @ 2
+
+
+* Show PyDoc @ 2
+
+
+* Variable indentation and tab size @ 1
+
+
+* Open Type @ 2
+
+
+* Showing syntactical errors @ 3
+
+
+* Having multiple clipboards @ 2
+
+
+* Adding tool bar @ 1
+
+
+* Commanding buffer @ 4
+
+
+* Subversion support using pysvn @ 3
+
+
+* Rename local variable @ 2
+
+
+* Rename function @ 2
+
+
+* Rename class @ 3
+
+
+* Move a class to another module @ 3
+
+
+* Local history @ 4
+
+
+* Find matching parens @ 1
+
+
+* Rename module refactoring @ 2
+
+
+* Last edit location; C-q @ 2
+
+
+* Running unit tests @ 3
+  Add a graphical view for running tests.
+
+
+* Enhance open dialog @ 1
+
+
+* Replacement; M-% @ 1
+
+
+* Save as; C-x C-w
+
+
+* Remembering last open project @ 1
+
+
+* File Encodings @ 2
+
+
+* Changing next/prev word to stop on underline and capital letters @ 1
+
+
+* Commenting and uncommenting lines @ 1
+
+
+* Enhancing searching @ 1
+  * End the search when some other key sequence is typed
+  * C-s C-s should start the last search
+  * Search history; BackSpace should go back to the matches found before
+  * Starting from begining when end of file is reached
+
+
+* Enhancing module running @ 2
+  * Showing running status in the GUI
+  * Printing output somewhere
+  * Getting input from somewhere
+  * Customizing CWD and parameters
+  * Running last run
+
+
+* User specified source folders @ 2
+    You should save this project specific information somewhere. How should
+  guessed source folders be used.
+
+
+* Enhancing syntax highlighting @ 1
+  * Use idlelib or Modified event for updating highlighting when necessary
+  * Only highlighting the changed region
+  * Extend highlighting region while highlighting if necessay
+
+
+* Enhancing menu @ 1
+  * Disable inaccessable items
+  * Showing keyboard short-cuts in front of menu items
+
+
+* Enhancing editor @ 1
+  * Kill line; C-k
+  * Select all; C-x h
+  * Go to line
+
+
+* Enhancing auto indentation @ 1
+  * Implicit line joining: Indenting lines whose previous line contains a ([{'''
+  * Indenting a range of file
+  * Faster indentation using line oriented tools; 3 times faster
+  * Removing extra spaces
+
+
+* Formating Code @ 2
+
+
+* Writing ASTs @ 4
+
+
+* Auto-complete variable names @ 3
+
+
+* Enhancing editor @ 1
+  * Clearing selection if something happens
+  * Unifying builtin and emacs-style selections; if selection
+    is active do the builtin cut and paste
+  * Edit menu: cut, paste, undo, redo, revert
+
+
+> Public release 0.1 : May 6, 2006
+
+
+* Making ASTs @ 4
+
+
+* Auto-complete module names @ 2
+
+
+* Enhancing editor and indentation @ 1
+  * Clearing undo list when opening a file; undoSeparator when saving
+  * Pressing back space should deindent; shortcuts for indenting and deindenting
+  * Showing modified file status
+
+
+* Showing current line in status bar @ 1
+
+
+* Make a new package @ 1
+  Should ask the source folder.
+
+
+* Switch editor dialog; C-x b and C-F6 @ 1
+
+
+* Make a new module @ 1
+  Should ask the source folder and package.
+
+
+--- Remaining Stories ---
+? Rename rope; maybe pyrope
+? Using underlined_words function style
+? Separate domain and presentation everywhere
+* Make tests less dependant
+* Make a new Project; Seperate Opening and creating projects
+* Add removing resources to Core
+? Project resource consistency; What if it is deleted after being created.
+? Distinction of functional and unit tests
+? Logging and status bar
+? Using SF SVN; Making a script for committing to SF

docs/workingon.txt

+
+? Iterator for accessing editor text or LineOrientedOutput
+? Should workingon.txt be under version control?
+? Consider using Tix or Pmw or idlelib
+? Separating domain and presentation in Core
+? not using editor._rope_title
+#! /usr/bin/python
+import rope.core
+
+if __name__ == '__main__':
+    rope.core.Core.get_core().run()

Empty file added.

+from threading import Thread
+from Tkinter import *
+import tkMessageBox
+import tkFileDialog
+import tkSimpleDialog
+
+from rope.fileeditor import FileEditor
+from rope.editor import GraphicalEditor
+from rope.project import Project, FileFinder, PythonFileRunner
+
+class Core(object):
+    '''The main class for the IDE'''
+    def __init__(self):
+        self.root = Tk()
+        self.root.title('Rope')
+        self.menubar = Menu(self.root, relief=RAISED, borderwidth=1)
+        self.root['menu'] = self.menubar
+        self._create_menu()
+
+        self.main = Frame(self.root, height='13c', width='26c', relief=RIDGE, bd=2)
+        self.editor_list = Frame(self.main, borderwidth=0)
+        self.editor_frame = Frame(self.main, borderwidth=0, relief=RIDGE)
+        self.status_bar = Frame(self.main, borderwidth=1, relief=RIDGE)
+        self.status_text = Label(self.status_bar, text='')
+        self.status_text.pack(side=LEFT)
+
+        self.editors = []
+        self.active_file_path = StringVar('')
+        self.active_editor = None
+
+        self._set_key_binding(self.root)
+        self.root.protocol('WM_DELETE_WINDOW', self.exit)
+        self.runningThread = Thread(target=self.run)
+        self.project = None
+    
+    def _create_menu(self):
+        fileMenu = Menu(self.menubar, tearoff=0)
+        self.menubar.add_cascade(label='File', menu=fileMenu, underline=1)
+        fileMenu.add_command(label='Open Project ...', command=self._open_project, underline=6)
+        fileMenu.add_command(label='Close Project', command=self.close_project, underline=3)
+        fileMenu.add_separator()
+        fileMenu.add_command(label='New File ...', command=self._create_new_file, underline=0)
+        fileMenu.add_command(label='New Folder ...', command=self._create_new_folder, underline=0)
+        fileMenu.add_separator()
+        fileMenu.add_command(label='Open File ...', command=self._open_file, underline=0)
+        fileMenu.add_command(label='Find File ...', command=self._find_file, underline=0)
+        fileMenu.add_separator()
+        fileMenu.add_command(label='Exit', command=self.exit, underline=1)
+
+    def _set_key_binding(self, widget):
+        widget.bind('<Control-x><Control-f>', self._open_file)
+        widget.bind('<Control-x><Control-n>', self._create_new_file)
+        def _save_active_editor(event):
+            self.save_file()
+            return 'break'
+        widget.bind('<Control-x><Control-s>', _save_active_editor)
+        widget.bind('<Control-x><Control-p>', self._open_project)
+        def _exit(event):
+            self.exit()
+            return 'break'
+        widget.bind('<Control-x><Control-c>', _exit)
+        widget.bind('<Control-x><Control-d>', self._create_new_folder)
+        widget.bind('<Control-R>', self._find_file)
+        widget.bind('<Control-F11>', self._run_active_editor)
+        def _close_active_editor(event):
+            self.close_active_editor()
+            return 'break'
+        widget.bind('<Control-x><k>', _close_active_editor)
+
+
+    def _find_file(self, event=None):
+        if not self.project:
+            tkMessageBox.showerror(parent=self.root, title='No Open Project',
+                                   message='No project is open')
+            return
+        toplevel = Toplevel()
+        toplevel.title('Find Project File')
+        find_dialog = Frame(toplevel)
+        name_label = Label(find_dialog, text='Name')
+        name = Entry(find_dialog)
+        found_label = Label(find_dialog, text='Matching Files')
+        found = Listbox(find_dialog, selectmode=SINGLE, width=46, height=15)
+        scrollbar = Scrollbar(find_dialog, orient=VERTICAL)
+        scrollbar['command'] = found.yview
+        found.config(yscrollcommand=scrollbar.set)
+        file_finder = FileFinder(self.project)
+        def name_changed(event):
+            if name.get() == '':
+                result = ()
+            else:
+                result = file_finder.find_files_starting_with(name.get())
+            found.delete(0, END)
+            for file in result:
+                found.insert(END, file.get_path())
+            if result:
+                found.selection_set(0)
+        def open_selected():
+            selection = found.curselection()
+            if selection:
+                resource_name = found.get(selection[0])
+                self.open_file(resource_name)
+                toplevel.destroy()
+        def cancel():
+            toplevel.destroy()
+        name.bind('<Any-KeyRelease>', name_changed)
+        name.bind('<Return>', lambda event: open_selected())
+        name.bind('<Escape>', lambda event: cancel())
+        found.bind('<Return>', lambda event: open_selected())
+        found.bind('<Escape>', lambda event: cancel())
+        def select_prev(event):
+            active = found.index(ACTIVE)
+            if active - 1 >= 0:
+                found.activate(active - 1)
+                found.see(active - 1)
+        found.bind('<Control-p>', select_prev)
+        def select_next(event):
+            active = found.index(ACTIVE)
+            if active + 1 < found.size():
+                found.activate(active + 1)
+                found.see(active + 1)
+        found.bind('<Control-n>', select_next)
+        name_label.grid(row=0, column=0, columnspan=2)
+        name.grid(row=1, column=0, columnspan=2)
+        found_label.grid(row=2, column=0, columnspan=2)
+        found.grid(row=3, column=0, columnspan=1)
+        scrollbar.grid(row=3, column=1, columnspan=1, sticky=N+S)
+        find_dialog.grid()
+        name.focus_set()
+        toplevel.grab_set()
+        self.root.wait_window(toplevel)
+        if event:
+            return 'break'
+
+    def _run_active_editor(self, event=None):
+        if not self.get_active_editor():
+            tkMessageBox.showerror(parent=self.root, title='No Open Editor',
+                                   message='No Editor is open.')
+            return
+        self.run_active_editor()
+        return 'break'
+
+    def _open_file(self, event=None):
+        if not self.project:
+            tkMessageBox.showerror(parent=self.root, title='No Open Project',
+                                   message='No project is open')
+            return 'break'
+        def doOpen(fileName):
+                self.open_file(fileName)
+        self._show_open_dialog(doOpen, 'Open File Dialog')
+        return 'break'
+
+    def _create_new_file(self, event=None):
+        if not self.project:
+            tkMessageBox.showerror(parent=self.root, title='No Open Project',
+                                   message='No project is open')
+            return 'break'
+        def doOpen(fileName):
+            self.create_file(fileName)
+        self._show_open_dialog(doOpen, 'New File Dialog')
+        return 'break'
+
+    def _create_new_folder(self, event=None):
+        if not self.project:
+            tkMessageBox.showerror(parent=self.root, title='No Open Project',
+                                   message='No project is open')
+            return 'break'
+        def doOpen(fileName):
+            self.create_folder(fileName)
+        self._show_open_dialog(doOpen, 'New Folder Dialog')
+        return 'break'
+
+    def _open_project(self, event=None):
+        def doOpen(projectRoot):
+            self.open_project(projectRoot)
+        directory = tkFileDialog.askdirectory(parent=self.root, title='Open Project')
+        if directory:
+            doOpen(directory)
+        return 'break'
+
+    def _show_open_dialog(self, openCommand, title='Open Dialog'):
+        input = tkSimpleDialog.askstring(title, 'Address :', parent=self.root)
+        if input:
+            try:
+                openCommand(input)
+            except Exception, e:
+                tkMessageBox.showerror(parent=self.root, title='Failed',
+                                       message=str(e))
+
+    def start(self):
+        self.runningThread.start()
+
+    def run(self):
+        self.root.rowconfigure(0, weight=1)
+        self.root.columnconfigure(0, weight=1)
+        self.main.rowconfigure(0, weight=1)
+        self.main.columnconfigure(0, weight=1)
+        self.editor_list.pack(fill=BOTH, side=TOP)
+        self.editor_frame.pack(fill=BOTH, expand=1)
+        self.status_bar.pack(fill=BOTH, side=BOTTOM)
+        self.main.pack(fill=BOTH, expand=1)
+        self.main.pack_propagate(0)
+        self.root.mainloop()
+
+    def open_file(self, fileName):
+        if self.project is None:
+            raise RopeException('No project is open')
+        file = self.project.get_resource(fileName)
+        for editor in self.editors:
+            if editor.get_file() == file:
+                editor._rope_title.invoke()
+                return editor
+        editor = FileEditor(file, GraphicalEditor(self.editor_frame))
+        self.editors.append(editor)
+        title = Radiobutton(self.editor_list, text=file.get_name(), variable=self.active_file_path,
+                            value=file.get_path(), indicatoron=0, bd=2,
+                            command=lambda: self.activate_editor(editor),
+                            selectcolor='#99A', relief=GROOVE)
+        editor._rope_title = title
+        title.select()
+        title.pack(fill=BOTH, side=LEFT)
+        self.activate_editor(editor)
+        self._set_key_binding(editor.get_editor().getWidget())
+        return editor
+
+    def activate_editor(self, editor):
+        if self.get_active_editor():
+            self.get_active_editor().get_editor().getWidget().forget()
+        editor.get_editor().getWidget().pack(fill=BOTH, expand=1)
+        editor.get_editor().getWidget().focus_set()
+        editor._rope_title.select()
+        self.active_editor = editor
+        self.editors.remove(editor)
+        self.editors.insert(0, editor)
+
+    def get_active_editor(self):
+        return self.active_editor
+
+    def close_active_editor(self):
+        if self.active_editor is None:
+            return
+        self.active_editor.get_editor().getWidget().forget()
+        self.editors.remove(self.active_editor)
+        self.active_editor._rope_title.forget()
+        self.active_editor = None
+        if self.editors:
+            self.editors[0]._rope_title.invoke()
+
+    def save_file(self):
+        activeEditor = self.get_active_editor()
+        if activeEditor:
+            activeEditor.save()
+
+    def create_file(self, fileName):
+        if self.project is None:
+            raise RopeException('No project is open')
+        self.project.create_file(fileName)
+        return self.open_file(fileName)
+
+    def open_project(self, projectRoot):
+        if self.project:
+            self.close_project()
+        self.project = Project(projectRoot)
+
+    def close_project(self):
+        while self.get_active_editor() is not None:
+            self.close_active_editor()
+        self.project = None
+
+    def create_folder(self, folderName):
+        self.project.create_folder(folderName)
+
+    def exit(self):
+        self.root.quit()
+
+    def get_open_project(self):
+        return self.project
+
+    def run_active_editor(self):
+        activeEditor = self.get_active_editor()
+        if activeEditor:
+            runner = PythonFileRunner(activeEditor.get_file())
+            return runner
+
+    @staticmethod
+    def get_core():
+        '''Get the singleton instance of Core'''
+        if not hasattr(Core, '_core'):
+            Core._core = Core()
+        return Core._core
+
+
+class RopeException(Exception):
+    '''Base exception for rope'''
+    pass
+from Tkinter import *
+from tkFont import *
+from ScrolledText import ScrolledText
+
+import rope.highlight
+import rope.searching
+import rope.indenter
+
+
+class TextEditor(object):
+    '''A class representing a text editor'''
+    def get_text(self):
+        pass
+    
+    def set_text(self, text):
+        pass
+
+    def get_start(self):
+        pass
+    
+    def get_insert(self):
+        pass
+
+    def get_end(self):
+        pass
+    
+    def get_relative(self, base_index, offset):
+        pass
+    
+    def get_index(self, offset):
+        pass
+    
+    def set_insert(self, index):
+        pass
+
+    def get(self, start=None, end=None):
+        pass
+    
+    def insert(self, index, text):
+        pass
+
+    def delete(self, start=None, end=None):
+        pass
+
+    def nextWord(self):
+        pass
+
+    def prevWord(self):
+        pass
+
+    def deleteNextWord(self):
+        pass
+
+    def deletePrevWord(self):
+        pass
+
+    def goToTheStart(self):
+        pass
+
+    def goToTheEnd(self):
+        pass
+
+    def set_highlighting(self, highlighting):
+        pass
+
+    def highlight_match(self, match):
+        pass
+
+    def search(self, keyword, start, case=True, forwards=True):
+        pass
+
+class TextIndex(object):
+    '''A class for pointing to a position in a text'''
+
+
+class GraphicalEditor(TextEditor):
+    def __init__(self, parent):
+        self.text = ScrolledText(parent, bg='white', 
+                         font=Font(family='Typewriter', size=14), 
+                         undo=True, maxundo=50, highlightcolor='#99A')
+        self.searcher = rope.searching.Searcher(self)
+        self._bind_keys()
+        self._initialize_highlighting()
+        self.highlighting = rope.highlight.NoHighlighting()
+        self.indenter = rope.indenter.NullIndenter()
+
+    def _initialize_highlighting(self):
+        def colorize(event):
+            start = 'insert linestart-2c'
+            end = 'insert lineend'
+            start_tags = self.text.tag_names(start)
+            if start_tags:
+                tag = start_tags[0]
+                range = self.text.tag_prevrange(tag, start + '+1c')
+                if self.text.compare(range[0], '<', start):
+                    start = range[0]
+                if self.text.compare(range[1], '>', end):
+                    end = range[1]
+            end_tags = self.text.tag_names(end)
+            if end_tags:
+                tag = end_tags[0]
+                range = self.text.tag_prevrange(tag, end + '+1c')
+                if self.text.compare(range[1], '>', end):
+                    end = range[1]
+            self._highlight_range(start, end)
+        self.text.bind('<Any-KeyRelease>', colorize)
+
+    def _highlight_range(self, startIndex, endIndex):
+        for style in self.highlighting.getStyles().keys():
+            self.text.tag_remove(style, startIndex, endIndex)
+        for start, end, kind in self.highlighting.highlights(GraphicalTextIndex(self, startIndex),
+                                                             GraphicalTextIndex(self, endIndex)):
+            self.text.tag_add(kind, start._getIndex(), end._getIndex())
+
+    def _bind_keys(self):
+        self.text.bind('<Alt-f>', lambda event: self.nextWord())
+        self.text.bind('<Alt-b>', lambda event: self.prevWord())
+        self.text.bind('<Alt-d>', lambda event: self.deleteNextWord())
+        def deletePrevWordListener(event):
+            self.deletePrevWord()
+            return 'break'
+        self.text.bind('<Alt-BackSpace>', deletePrevWordListener)
+        def doUndo(event):
+            self.undo()
+            return 'break'
+        def doRedo(event):
+            self.redo()
+            return 'break'
+        self.text.bind('<Control-x><u>', doUndo)
+        self.text.bind('<Control-x><r>', doRedo)
+        def doGoToTheStart(event):
+            self.goToTheStart()
+            self.text.see(INSERT)
+        def doGoToTheEnd(event):
+            self.goToTheEnd()
+            self.text.see(INSERT)
+        self.text.bind('<Alt-less>', doGoToTheStart)
+        self.text.bind('<Alt-KeyPress->>', doGoToTheEnd)
+        def doSetMark(event):
+            self.setMark()
+            return 'break'
+        self.text.bind('<Control-space>', doSetMark)
+        def doCopy(event):
+            self.copyRegion()
+            return 'break'
+        self.text.bind('<Alt-w>', doCopy)
+        def doCut(event):
+            self.cutRegion()
+            return 'break'
+        self.text.bind('<Control-w>', doCut)
+        def doPaste(event):
+            self.paste()
+            return 'break'
+        self.text.bind('<Control-y>', doPaste)
+        def escape(event):
+            self.clearMark()
+            if self.get_searcher().is_searching():
+                self.get_searcher().cancel_searching()
+        self.text.bind('<Control-g>', escape)
+        self.text.bind('<Control-x><Control-x>', lambda event: self.swapMarkAndInsert())
+        def goNextPage(event):
+            self.nextPage()
+            return 'break'
+        self.text.bind('<Control-v>', goNextPage)
+        def goPrevPage(event):
+            self.prevPage()
+            return 'break'
+        self.text.bind('<Alt-v>', goPrevPage)
+        def doInsertTab(event):
+            self.insertTab()
+            return 'break'
+        def indent_line(event):
+            self.indenter.indent_line(self.get_insert())
+            return 'break'
+        self.text.bind('<Control-i>', indent_line)
+        self.text.bind('<Tab>', doInsertTab)
+        def return_handler(event):
+            if self.searcher.is_searching():
+                self.searcher.end_searching()
+                return 'break'
+            self.text.insert(INSERT, '\n')
+            self.indenter.indent_line(self.get_insert())
+            self.text.see(INSERT)
+            return 'break'
+        self.text.bind('<Return>', return_handler)
+        self.text.event_add('<<ForwardSearch>>', '<Control-s>')
+        self.text.event_add('<<BackwardSearch>>', '<Control-r>')
+        self.text.bind('<<ForwardSearch>>',
+                       lambda event: self._search_event(True), '+')
+        self.text.bind('<<BackwardSearch>>',
+                       lambda event: self._search_event(False))
+        self.text.bind('<Any-KeyPress>', self._search_handler)
+
+    def get_text(self):
+        return self.text.get('1.0', END)[0 : -1]
+    
+    def set_text(self, text):
+        self.text.delete('1.0', END)
+        self.text.insert('1.0', text)
+        self.text.mark_set(INSERT, '1.0')
+        self._highlight_range('0.0', 'end')
+
+    def get_start(self):
+        return GraphicalTextIndex(self, '1.0')
+
+    def get_insert(self):
+        return GraphicalTextIndex(self, INSERT)
+
+    def get_end(self):
+        return GraphicalTextIndex(self, END)
+
+    def get_relative(self, textIndex, offset):
+        return GraphicalTextIndex(self, self._go(textIndex._getIndex(), offset))
+
+    def get_index(self, offset):
+        return GraphicalTextIndex(self, self._go('1.0', offset))
+
+    def _go(self, fromIndex, count):
+        if count >= 0:
+            return fromIndex + ('+%dc' % count)
+        else:
+            return fromIndex + ('%dc' % count)
+
+    def _get_line_from_index(self, index):
+        return int(self.text.index(index).split('.')[0])
+        
+    def _get_column_from_index(self, index):
+        return int(self.text.index(index).split('.')[1])
+    
+    def set_insert(self, textIndex):
+        self.text.mark_set(INSERT, textIndex._getIndex())
+
+    def get(self, start=None, end=None):
+        startIndex = INSERT
+        endIndex = None
+        if start is not None:
+            startIndex = start._getIndex()
+            if start == self.get_end():
+                return ''
+        if end is not None:
+            endIndex = end._getIndex()
+        return self.text.get(startIndex, endIndex)
+    
+    def insert(self, textIndex, text):
+        self.text.insert(textIndex._getIndex(), text)
+
+    def delete(self, start = None, end = None):
+        startIndex = INSERT
+        if start is not None:
+            startIndex = start._getIndex()
+            if start == self.get_end():
+                return
+        endIndex = None
+        if end is not None:
+            endIndex = end._getIndex()
+        self.text.delete(startIndex, endIndex)
+        
+    def _get_next_word_index(self):
+        result = INSERT
+        while self.text.compare(result, '!=', 'end-1c') and \
+              not self.text.get(result)[0].isalnum():
+            result = self.text.index(result + '+1c')
+        return result + ' wordend'
+
+    def nextWord(self):
+        self.text.mark_set(INSERT, self._get_next_word_index())
+        self.text.see(INSERT)
+
+    def _get_prev_word_index(self):
+        result = INSERT
+        while not self.text.compare(result, '==', '1.0') and \
+              not self.text.get(result + '-1c')[0].isalnum():
+            result = self.text.index(result + '-1c')
+        return result + '-1c wordstart'
+
+    def prevWord(self):
+        self.text.mark_set(INSERT, self._get_prev_word_index())
+        self.text.see(INSERT)
+
+    def deleteNextWord(self):
+        self.text.delete(INSERT, self._get_next_word_index())
+
+    def deletePrevWord(self):
+        self.text.delete(self._get_prev_word_index(), INSERT)
+
+    def getWidget(self):
+        return self.text
+
+    def undoSeparator(self):
+        self.text.edit_separator()
+
+    def undo(self):
+        try:
+            self.text.edit_undo()
+        except TclError:
+            pass
+
+    def clear_undo(self):
+        self.text.edit_reset()
+        
+    def redo(self):
+        try:
+            self.text.edit_redo()
+        except TclError:
+            pass
+
+    def goToTheStart(self):
+        self.set_insert(self.get_start())
+    
+    def goToTheEnd(self):
+        self.set_insert(self.get_end())
+
+    def generate_event(self, event):
+        self.text.event_generate(event)
+
+    def setMark(self):
+        self.text.mark_set('mark', INSERT)
+
+    def clearMark(self):
+        self.text.mark_unset('mark')
+
+    def _selectRegion(self):
+        start = 'mark'
+        end = INSERT
+        if self.text.compare(start, '>', end):
+            start, end = end, start
+        self.text.tag_add(SEL, start, end)
+
+    def copyRegion(self):
+        try:
+            self._selectRegion()
+            self.text.event_generate('<<Copy>>')
+            self.text.tag_remove(SEL, '1.0', END)
+        except TclError:
+            pass
+
+    def cutRegion(self):
+        try:
+            self._selectRegion()
+            self.text.event_generate('<<Cut>>')
+            self.text.see(INSERT)
+        except TclError:
+            pass
+
+    def paste(self):
+        self.text.event_generate('<<Paste>>')
+        self.text.see(INSERT)
+
+    def swapMarkAndInsert(self):
+        try:
+            mark = self.text.index('mark')
+            self.setMark()
+            self.text.mark_set(INSERT, mark)
+        except TclError:
+            pass
+
+    def nextPage(self):
+        self.text.event_generate('<Next>')
+
+    def prevPage(self):
+        self.text.event_generate('<Prior>')
+
+    def insertTab(self, textIndex = None):
+        index = INSERT
+        if textIndex is not None:
+            index = textIndex._getIndex()
+        self.text.insert(INSERT, ' ' * 4)
+
+    def set_highlighting(self, highlighting):
+        self.highlighting = highlighting
+        for name, style in self.highlighting.getStyles().iteritems():
+            fontKWs = {}
+            if style.italic is not None:
+                if style.italic:
+                    fontKWs['slant'] = 'italic'
+                else:
+                    fontKWs['slant'] = 'roman'
+            if style.bold is not None:
+                if style.bold:
+                    fontKWs['weight'] = 'bold'
+                else:
+                    fontKWs['weight'] = 'normal'
+            if style.underline is not None:
+                if style.underline:
+                    fontKWs['underline'] = 1
+                else:
+                    fontKWs['underline'] = 0
+            if style.strikethrough is not None:
+                if style.strikethrough:
+                    fontKWs['overstrike'] = 1
+                else:
+                    fontKWs['overstrike'] = 0
+            font = Font(font=self.text['font']).copy()
+            font.configure(**fontKWs)
+            configKWs = {}
+            if style.color is not None:
+                configKWs['foreground'] = style.color
+            configKWs['font'] = font
+            self.text.tag_config(name, **configKWs)
+        self._highlight_range('0.0', 'end')
+
+    def get_searcher(self):
+        return self.searcher
+
+    def highlight_match(self, match):
+        if not match:
+            return
+        self.text.tag_remove(SEL, '1.0', END)
+        self.text.tag_add(SEL, match.start._getIndex(), match.end._getIndex())
+        if match.side == 'right':
+            self.text.mark_set(INSERT, match.end._getIndex())
+        else:
+            self.text.mark_set(INSERT, match.start._getIndex())
+        self.text.see(INSERT)
+
+
+    def _search_event(self, forward):
+        if self.searcher.is_searching():
+            self.searcher.configure_search(forward)
+            self.searcher.next_match()
+        else:
+            self.searcher.start_searching()
+            self.searcher.configure_search(forward)
+
+    def _search_handler(self, event):
+        if not self.searcher.is_searching():
+            return
+        import string
+        if len(event.char) == 1 and (event.char.isalnum() or
+                                     event.char in string.punctuation):
+            self.searcher.append_keyword(event.char)
+            return 'break'
+        if event.keysym == 'space':
+            self.searcher.append_keyword(event.char)
+            return 'break'
+        if event.keysym == 'BackSpace':
+            self.searcher.shorten_keyword()
+            return 'break'
+        if event.keysym == 'Return':
+            self.searcher.end_searching()
+            return 'break'
+        return 'break'
+
+    def search(self, keyword, start, case=True, forwards=True):
+        found = self.text.search(keyword, start._getIndex(),
+                                 nocase=int(not case), backwards=int(not forwards))
+        if not found:
+            return None
+        return GraphicalTextIndex(self, found)
+
+    def set_indenter(self, text_indenter):
+        self.indenter = text_indenter
+
+    def get_indenter(self):
+        return self.indenter
+
+
+class GraphicalTextIndex(TextIndex):
+    '''An immutable class for pointing to a position in a text'''
+    def __init__(self, editor, index):
+        self.index = index
+        self.editor = editor
+        if self.editor.text.compare(index, '==', 'end'):
+            self.index = 'end-1c'
+        self.index = editor.text.index(self.index)
+
+    def __cmp__(self, index):
+        assert self.editor == index.editor
+        if self.editor.text.compare(self.index, '<', index.index):
+            return -1
+        if self.editor.text.compare(self.index, '>', index.index):
+            return +1
+        return 0
+    
+    def _getIndex(self):
+        return self.index
+
+    def __str__(self):
+        return '<%s, %s>' % (self.__class__.__name__, self.index)
+

rope/fileeditor.py

+import rope.highlight
+import rope.indenter
+
+class FileEditor(object):
+    def __init__(self, file, editor):
+        self.file = file
+        self.editor = editor
+        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_text(self.file.read())
+
+    def save(self):
+        self.file.write(self.editor.get_text())
+
+    def get_editor(self):
+        return self.editor
+
+    def get_file(self):
+        return self.file

rope/highlight.py

+import re
+import keyword
+
+
+class Highlighting(object):
+    def getStyles(self):
+        '''Returns the dictionary of styles used in highlighting texts by this highlighting'''
+
+    def highlights(self, startIndex, endIndex):
+        '''Generates highlighted ranges as (start, end, style) tuples'''
+
+
+class HighlightingStyle(object):
+    def __init__(self, color=None, bold=None, italic=None, strikethrough=None, underline=None):
+        self.color = color
+        self.bold = bold
+        self.italic = italic
+        self.strikethrough = strikethrough
+        self.underline = underline
+
+
+class PythonHighlighting(Highlighting):
+    def __init__(self, editor):
+        self.editor = editor
+        kw = r"\b" + PythonHighlighting.any("keyword", keyword.kwlist) + r"\b"
+        import __builtin__
+        builtinlist = [str(name) for name in dir(__builtin__)
+                       if not name.startswith('_')]
+        builtin = r"([^.'\"\\]\b|^)" + PythonHighlighting.any("builtin", builtinlist) + r"\b"
+        comment = PythonHighlighting.any("comment", [r"#[^\n]*"])
+        sqstring = r"(\b[rR])?'[^'\\\n]*(\\.[^'\\\n]*)*'?"
+        dqstring = r'(\b[rR])?"[^"\\\n]*(\\.[^"\\\n]*)*"?'
+        sq3string = r"(\b[rR])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
+        dq3string = r'(\b[rR])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
+        string = PythonHighlighting.any("string", [sq3string, dq3string, sqstring, dqstring])
+        self.pattern = re.compile(kw + "|" + builtin + '|' + comment + "|" + string)
+
+    @staticmethod
+    def any(name, list):
+        return "(?P<%s>" % name + "|".join(list) + ")"
+
+    def getStyles(self):
+        return {'keyword': HighlightingStyle(color='blue', bold=True),
+                'string' : HighlightingStyle(color='#004080'),
+                'comment' : HighlightingStyle(color='#008000', italic=True),
+                'builtin' : HighlightingStyle(color='pink'),
+                'definition' : HighlightingStyle(color='purple', bold=True)}
+
+    def highlights(self, start, end):
+        text = self.editor.get(start, end)
+        for match in self.pattern.finditer(text):
+            for key, value in match.groupdict().items():
+                if value:
+                    a, b = match.span(key)
+                    yield (self.editor.get_relative(start, a),
+                           self.editor.get_relative(start, b), key)
+                    if value in ("def", "class"):
+                        idprog = re.compile(r"\s+(\w+)", re.S)
+                        m1 = idprog.match(text, b)
+                        if m1:
+                            a, b = m1.span(1)
+                            yield (self.editor.get_relative(start, a),
+                                   self.editor.get_relative(start, b), 'definition')
+
+
+class NoHighlighting(Highlighting):
+    def getStyles(self):
+        return {}
+
+    def highlights(self, startIndex, endIndex):
+        if False:
+            yield None
+import re
+
+class TextIndenter(object):
+    '''A class for formatting texts'''
+    def indent_line(self, index):
+        '''indent the current line'''
+
+class NullIndenter(TextIndenter):
+    pass
+
+class PythonCodeIndenter(TextIndenter):
+    def __init__(self, editor):
+        self.editor = editor
+
+    def _get_line_start(self, index):
+        while index != self.editor.get_start():
+            index = self.editor.get_relative(index, -1)
+            if self.editor.get(index) == '\n':
+                return self.editor.get_relative(index, +1)
+        return self.editor.get_start()
+
+    def _get_prev_line_start(self, line_start):
+        return self._get_line_start(self.editor.get_relative(line_start, -1))
+
+    def _get_line_end(self, index):
+        while index != self.editor.get_end():
+            if self.editor.get(index) == '\n':
+                return index
+            index = self.editor.get_relative(index, +1)
+        return self.editor.get_end()
+
+    def _set_line_indents(self, line_start, indents):
+        old_indents = self._count_line_indents(line_start)
+        indent_diffs = indents - old_indents
+        if indent_diffs == 0:
+            return
+        if indent_diffs > 0:
+            self.editor.insert(line_start, ' ' * indent_diffs)
+        else:
+            self.editor.delete(line_start, self.editor.get_relative(line_start, -indent_diffs))
+
+    def _get_line_contents(self, line_start):
+        end = self._get_line_end(line_start)
+        return self.editor.get(line_start, end)
+
+    def _get_last_non_empty_line(self, line_start):
+        current_line = self._get_prev_line_start(line_start)
+        while current_line != self.editor.get_start() and \
+                  self._get_line_contents(current_line).strip() == '':
+            current_line = self._get_prev_line_start(current_line)
+        return current_line
+
+    def _count_line_indents(self, index):
+        contents = self._get_line_contents(index)
+        result = 0
+        for x in contents:
+            if x == ' ':
+                result += 1
+            else:
+                break
+        return result
+
+    def _get_starting_backslash_line(self, line_start):
+        current = line_start
+        while current != self.editor.get_start():
+            new_line = self._get_prev_line_start(current)
+            if not self._get_line_contents(new_line).rstrip().endswith('\\'):
+                return current
+            current = new_line
+        return self.editor.get_start()
+
+    def _get_correct_indentation(self, line_start):
+        if line_start == self.editor.get_start():
+            return 0
+        new_indent = self._get_base_indentation(line_start)
+
+        prev_start = self._get_last_non_empty_line(line_start)
+        prev_line = self._get_line_contents(prev_start)
+        if prev_start == line_start or prev_line.strip() == '':
+            new_indent = 0
+        else:
+            new_indent += self._get_indentation_changes_caused_by_prev_line(prev_line)
+        current_line = self._get_line_contents(line_start)
+        new_indent += self._get_indentation_changes_caused_by_current_line(current_line)
+        return new_indent
+        
+    def _get_base_indentation(self, line_start):
+        current_start = self._get_last_non_empty_line(line_start)
+        current_line = self._get_line_contents(current_start)
+
+        openings = 0
+        while True:
+            current_line = self._get_line_contents(current_start)
+            current = len(current_line) - 1
+            while current >= 0:
+                if current_line[current] in list('([{'):
+                    openings += 1
+                if current_line[current] in list(')]}'):
+                    openings -= 1
+                if openings > 0:
+                    return current + 1
+                current -= 1
+            if openings == 0:
+                break
+            if current_start == self.editor.get_start():
+                break
+            current_start = self._get_last_non_empty_line(current_start)
+
+        if current_line.rstrip().endswith('\\'):
+            real_start = self._get_starting_backslash_line(current_start)
+            if (real_start == current_start):
+                try:
+                    return current_line.index(' = ') + 3
+                except ValueError:
+                    match = re.search('\\b ', current_line)
+                    if match:
+                        return match.start() + 1
+                    else:
+                        return len(current_line) + 1
+        else:
+            second_prev_start = self._get_prev_line_start(current_start)
+            if second_prev_start != current_start and \
+                   self._get_line_contents(second_prev_start).rstrip().endswith('\\'):
+                real_start = self._get_starting_backslash_line(second_prev_start)
+                return self._count_line_indents(real_start)
+        return self._count_line_indents(current_start)
+
+
+    def _is_line_continued(self, line_contents):
+        if line_contents.endswith('\\'):
+            return True
+        current = len(line_contents) - 1
+        openings = 0
+        while current >= 0:
+            if line_contents[current] in list('([{'):
+                openings += 1
+            if line_contents[current] in list(')]}'):
+                openings -= 1
+            if openings > 0:
+                return True
+            current -= 1
+        return False
+
+
+    def _get_indentation_changes_caused_by_prev_line(self, prev_line):
+        new_indent = 0
+        if prev_line.rstrip().endswith(':'):
+            new_indent += 4
+        if prev_line.strip() == 'pass':
+            new_indent -= 4
+        if (prev_line.lstrip().startswith('return ') or
+            prev_line.lstrip().startswith('raise ')) and not self._is_line_continued(prev_line):
+            new_indent -= 4
+        if prev_line.strip() == 'break':
+            new_indent -= 4
+        if prev_line.strip() == 'continue':
+            new_indent -= 4
+        return new_indent
+        
+    def _get_indentation_changes_caused_by_current_line(self, current_line):
+        new_indent = 0
+        if current_line.strip() == 'else:':
+            new_indent -= 4
+        if current_line.strip() == 'finally:':
+            new_indent -= 4
+        if current_line.lstrip().startswith('except ') and current_line.rstrip().endswith(':'):
+            new_indent -= 4
+        return new_indent
+
+    def indent_line(self, index):
+        start = self._get_line_start(index)
+        self._set_line_indents(start, self._get_correct_indentation(start))
+
+import os
+import sys
+import subprocess
+
+import rope.core
+
+class Project(object):
+    '''A Project containing files and folders'''
+    def __init__(self, projectRootAddress):
+        self.root = projectRootAddress
+        if not os.path.exists(self.root):
+            os.mkdir(self.root)
+        elif not os.path.isdir(self.root):
+            raise rope.core.RopeException('Project root exists and is not a directory')
+    
+    def get_root_folder(self):
+        return Folder(self, '')
+
+    def get_root_address(self):
+        return self.root
+
+    def get_resource(self, resourceName):
+        path = self._get_resource_path(resourceName)
+        if not os.path.exists(path):
+            raise rope.core.RopeException('resource %s does not exist' % resourceName)
+        if os.path.isfile(path):
+            return File(self, resourceName)
+        if os.path.isdir(path):
+            return Folder(self, resourceName)
+        raise rope.core.RopeException('Unknown resource ' + resourceName)
+
+    def create_file(self, fileName):
+        filePath = self._get_resource_path(fileName)
+        if os.path.exists(filePath):
+            if not os.path.isfile(filePath):
+                raise rope.core.RopeException('File already exists')
+            else:
+                raise rope.core.RopeException('A folder with the same name as this file already exists')
+        try:
+            newFile = open(filePath, 'w')
+        except IOError, e:
+            raise rope.core.RopeException(e)
+        newFile.close()
+
+    def create_folder(self, folderName):
+        folderPath = self._get_resource_path(folderName)
+        if os.path.exists(folderPath):
+            if not os.path.isdir(folderPath):
+                raise rope.core.RopeException('A file with the same name as this folder already exists')
+            else:
+                raise rope.core.RopeException('Folder already exists')
+        os.mkdir(folderPath)
+
+    def _get_resource_path(self, name):
+        return os.path.join(self.root, *name.split('/'))
+
+    def _get_files_recursively(self, folder):
+        result = []
+        for file in folder.get_files():
+            if not file.get_name().endswith('.pyc'):
+                result.append(file)
+        for folder in folder.get_folders():
+            if not folder.get_name().startswith('.'):
+                result.extend(self._get_files_recursively(folder))
+        return result
+
+    def get_files(self):
+        return self._get_files_recursively(self.get_root_folder())
+
+    def _is_package(self, folder):
+        init_dot_py = folder.get_path() + '/__init__.py'
+        try:
+            init_dot_py_file = self.get_resource(init_dot_py)
+            if not init_dot_py_file.is_folder():
+                return True
+        except rope.core.RopeException:
+            pass
+        return False
+
+    def _find_source_folders(self, folder):
+        for resource in folder.get_files():
+            if resource.get_name().endswith('.py'):
+                return [folder]
+        for resource in folder.get_folders():
+            if self._is_package(resource):
+                return [folder]
+        result = []
+        for resource in folder.get_folders():
+            result.extend(self._find_source_folders(resource))
+        return result
+
+    def get_source_folders(self):
+        return self._find_source_folders(self.get_root_folder())
+
+    @staticmethod
+    def remove_recursively(file):
+        for root, dirs, files in os.walk(file, topdown=False):
+            for name in files:
+                os.remove(os.path.join(root, name))
+            for name in dirs:
+                os.rmdir(os.path.join(root, name))
+        if os.path.isdir(file):
+            os.rmdir(file)
+        else:
+            os.remove(file)
+
+
+class Resource(object):
+    '''Represents files and folders in a project'''
+    def remove(self):
+        '''Removes resource from the project'''
+
+    def get_name(self):
+        '''Returns the name of this resource'''
+    
+    def get_path(self):
+        '''Returns the path of this resource relative to the project root
+        
+        The path is the list of parent directories separated by '/' followed
+        by the resource name.
+        '''
+
+    def is_folder(self):
+        '''Returns true if the resouse is a folder'''
+
+    def get_project(self):
+        '''Returns the project this resource belongs to'''
+
+    def _get_real_path(self):
+        '''Returns the file system path of this resource'''
+
+
+class File(Resource):
+    '''Represents a file in a project'''
+    def __init__(self, project, fileName):
+        self.project = project
+        self.fileName = fileName
+    
+    def read(self):
+        return open(self.project._get_resource_path(self.fileName)).read()
+
+    def write(self, contents):
+        file = open(self.project._get_resource_path(self.fileName), 'w')
+        file.write(contents)
+        file.close()
+
+    def remove(self):
+        Project.remove_recursively(self.project._get_resource_path(self.fileName))
+
+    def get_name(self):
+        return self.fileName.split('/')[-1]
+
+    def get_path(self):
+        return self.fileName
+
+    def is_folder(self):
+        return False
+
+    def __eq__(self, resource):
+        if not isinstance(resource, File):
+            return False
+        return self.get_path() == resource.get_path()
+
+    def _get_real_path(self):
+        return self.project._get_resource_path(self.fileName)
+
+    def get_project(self):
+        return self.project
+
+
+class Folder(Resource):
+    '''Represents a folder in a project'''
+    def __init__(self, project, folderName):
+        self.project = project
+        self.folderName = folderName
+
+    def _getFolderPath(self):
+        return self.project._get_resource_path(self.folderName)
+    
+    def remove(self):
+        Project.remove_recursively(self.project._get_resource_path(self.folderName))
+
+    def get_name(self):
+        return self.folderName.split('/')[-1]
+
+    def get_path(self):
+        return self.folderName
+
+    def is_folder(self):
+        return True
+
+    def get_children(self):
+        '''Returns the children resources of this folder'''
+        path = self._getFolderPath()
+        result = []
+        content = os.listdir(path)
+        for name in content:
+            if self.get_path() != '':
+                resourceName = self.get_path() + '/' + name
+            else:
+                resourceName = name
+            result.append(self.project.get_resource(resourceName))
+        return result
+
+    def get_files(self):
+        result = []
+        for resource in self.get_children():
+            if not resource.is_folder():
+                result.append(resource)
+        return result
+
+    def get_folders(self):
+        result = []
+        for resource in self.get_children():
+            if resource.is_folder():
+                result.append(resource)
+        return result
+
+    def __eq__(self, resource):
+        if not isinstance(resource, Folder):
+            return False
+        return self.get_path() == resource.get_path()
+
+    def get_project(self):
+        return self.project
+
+    def _get_real_path(self):
+        return self.project._get_resource_path(self.folderName)
+
+
+class FileFinder(object):
+    def __init__(self, project):
+        self.project = project
+        self.last_keyword = None
+        self.last_result = None
+
+    def find_files_starting_with(self, starting):
+        '''Returns the Files in the project whose names starts with starting'''
+        files = []
+        if self.last_keyword is not None and starting.startswith(self.last_keyword):
+            files = self.last_result
+        else:
+            files = self.project.get_files()
+        result = []
+        for file in files:
+            if file.get_name().startswith(starting):
+                result.append(file)
+        self.last_keyword = starting
+        self.last_result = result
+        return result
+
+
+class PythonFileRunner(object):
+    '''A class for running python project files'''
+    def __init__(self, file, stdin=None, stdout=None):
+        self.file = file
+        file_path = self.file._get_real_path()
+        env = {}
+        env.update(os.environ)
+        source_folders = []
+        for folder in file.get_project().get_source_folders():
+            source_folders.append(os.path.abspath(folder._get_real_path()))
+        env['PYTHONPATH'] = env.get('PYTHONPATH', '') + ':' + \
+                            os.pathsep.join(source_folders)
+        self.process = subprocess.Popen(executable=sys.executable,
+                                        args=(sys.executable, self.file.get_name()),
+                                        cwd=os.path.split(file_path)[0], stdin=stdin,
+                                        stdout=stdout, stderr=stdout, env=env)
+
+    def wait_process(self):
+        '''Wait for the process to finish'''
+        self.process.wait()
+
+    def kill_process(self):
+        '''Stop the process. This does not work on windows.'''
+        os.kill(self.process.pid, 9)

rope/searching.py

+
+class SearchingState(object):
+    
+    def append_keyword(self, searcher, postfix):
+        '''Appends postfix to the keyword'''
+
+    def shorten_keyword(self, searcher):
+        '''Deletes the last char of keyword'''
+
+    def next_match(self, searcher):
+        '''Go to the next match'''
+
+class ForwardSearching(SearchingState):
+    def append_keyword(self, searcher, postfix):
+        start = searcher.editor.get_relative(searcher.editor.get_insert(), -len(searcher.keyword))
+        searcher.keyword += postfix
+        searcher._match(start)
+
+    def shorten_keyword(self, searcher):
+        if searcher.keyword == '':
+            return
+        searcher.keyword = searcher.keyword[:-1]
+        start = searcher.editor.get_relative(searcher.editor.get_insert(), -1)
+        searcher._match(start, forward=False)
+
+    def next_match(self, searcher):
+        if not searcher.keyword:
+            return
+        searcher._match(searcher.editor.get_insert())
+
+    def is_searching(self, searcher):
+        return True
+
+class BackwardSearching(SearchingState):
+    def append_keyword(self, searcher, postfix):
+        searcher.keyword += postfix
+        start = searcher.editor.get_relative(searcher.editor.get_insert(), +len(searcher.keyword))
+        searcher._match(start, forward=False, insert_side='left')
+
+    def shorten_keyword(self, searcher):
+        if not searcher.keyword:
+            return
+        searcher.keyword = searcher.keyword[:-1]
+        searcher._match(searcher.editor.get_insert(), insert_side='left')
+
+    def next_match(self, searcher):
+        if not searcher.keyword:
+            return
+        searcher._match(searcher.editor.get_insert(), forward=False, insert_side='left')
+
+    def is_searching(self, searcher):
+        return True
+
+class NotSearching(SearchingState):
+    '''A null object for when not searching'''
+
+    def append_keyword(self, searcher, postfix):
+        pass
+
+    def shorten_keyword(self, searcher):
+        pass
+
+    def next_match(self, searcher):
+        pass
+
+    def is_searching(self, searcher):
+        return False
+
+class Match(object):
+    def __init__(self, start, end, side='right'):
+        self.start = start
+        self.end = end
+        self.side = side
+
+class Searcher(object):
+    '''A class for searching GraphicalEditors'''
+
+    def __init__(self, editor):
+        self.editor = editor
+        self.keyword = ''
+        self.searching_state = NotSearching()
+        self.current_match = None
+
+    def start_searching(self):
+        self.keyword = ''
+        self.starting_index = self.editor.get_insert()
+        self.searching_state = ForwardSearching()
+        self.current_match = Match(self.starting_index, self.starting_index)
+
+    def end_searching(self):
+        self.searching_state = NotSearching()
+        self.current_match = Match(self.editor.get_insert(), self.editor.get_insert())
+        self.editor.highlight_match(self.current_match)
+
+    def is_searching(self):
+        return self.searching_state.is_searching(self)
+
+    def append_keyword(self, postfix):
+        self.searching_state.append_keyword(self, postfix)
+
+    def shorten_keyword(self):
+        self.searching_state.shorten_keyword(self)
+
+    def cancel_searching(self):
+        self.searching_state = NotSearching()
+        self.current_match = Match(self.starting_index, self.starting_index)
+        self.editor.highlight_match(self.current_match)
+
+    def configure_search(self, forward=True):
+        if forward and self.searching_state.is_searching(self) and \
+               not isinstance(self.searching_state, ForwardSearching):
+            self.searching_state = ForwardSearching()
+        if not forward and self.searching_state.is_searching(self) and \
+               not isinstance(self.searching_state, BackwardSearching):
+            self.searching_state = BackwardSearching()
+
+    def next_match(self):
+        self.searching_state.next_match(self)
+
+    def _match(self, start, forward=True, insert_side='right'):
+        if self.keyword:
+            case = False
+            if not self.keyword.islower():
+                case = True
+            found = self.editor.search(self.keyword, start, case=case, forwards=forward)
+            if found:
+                found_end = self.editor.get_relative(found, len(self.keyword))
+                self.current_match = Match(found, found_end, insert_side)
+                self.editor.highlight_match(self.current_match)
+        else:
+            self.current_match = Match(self.starting_index, self.starting_index)
+            self.editor.highlight_match(self.current_match)
+
+    def get_match(self):
+        return self.current_match

Empty file added.

ropetest/coretest.py

+import os
+import unittest
+
+from rope.core import Core, RopeException
+from ropetest.projecttest import SampleProjectMaker
+
+class CoreTest(unittest.TestCase):
+
+    def setUp(self):
+        unittest.TestCase.setUp(self)
+        Core.get_core().close_project()
+        self.projectMaker = SampleProjectMaker()
+        self.fileName = self.projectMaker.getSampleFileName()
+        self.fileName2 = 'samplefile2.txt'
+        file = open(self.fileName, 'w')
+        file.write('sample text')
+        file.close()
+        Core.get_core().open_project(self.projectMaker.getRoot())
+        self.textEditor = Core.get_core().open_file(self.fileName)
+        self.project = Core.get_core().get_open_project()
+
+    def tearDown(self):
+        self.projectMaker.removeAll()
+        os.remove(self.fileName)
+        if os.path.exists(self.fileName2):
+            os.remove(self.fileName2)
+        unittest.TestCase.tearDown(self)
+
+    def testOpeningFiles(self):
+        self.assertEquals(self.projectMaker.getSampleFileContents(), self.textEditor.get_editor().get_text())
+
+    def testActiveEditor(self):
+        self.assertEquals(self.textEditor, Core.get_core().get_active_editor())
+        newEditor = Core.get_core().open_file(self.fileName)
+        self.assertEquals(newEditor, Core.get_core().get_active_editor())
+
+    def testSaving(self):
+        self.textEditor.get_editor().set_text('another text')
+        Core.get_core().save_file()
+
+    def testErrorWhenOpeningANonExistentFile(self):
+        try:
+            Core.get_core().open_file(self.fileName2)
+            self.fail('Should have thrown exception; file doesn\'t exist')
+        except RopeException:
+            pass
+    
+    def testMakingNewFiles(self):
+        editor = Core.get_core().create_file(self.fileName2)
+        editor.get_editor().set_text('file2')
+        editor.save()
+
+    def testErrorWhenMakingAlreadyExistantFile(self):
+        try:
+            editor = Core.get_core().create_file(self.fileName)
+            self.fail('Show have throws exception; file already exists')
+        except RopeException:
+            pass
+
+    def testCreatingFolder(self):
+        Core.get_core().create_folder('SampleFolder')
+
+    def test_running_current_editor(self):
+        self.project.create_file('sample.py')
+        self.project.create_file('output.txt')
+        sample_file = self.project.get_resource('sample.py')
+        sample_file.write("file = open('output.txt', 'w')\nfile.write('run')\nfile.close()\n")
+        Core.get_core().open_file('sample.py')
+        runner = Core.get_core().run_active_editor()
+        runner.wait_process()
+        self.assertEquals('run', self.project.get_resource('output.txt').read())
+
+    def test_not_reopening_editors(self):
+        editor1 = Core.get_core().open_file(self.projectMaker.getSampleFileName())
+        editor2 = Core.get_core().open_file(self.projectMaker.getSampleFileName())
+        self.assertTrue(editor1 is editor2)
+    
+    def test_closing_editor(self):
+        editor = Core.get_core().open_file(self.projectMaker.getSampleFileName())
+        self.assertEquals(Core.get_core().get_active_editor(), editor)
+        Core.get_core().close_active_editor()
+        self.assertNotEquals(Core.get_core().get_active_editor(), editor)
+
+    def test_closing_the_last_editor(self):
+        Core.get_core().close_active_editor()
+        self.assertTrue(Core.get_core().get_active_editor() is None)
+
+if __name__ == '__main__':
+    unittest.main()

ropetest/editortest.py

+import unittest
+
+from rope.core import Core
+from rope.searching import Searcher
+from ropetest.mockeditortest import GraphicalEditorFactory, MockEditorFactory
+from rope.indenter import PythonCodeIndenter
+
+class GraphicalEditorTest(unittest.TestCase):
+    __factory = GraphicalEditorFactory()
+    def setUp(self):
+        unittest.TestCase.setUp(self)
+        self.editor = self.__factory.create()
+        self.editor.set_text('sample text')
+    
+    def tearDown(self):
+        unittest.TestCase.tearDown(self)
+
+    def test_undo(self):
+        self.editor.undoSeparator()
+        self.editor.insert(self.editor.get_end(), '.')
+        self.assertEquals('sample text.', self.editor.get_text())
+        self.editor.undo()
+        self.assertEquals('sample text', self.editor.get_text(),self.editor.get_text())
+
+    def test_redo(self):
+        self.editor.undoSeparator()
+        self.editor.insert(self.editor.get_end(), '.')
+        self.editor.undo()
+        self.editor.redo()
+        self.assertEquals('sample text.', self.editor.get_text(),self.editor.get_text())
+
+    def test_nothing_to_undo(self):
+        self.editor.undo()
+        self.editor.undo()
+        self.editor.undo()
+
+    def test_nothing_to_redo(self):
+        self.editor.redo()
+
+    def test_copying(self):
+        self.editor.setMark()
+        self.editor.goToTheEnd()
+        self.editor.copyRegion()
+        self.editor.paste()
+        self.assertEquals('sample textsample text', self.editor.get_text())
+
+    def test_copying_in_the_middle(self):
+        self.editor.nextWord()
+        self.editor.setMark()
+        self.editor.goToTheEnd()
+        self.editor.copyRegion()
+        self.editor.goToTheStart()
+        self.editor.paste()
+        self.assertEquals(' textsample text', self.editor.get_text())
+
+    def test_cutting(self):
+        self.editor.setMark()
+        self.editor.nextWord()
+        self.editor.cutRegion()
+        self.assertEquals(' text', self.editor.get_text())
+        self.editor.paste()
+        self.assertEquals('sample text', self.editor.get_text())
+
+    def test_mark_not_set(self):
+        self.editor.cutRegion()
+        self.editor.copyRegion()
+        self.assertEquals('sample text', self.editor.get_text())
+
+    def test_clear_mark(self):
+        self.editor.setMark()
+        self.editor.nextWord()
+        self.editor.clearMark()
+        self.editor.cutRegion()
+        self.assertEquals('sample text', self.editor.get_text())
+
+    def test_when_insert_while_mark_precedes(self):
+        self.editor.nextWord()
+        self.editor.setMark()
+        self.editor.goToTheStart()
+        self.editor.cutRegion()
+        self.assertEquals(' text', self.editor.get_text())
+
+    def test_swap_mark_and_insert(self):
+        self.editor.setMark()
+        self.editor.nextWord()
+        self.editor.swapMarkAndInsert()
+        self.assertEquals(self.editor.get_start(), self.editor.get_insert())
+        self.editor.cutRegion()
+        self.assertEquals(' text', self.editor.get_text())
+
+    def test_no_mark_swap_mark_and_insert(self):
+        self.editor.swapMarkAndInsert()
+        self.assertEquals('sample text', self.editor.get_text())
+
+    def test_swap_mark_and_insert_while_insert_precedes(self):
+        self.editor.nextWord()
+        self.editor.setMark()
+        self.editor.goToTheStart()
+        self.editor.swapMarkAndInsert()
+        self.assertEquals(self.editor.get_index(6), self.editor.get_insert())
+        self.editor.cutRegion()
+        self.assertEquals(' text', self.editor.get_text())
+
+    def test_insert_tab(self):
+        self.editor.set_text('')
+        self.editor.insertTab()
+        self.assertEquals((' ' * 4), self.editor.get_text())
+        self.editor.insertTab(self.editor.get_end())
+        self.assertEquals((' ' * 8), self.editor.get_text())
+
+    def test_clear_undo(self):
+        self.editor.set_text('sample text')
+        self.editor.clear_undo()
+        self.editor.undo()
+        self.assertEquals('sample text', self.editor.get_text())
+
+
+if __name__ == '__main__':
+    unittest.main()
+

ropetest/fileeditortest.py

+import unittest
+
+from rope.editor import *
+from rope.fileeditor import *
+from rope.project import Project
+from ropetest.mockeditor import MockEditor
+from ropetest.projecttest import SampleProjectMaker
+
+class FileEditorTest(unittest.TestCase):
+    
+    def setUp(self):
+        unittest.TestCase.setUp(self)
+        self.text = MockEditor()
+        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)
+    
+    def tearDown(self):
+        self.projectMaker.removeAll()
+        unittest.TestCase.tearDown(self)
+        
+    def testCreation(self):
+        self.assertEquals(self.projectMaker.getSampleFileContents(),
+                          self.editor.get_editor().get_text())
+
+    def testSaving(self):
+        self.text.set_text('another text')
+        self.editor.save()
+        self.assertEquals('another text', self.project.get_resource(self.fileName).read())
+
+
+if __name__ == '__main__':
+    unittest.main()

ropetest/highlighttest.py

+import unittest
+
+from ropetest.mockeditortest import GraphicalEditorFactory, MockEditorFactory
+from rope.highlight import PythonHighlighting, HighlightingStyle
+
+class HighlightTest(unittest.TestCase):
+    __factory = MockEditorFactory()
+    def setUp(self):
+        self.editor = self.__factory.create()
+        self.highlighting = PythonHighlighting(self.editor)
+        unittest.TestCase.setUp(self)
+    
+    def tearDown(self):
+        unittest.TestCase.tearDown(self)
+
+    def _assertOutcomesEquals(self, text, expected, not_expected=[], start=None, end=None):
+        self.editor.set_text(text)
+        startIndex = self.editor.get_start()
+        if start is not None:
+            startIndex = self.editor.get_index(start)
+        endIndex = self.editor.get_end()
+        if end is not None:
+            endIndex = self.editor.get_index(end)
+        highlights = []
+        for result in self.highlighting.highlights(startIndex, endIndex):
+            highlights.append(result)
+        for highlight in expected:
+            current = (self.editor.get_index(highlight[0]),
+                       self.editor.get_index(highlight[1]), highlight[2])
+            self.assertTrue(current in highlights)
+        for highlight in not_expected:
+            current = (self.editor.get_index(highlight[0]),
+                       self.editor.get_index(highlight[1]), highlight[2])
+            self.assertTrue(current not in highlights)
+
+    def testKeywordHighlighting(self):
+        text = 'def sample_function():\n    pass\n'
+        highs = [(0, 3, 'keyword'), (27, 31, 'keyword')]
+        self._assertOutcomesEquals(text, highs)
+
+    def testKeywordHighlighting2(self):
+        text = 'import re\nclass Test(object):    def f(self):\npass\n'
+        highs = [(0, 6, 'keyword'), (10, 15, 'keyword'),
+                 (33, 36, 'keyword'), (46, 50, 'keyword')]
+        self._assertOutcomesEquals(text, highs)
+
+    def testKeywordHighlighting3(self):
+        text = '   for x in range(10):'
+        highs = [(3, 6, 'keyword'), (9, 11, 'keyword')]
+        self._assertOutcomesEquals(text, highs)
+
+    def test_not_highlighting_keywords_when_partof_other_words(self):
+        text = 'class_'
+        not_highs = [(0, 5, 'keyword')]
+        self._assertOutcomesEquals(text, [], not_highs)
+
+    def test_not_highlighting_keywords_when_partof_other_words2(self):
+        text = 'in_for_class = def3 + _def + def_ + def_while'
+        not_highs = [(0, 2, 'keyword'), (3, 6, 'keyword'), (7, 12, 'keyword'),
+                     (0, 2, 'keyword'), (15, 18, 'keyword'), (29, 32, 'keyword'),
+                     (36, 39, 'keyword'), (40, 45, 'keyword')]
+        self._assertOutcomesEquals(text, [], not_highs)
+
+    def test_no_highlighting(self):
+        from rope.highlight import NoHighlighting
+        noHigh = NoHighlighting()
+        text = 'def sample_function():\n    pass\n'
+        expected = []
+        for result in noHigh.highlights(None, None):
+            self.assertEquals(expected[0], result)
+            del expected[0]
+        self.assertFalse(expected)
+
+    def test_get_styles(self):
+        self.assertEquals(True, self.highlighting.getStyles().has_key('keyword'))
+        self.assertTrue(isinstance(self.highlighting.getStyles()['keyword'], HighlightingStyle))
+
+    def test_following_keywords(self):
+        text = 'if not'
+        highs = [(0, 2, 'keyword'), (3, 6, 'keyword')]
+        self._assertOutcomesEquals(text, highs)
+
+    def test_keywords_in_strings(self):
+        text = 's = " def "'
+        not_highs = [(6, 9, 'keyword')]
+        self._assertOutcomesEquals(text, [], not_highs)
+
+    def test_function_definition(self):
+        text = 'def func(args):'
+        highs = [(0, 3, 'keyword'), (4, 8, 'definition')]
+        self._assertOutcomesEquals(text, highs)
+        
+    def test_class_definition(self):
+        self.assertTrue('definition' in self.highlighting.getStyles())
+        text = 'class Sample(object):'
+        highs = [(0, 5, 'keyword'), (6, 12, 'definition')]
+        self._assertOutcomesEquals(text, highs)
+
+    def test_comments(self):
+        self.assertTrue('comment' in self.highlighting.getStyles())
+        text = 'a = 2 # Hello world\ntest = 12'
+        highs = [(6, 19, 'comment')]
+        self._assertOutcomesEquals(text, highs)
+
+    def test_long_strings(self):
+        self.assertTrue('string' in self.highlighting.getStyles())
+        text = "a = '''2 # multiline \n comments'''\nb = 2"
+        highs = [(4, 34, 'string')]
+        self._assertOutcomesEquals(text, highs)
+
+    def test_highlighting_a_part_of_editor(self):
+        text = 'print a\nprint b\nprint c'
+        highs = [(8, 13, 'keyword')]
+        not_highs = [(0, 5, 'keyword'), (16, 21, 'keyword')]
+        self._assertOutcomesEquals(text, highs, not_highs, start=8, end=15)
+        
+    def test_highlighting_builtins(self):
+        self.assertTrue('builtin' in self.highlighting.getStyles())
+        text = 'a = None'
+        highs = [(4, 8, 'builtin')]
+        self._assertOutcomesEquals(text, highs)
+        
+
+if __name__ == '__main__':
+    unittest.main()

ropetest/indentertest.py

+import unittest
+
+from ropetest.mockeditortest import GraphicalEditorFactory, MockEditorFactory
+from rope.indenter import PythonCodeIndenter
+
+class PythonCodeIndenterTest(unittest.TestCase):
+    __factory = MockEditorFactory()
+    def setUp(self):
+        unittest.TestCase.setUp(self)
+        self.editor = self.__factory.create()
+        self.indenter = PythonCodeIndenter(self.editor)
+    
+    def tearDown(self):
+        unittest.TestCase.tearDown(self)
+
+    def test_python_indenter(self):
+        self.editor.set_text('print "hello"\n')
+        self.indenter.indent_line(self.editor.get_start())
+        self.assertEquals('print "hello"\n', self.editor.get_text())
+
+    def test_indenting_lines_with_indented_previous_line(self):
+        self.editor.set_text('def f():\n    g()\nh()\n')
+        self.indenter.indent_line(self.editor.get_relative(self.editor.get_end(), -2))
+        self.assertEquals('def f():\n    g()\n    h()\n', self.editor.get_text())
+
+    def test_indenting_lines_with_indented_previous_line(self):
+        self.editor.set_text('def f():\n    g()\nh()\n')
+        self.indenter.indent_line(self.editor.get_relative(self.editor.get_end(), -2))
+        self.assertEquals('def f():\n    g()\n    h()\n', self.editor.get_text())
+
+    def test_indenting_lines_when_prev_line_ends_with_a_colon(self):
+        self.editor.set_text('def f():\ng()')
+        self.indenter.indent_line(self.editor.get_relative(self.editor.get_end(), -2))
+        self.assertEquals('def f():\n    g()', self.editor.get_text())
+
+    def test_empty_prev_line(self):
+        self.editor.set_text('    \nprint "hello"\n')
+        self.indenter.indent_line(self.editor.get_index(10))
+        self.assertEquals('    \nprint "hello"\n', self.editor.get_text())
+
+    def test_indenting_lines_with_indented_previous_line_with_empty_line(self):
+        self.editor.set_text('def f():\n    g()\n\nh()\n')
+        self.indenter.indent_line(self.editor.get_relative(self.editor.get_end(), -2))
+        self.assertEquals('def f():\n    g()\n\n    h()\n', self.editor.get_text())
+
+    def test_indenting_lines_when_prev_line_ends_with_a_colon_with_empty_line(self):
+        self.editor.set_text('def f():\n\ng()')
+        self.indenter.indent_line(self.editor.get_relative(self.editor.get_end(), -2))
+        self.assertEquals('def f():\n\n    g()', self.editor.get_text())
+
+    def test_indenting_lines_at_start(self):
+        self.editor.set_text('    g()\n')
+        self.indenter.indent_line(self.editor.get_start())
+        self.assertEquals('g()\n', self.editor.get_text())
+
+    def test_indenting_lines_with_start_previous_line(self):
+        self.editor.set_text('\n    g()\n')
+        self.indenter.indent_line(self.editor.get_relative(self.editor.get_end(), -1))
+        self.assertEquals('\ng()\n', self.editor.get_text())
+
+    def test_deindenting_after_pass(self):
+        self.editor.set_text('def f():\n    pass\n    f()')
+        self.indenter.indent_line(self.editor.get_end())
+        self.assertEquals('def f():\n    pass\nf()', self.editor.get_text())
+        
+    def test_explicit_line_continuation(self):
+        self.editor.set_text('c = a + \\\nb')
+        self.indenter.indent_line(self.editor.get_end())
+        self.assertEquals('c = a + \\\n    b', self.editor.get_text())