Skip to content

Instantly share code, notes, and snippets.

@etherald
Last active May 26, 2020 18:18
Show Gist options
  • Save etherald/91b37ffdc27ff4923401 to your computer and use it in GitHub Desktop.
Save etherald/91b37ffdc27ff4923401 to your computer and use it in GitHub Desktop.
Edile is a basic but useful text editor for Linux, Mac OSX and other unix-like operating systems. Written in Python, Edile is implemented in a single source code file and requires no installation - you simply execute the source code file. Edile requires Python, PyGtk and if you have GtkSourceView installed it will use that for syntax highlightin…
#!/usr/bin/env python
########################################################################
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# -----------------------------------------------------------------------------
#
# This is Edile: A PyGTK+ text editor.
# http://edile.googlecode.com
# Edile is a basic but useful text editor implemented in one source code file.
# It requires only PyGTK but if you have pygtksourceview2 installed it will use that.
#
# This file is based on part of the tutorial: Linux GUI Programming with GTK+ and Glade3
# http://www.micahcarrick.com/12-24-2007/gtk-glade-tutorial-part-1.html
########################################################################
# edit default configuration here
#=======================================================================
CONF_FONT = "inconsolata 10"
CONF_HIGHLIGHT_CURRENT_LINE = True
CONF_HIGHLIGHT_MATCHING_BRACKETS = True
CONF_OVERWRITE = False # insert/overwrite mode
CONF_SHOW_LINE_NUMBERS = True
CONF_AUTO_INDENT = True
CONF_INDENT_ON_TAB = True
CONF_SHOW_RIGHT_MARGIN = True
CONF_RIGHT_MARGIN_POSITION = 72
CONF_HIGHLIGHT_SYNTAX = True
CONF_SPACES_INSTEAD_OF_TABS = True
CONF_TAB_WIDTH = 4
CONF_INDENT_WIDTH = -1 # -1 means use same as tab width
CONF_MAX_UNDO_LEVELS = 50 # -1 means no limit
CONF_HIGHLIGHT_CHANGES_SINCE_SAVE = True
# color for highlighting changes since last save
CONF_CHANGE_HIGHLIGHT = "dark slate gray" # from /etc/X11/rgb.txt
# color for highlighting find/replace matches
CONF_FIND_HIGHLIGHT = "IndianRed4"
# gtksrcview color scheme
CONF_STYLE_SCHEME = ("oblivion","kate","tango","classic")
# 'note pad'. will open this file on startup instead of untitled. empty string means no default file.
CONF_DEFAULT_FILE = ""
# whether or not to load plugins. off by default.
# plugins provide a way for you to customise edile for your purpose or environment
# without changing the application's source code.
CONF_LOAD_PLUGINS = False
# whether to load plugins when running as the super user. 'no' by default.
CONF_LOAD_PLUGINS_AS_ROOT = False
# url of file to load plugins from. any scheme should work (e.g. file:///, http://)
# default is http://edile.googlecode.com/files/edile_plugins-0.1.8.py
# see that file for a description of the plugin interface.
# as of version 0.1.8 options can also be set from that file.
CONF_PLUGIN_LOCATION = "http://edile.googlecode.com/files/edile_plugins-0.1.7.py"
# sha 256 hash of the plugin file for authentication. empty string means no authentication.
CONF_PLUGIN_SHA256 = "eca99d7b7ae2042a742366d7b389857d7fa2ee496573d7dee4ebb5a885b53288"
# move whichever you want as default into the first position
CONF_WRAP = ('None','Character','Word')
CONF_SMART_HOME_END_TYPE = ('Before','Disabled','After','Always')
#=======================================================================
# end configuration
# stuff here you probably shouldn't edit
#=======================================================================
EDILE_VERSION = '0.2'
EDILE_URL = 'http://edile.googlecode.com'
EDILE_NAME = 'Edile'
EDILE_DESCRIPTION = 'a pygtk text editor in one file.'
#=======================================================================
# here begins the program edile
# TODO: command line options (verbosity, language, conf options)
# improve find code. handle everything internal to edile w/o relying on gtksrcview features eg. case sensitivity
# config option for encoding to use when saving file instead of always file's encoding
# generally firm everything up. condense and refactor code, exception handling, etc.
# UI definition. GTK builder XML string
U_I = '''
<?xml version="1.0"?>
<interface>
<object class="GtkUIManager" id="uimanager1">
<child>
<object class="GtkActionGroup" id="actiongroup1">
<child>
<object class="GtkAction" id="file_menu">
<property name="name">file_menu</property>
<property name="label" translatable="yes">_File</property>
</object>
</child>
<child>
<object class="GtkAction" id="new_menu_item">
<property name="stock_id">gtk-new</property>
<property name="name">new_menu_item</property>
<property name="label" translatable="yes">_New</property>
<signal handler="on_new_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="new_window_menu_item">
<property name="stock_id">gtk-new</property>
<property name="name">new_window_menu_item</property>
<property name="label" translatable="yes">N_ew Window</property>
<signal handler="on_new_window_menu_item_activate" name="activate"/>
</object>
<accelerator key="N" modifiers="GDK_CONTROL_MASK|GDK_SHIFT_MASK"/>
</child>
<child>
<object class="GtkAction" id="open_menu_item">
<property name="stock_id">gtk-open</property>
<property name="name">open_menu_item</property>
<property name="label" translatable="yes">_Open</property>
<signal handler="on_open_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="open_in_new_window_menu_item">
<property name="stock_id">gtk-open</property>
<property name="name">open_in_new_window_menu_item</property>
<property name="label" translatable="yes">Open _In New Window</property>
<signal handler="on_open_in_new_window_menu_item_activate" name="activate"/>
</object>
<accelerator key="O" modifiers="GDK_CONTROL_MASK|GDK_SHIFT_MASK"/>
</child>
<child>
<object class="GtkAction" id="save_menu_item">
<property name="stock_id">gtk-save</property>
<property name="name">save_menu_item</property>
<property name="label" translatable="yes">_Save</property>
<signal handler="on_save_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="save_as_menu_item">
<property name="stock_id">gtk-save-as</property>
<property name="name">save_as_menu_item</property>
<property name="label" translatable="yes">Save _As</property>
<signal handler="on_save_as_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="reload_menu_item">
<property name="stock_id">gtk-reload</property>
<property name="name">reload_menu_item</property>
<property name="label" translatable="yes">Reload</property>
<signal handler="on_reload_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="execute_menu_item">
<property name="stock_id">gtk-execute</property>
<property name="name">execute_menu_item</property>
<property name="label" translatable="yes">_Run In Terminal</property>
<signal handler="on_execute_menu_item_activate" name="activate"/>
</object>
<accelerator key="R" modifiers="GDK_CONTROL_MASK"/>
</child>
<child>
<object class="GtkAction" id="close_menu_item">
<property name="stock_id">gtk-close</property>
<property name="name">close_menu_item</property>
<property name="label" translatable="yes">_Close</property>
<signal handler="on_close_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="quit_menu_item">
<property name="stock_id">gtk-quit</property>
<property name="name">quit_menu_item</property>
<property name="label" translatable="yes">_Quit</property>
<signal handler="on_quit_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="edit_menu">
<property name="name">edit_menu</property>
<property name="label" translatable="yes">_Edit</property>
</object>
</child>
<child>
<object class="GtkAction" id="undo_menu_item">
<property name="stock_id">gtk-undo</property>
<property name="name">undo_menu_item</property>
<property name="label" translatable="yes">_Undo</property>
<signal handler="on_undo_menu_item_activate" name="activate"/>
</object>
<accelerator key="Z" modifiers="GDK_CONTROL_MASK"/>
</child>
<child>
<object class="GtkAction" id="redo_menu_item">
<property name="stock_id">gtk-redo</property>
<property name="name">redo_menu_item</property>
<property name="label" translatable="yes">_Redo</property>
<signal handler="on_redo_menu_item_activate" name="activate"/>
</object>
<accelerator key="Z" modifiers="GDK_CONTROL_MASK|GDK_SHIFT_MASK"/>
</child>
<child>
<object class="GtkAction" id="cut_menu_item">
<property name="stock_id">gtk-cut</property>
<property name="name">cut_menu_item</property>
<property name="label" translatable="yes">Cu_t</property>
<signal handler="on_cut_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="copy_menu_item">
<property name="stock_id">gtk-copy</property>
<property name="name">copy_menu_item</property>
<property name="label" translatable="yes">_Copy</property>
<signal handler="on_copy_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="paste_menu_item">
<property name="stock_id">gtk-paste</property>
<property name="name">paste_menu_item</property>
<property name="label" translatable="yes">_Paste</property>
<signal handler="on_paste_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="select_all_menu_item">
<property name="stock_id">gtk-select-all</property>
<property name="name">select_all_menu_item</property>
<property name="label" translatable="yes">Select _All</property>
<signal handler="on_select_all_menu_item_activate" name="activate"/>
</object>
<accelerator key="A" modifiers="GDK_CONTROL_MASK"/>
</child>
<child>
<object class="GtkAction" id="delete_menu_item">
<property name="stock_id">gtk-delete</property>
<property name="name">delete_menu_item</property>
<property name="label" translatable="yes">_Delete</property>
<signal handler="on_delete_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="search_menu">
<property name="name">search_menu</property>
<property name="label" translatable="yes">_Search</property>
</object>
</child>
<child>
<object class="GtkAction" id="find_prev_selected_menu_item">
<property name="name">find_prev_selected_menu_item</property>
<property name="label" translatable="yes">Find Previous Selected</property>
<signal handler="on_find_prev_selected_menu_item_activate" name="activate"/>
</object>
<accelerator key="E" modifiers="GDK_CONTROL_MASK"/>
</child>
<child>
<object class="GtkAction" id="find_next_selected_menu_item">
<property name="name">find_next_selected_menu_item</property>
<property name="label" translatable="yes">Find Next Selected</property>
<signal handler="on_find_next_selected_menu_item_activate" name="activate"/>
</object>
<accelerator key="D" modifiers="GDK_CONTROL_MASK"/>
</child>
<child>
<object class="GtkAction" id="show_find_menu_item">
<property name="stock_id">gtk-find</property>
<property name="name">show_find_menu_item</property>
<property name="label" translatable="yes">_Find &amp; Replace</property>
<signal handler="on_show_find_menu_item_activate" name="activate"/>
</object>
<accelerator key="F" modifiers="GDK_CONTROL_MASK"/>
</child>
<child>
<object class="GtkAction" id="find_next_menu_item">
<property name="stock_id">gtk-go-forward</property>
<property name="name">find_next_menu_item</property>
<property name="label" translatable="yes">Find _Next</property>
<signal handler="on_find_next_menu_item_activate" name="activate"/>
</object>
<accelerator key="G" modifiers="GDK_CONTROL_MASK"/>
</child>
<child>
<object class="GtkAction" id="find_previous_menu_item">
<property name="stock_id">gtk-go-back</property>
<property name="name">find_previous_menu_item</property>
<property name="label" translatable="yes">Find _Previous</property>
<signal handler="on_find_previous_menu_item_activate" name="activate"/>
</object>
<accelerator key="G" modifiers="GDK_CONTROL_MASK|GDK_SHIFT_MASK"/>
</child>
<child>
<object class="GtkAction" id="find_all_menu_item">
<property name="name">find_all_menu_item</property>
<property name="label" translatable="yes">Find All</property>
<signal handler="on_find_all_menu_item_activate" name="activate"/>
</object>
<!--<accelerator key="F" modifiers="GDK_CONTROL_MASK|GDK_SHIFT_MASK"/>-->
</child>
<!-- <child>
<object class="GtkAction" id="replace_next_menu_item">
<property name="stock_id">gtk-find-and-replace</property>
<property name="name">replace_next_menu_item</property>
<property name="label" translatable="yes">Replace Next</property>
<signal handler="on_replace_next_menu_item_activate" name="activate"/>
</object>
<accelerator key="G" modifiers="GDK_CONTROL_MASK|GDK_SHIFT_MASK|GDK_MOD1_MASK"/>
</child> -->
<child>
<object class="GtkAction" id="replace_all_menu_item">
<property name="name">replace_all_menu_item</property>
<property name="label" translatable="yes">Replace All</property>
<signal handler="on_replace_all_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="view_menu">
<property name="name">view_menu</property>
<property name="label" translatable="yes">_View</property>
</object>
</child>
<child>
<object class="GtkAction" id="wrap_menu">
<property name="name">wrap_menu</property>
<property name="label" translatable="yes">W_rap</property>
</object>
</child>
<child>
<object class="GtkRadioAction" id="wrap_none_menu_item">
<property name="name">wrap_none_menu_item</property>
<property name="label" translatable="yes">_None</property>
<!--<property name="group">wrap_none_menu_item</property>-->
<signal handler="on_wrap_none_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkRadioAction" id="wrap_char_menu_item">
<property name="name">wrap_char_menu_item</property>
<property name="label" translatable="yes">_Character</property>
<property name="group">wrap_none_menu_item</property>
<signal handler="on_wrap_char_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkRadioAction" id="wrap_word_menu_item">
<property name="name">wrap_word_menu_item</property>
<property name="label" translatable="yes">_Word</property>
<property name="group">wrap_none_menu_item</property>
<signal handler="on_wrap_word_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="select_font_menu_item">
<property name="name">select_font_menu_item</property>
<property name="label" translatable="yes">Select Font...</property>
<signal handler="on_select_font_menu_item_activate" name="activate"/>
</object>
</child>
<child>
<object class="GtkAction" id="help_menu">
<property name="name">help_menu</property>
<property name="label" translatable="yes">_Help</property>
</object>
</child>
<child>
<object class="GtkAction" id="view_source_menu_item">
<!--<property name="stock_id">gtk-help</property>-->
<property name="name">view_source_menu_item</property>
<property name="label" translatable="yes">_View Source</property>
<signal handler="on_view_source_menu_item_activate" name="activate"/>
</object>
<!--<accelerator key="" modifiers=""/>-->
</child>
<child>
<object class="GtkAction" id="about_menu_item">
<property name="stock_id">gtk-about</property>
<property name="name">about_menu_item</property>
<property name="label" translatable="yes">_About</property>
<signal handler="on_about_menu_item_activate" name="activate"/>
</object>
</child>
</object>
</child>
<ui>
<menubar name="menubar1">
<menu action="file_menu">
<menuitem action="new_menu_item"/>
<menuitem action="new_window_menu_item"/>
<separator/>
<menuitem action="open_menu_item"/>
<menuitem action="open_in_new_window_menu_item"/>
<separator/>
<menuitem action="save_menu_item"/>
<menuitem action="save_as_menu_item"/>
<separator/>
<menuitem action="reload_menu_item"/>
<menuitem action="execute_menu_item"/>
<separator/>
<menuitem action="close_menu_item"/>
<menuitem action="quit_menu_item"/>
</menu>
<menu action="edit_menu">
<menuitem action="undo_menu_item"/>
<menuitem action="redo_menu_item"/>
<separator/>
<menuitem action="cut_menu_item"/>
<menuitem action="copy_menu_item"/>
<menuitem action="paste_menu_item"/>
<menuitem action="select_all_menu_item"/>
<separator/>
<menuitem action="delete_menu_item"/>
</menu>
<menu action="search_menu">
<menuitem action="find_prev_selected_menu_item"/>
<menuitem action="find_next_selected_menu_item"/>
<separator/>
<menuitem action="show_find_menu_item"/>
<menuitem action="find_next_menu_item"/>
<menuitem action="find_previous_menu_item"/>
<menuitem action="find_all_menu_item"/>
<separator/>
<!--<menuitem action="replace_next_menu_item"/>-->
<menuitem action="replace_all_menu_item"/>
</menu>
<menu action="view_menu">
<menu action="wrap_menu">
<menuitem action="wrap_none_menu_item"/>
<menuitem action="wrap_char_menu_item"/>
<menuitem action="wrap_word_menu_item"/>
</menu>
<separator/>
<menuitem action="select_font_menu_item"/>
</menu>
<menu action="help_menu">
<menuitem action="view_source_menu_item"/>
<menuitem action="about_menu_item"/>
</menu>
</menubar>
</ui>
</object>
<object class="GtkWindow" id="window">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="title" translatable="yes">Edile Text Editor</property>
<property name="default_width">540</property>
<property name="default_height">660</property>
<signal handler="on_window_destroy" name="destroy"/>
<signal handler="on_window_delete_event" name="delete_event"/>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<object class="GtkMenuBar" constructor="uimanager1" id="menubar1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
</object>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">0</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="left_margin">2</property>
<property name="right_margin">2</property>
<property name="wrap_mode">GTK_WRAP_NONE</property>
<!--<signal handler="on_text_view_insert_at_cursor" name="insert-at-cursor"/>-->
<!--<signal name="drag_data_received" handler="on_log_drag_data_received"/>-->
</object>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkStatusbar" id="statusbar">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">2</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
'''
# for the search ui, because it was easier this way.
SEARCH_UI = '''
<?xml version="1.0"?>
<interface>
<!-- search ui -->
<object class="GtkWindow" id="search_window">
<signal handler="on_search_window_delete_event" name="delete_event"/>
<property name="skip-taskbar-hint">True</property>
<child>
<object class="GtkVBox" id="vbox9">
<property name="visible">True</property>
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">Find</property>
<property name="justify">GTK_JUSTIFY_RIGHT</property>
</object>
</child>
<child>
<object class="GtkEntry" id="search_find_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<signal handler="search_find_field_changed" name="changed"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">Replace</property>
<property name="justify">GTK_JUSTIFY_RIGHT</property>
</object>
</child>
<child>
<object class="GtkEntry" id="search_replace_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<signal handler="search_replace_field_changed" name="changed"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="search_whole_word_checkbox">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Whole word</property>
<property name="draw_indicator">True</property>
<signal handler="on_search_whole_word_checkbox_toggled" name="toggled"/>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="search_case_sensitive_checkbox">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Case sensitive</property>
<property name="draw_indicator">True</property>
<!--<property name="active">True</property>-->
<signal handler="on_search_case_sensitive_checkbox_toggled" name="toggled"/>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3">
<property name="visible">True</property>
<child>
<object class="GtkButton" id="search_close_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">Close</property>
<signal handler="on_search_window_delete_event" name="clicked"/>
</object>
</child>
<child>
<object class="GtkButton" id="search_find_previous_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">Find Previous</property>
<signal handler="on_find_previous_menu_item_activate" name="clicked"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="search_find_next_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">Find Next</property>
<signal handler="on_find_next_menu_item_activate" name="clicked"/>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="search_replace_and_find_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">Find &amp; Replace</property>
<signal handler="on_replace_and_find_menu_item_activate" name="clicked"/>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">5</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
'''
import sys
import os
import imp,types,tempfile
import string
#import urlparse
import urllib
#import platform
import gio #this breaks windows
import gtk,gobject
import pango
import subprocess
import hashlib
from optparse import OptionParser
# prints about info from global vars to console
def print_about():
print "\n%s %s\n%s\n%s"%(EDILE_NAME,EDILE_VERSION,EDILE_URL,EDILE_DESCRIPTION)
print_about()
try:
import gtksourceview2
using_source_view = True
print "using gtksourceview"
except ImportError:
using_source_view = False
print "not using gtksourceview"
pass
# names for text wrapping types
CONF_WRAP_MAP = {'None':gtk.WRAP_NONE,'Character':gtk.WRAP_CHAR, 'Word':gtk.WRAP_WORD, 'Word Character':gtk.WRAP_WORD_CHAR}
if using_source_view:
# names for gtksourceview smart home end types
CONF_SMART_HOME_END_TYPE_MAP = {'Disabled':gtksourceview2.SMART_HOME_END_DISABLED,'Before':gtksourceview2.SMART_HOME_END_BEFORE,'After':gtksourceview2.SMART_HOME_END_AFTER,'Always':gtksourceview2.SMART_HOME_END_ALWAYS}
class Edile:
###
# on_* = action methods from ui
###
# Called when the user clicks the 'About' menu. We use gtk_show_about_dialog()
# which is a convenience function to show a GtkAboutDialog. This dialog will
# NOT be modal but will be on top of the main application window.
def on_about_menu_item_activate(self, menuitem, data=None):
# show standard gtk about window
def show_about():
if self.about_dialog:
self.about_dialog.present()
return
about_dialog = gtk.AboutDialog()
about_dialog.set_transient_for(self.window)
about_dialog.set_destroy_with_parent(True)
about_dialog.set_name(EDILE_NAME)
about_dialog.set_version(EDILE_VERSION)
about_dialog.set_copyright("")
about_dialog.set_website(EDILE_URL)
about_dialog.set_comments(EDILE_DESCRIPTION)
#about_dialog.set_authors(EDILE_AUTHORS)
about_dialog.set_logo_icon_name(gtk.STOCK_EDIT)
# callbacks for destroying the about dialog
def close(dialog, response, editor):
editor.about_dialog = None
dialog.destroy()
def delete_event(dialog, event, editor):
editor.about_dialog = None
return True
about_dialog.connect("response", close, self)
about_dialog.connect("delete-event", delete_event, self)
self.about_dialog = about_dialog
about_dialog.show()
# run functions
print_about()
show_about()
# view source. displays this file in a new instance.
def on_view_source_menu_item_activate(self, menuitem, data=None):
exe = __file__
exepath = os.path.realpath(exe)
self.spawn(exepath)
# tries to run file in a terminal
def on_execute_menu_item_activate(self,menuitem,data=None):
exe = self.filename
if exe == None:
self.error_message("Save file first!")
return
if os.path.exists(exe):
exepath = os.path.realpath(exe)
subprocess.Popen(["xterm", "-hold", "-e", "\"%s\""%(exepath)])
# When our window is destroyed, we want to break out of the GTK main loop.
# We do this by calling gtk_main_quit(). We could have also just specified
# gtk_main_quit as the handler in Glade!
def on_window_destroy(self, widget, data=None):
gtk.main_quit()
# When the window is requested to be closed, we need to check if they have
# unsaved work. We use this callback to prompt the user to save their work
# before they exit the application. From the "delete-event" signal, we can
# choose to effectively cancel the close based on the value we return.
def on_window_delete_event(self, widget, event, data=None):
if self.check_for_save():
self.on_save_menu_item_activate(None, None)
# if user cancelled save
if self.filename == None: return True
self.log('exiting')
return False # Propogate event
# Called when the user clicks the 'New' menu. We need to prompt for save if
# the file has been modified, and then delete the buffer and clear the
# modified flag.
def on_new_menu_item_activate(self, menuitem, data=None):
if self.check_for_save(): self.on_save_menu_item_activate(None, None)
# clear editor for a new file
buff = self.text_view.get_buffer()
buff.set_text("")
buff.set_modified(False)
self.filename = None
self.language = None
self.text_encoding = None
self.reset_default_status()
def on_new_window_menu_item_activate(self, menuitem, data=None):
self.spawn()
# Called when the user clicks the 'Open' menu. We need to prompt for save if
# thefile has been modified, allow the user to choose a file to open, and
# then call load_file() on that file.
def on_open_menu_item_activate(self, menuitem, data=None):
if self.check_for_save(): self.on_save_menu_item_activate(None, None)
filename = self.get_open_filename()
if filename: self.load_file(filename)
def on_open_in_new_window_menu_item_activate(self, menuitem, data=None):
filename = self.get_open_filename()
if filename: self.spawn(filename)
# Called when the user clicks the 'Save' menu. We need to allow the user to choose
# a file to save if it's an untitled document, and then call write_file() on that
# file.
def on_save_menu_item_activate(self, menuitem, data=None):
if self.filename == None:
filename = self.get_save_filename()
if filename: self.write_file(filename)
else: self.write_file(None)
# Called when the user clicks the 'Save As' menu. We need to allow the user
# to choose a file to save and then call write_file() on that file.
def on_save_as_menu_item_activate(self, menuitem, data=None):
filename = self.get_save_filename()
if filename: self.write_file(filename)
def on_close_menu_item_activate(self, menuitem, data=None):
if self.check_for_save():
self.on_save_menu_item_activate(None, None)
# if user cancelled save
if self.filename == None: return
self.text_view.set_sensitive(False)
buff = self.text_view.get_buffer()
buff.set_text("")
buff.set_modified(False)
self.text_view.set_sensitive(True)
self.filename = None
self.language = None
self.reset_default_status()
# Offer to save any changes, then reload original file from disk
def on_reload_menu_item_activate(self, menuitem, data=None):
old_filename = self.filename
if self.check_for_save():
self.on_save_as_menu_item_activate(menuitem, None)
# if user cancelled save
if self.filename == None: return
self.log('reloading %s'%(old_filename))
self.load_file(old_filename)
# Called when the user clicks the 'Quit' menu. We need to prompt for save if
# the file has been modified and then break out of the GTK+ main loop
def on_quit_menu_item_activate(self, menuitem, data=None):
if self.check_for_save():
self.on_save_menu_item_activate(None, None)
# if user cancelled save
if self.filename == None: return
self.log('exiting')
gtk.main_quit()
def on_undo_menu_item_activate(self, menuitem, data=None):
if using_source_view:
if self.text_view.get_buffer().can_undo():
self.text_view.get_buffer().undo()
def on_redo_menu_item_activate(self, menuitem, data=None):
if using_source_view:
if self.text_view.get_buffer().can_redo():
self.text_view.get_buffer().redo()
# Called when the user clicks the 'Cut' menu.
def on_cut_menu_item_activate(self, menuitem, data=None):
buff = self.text_view.get_buffer();
buff.cut_clipboard (gtk.clipboard_get(), True);
# Called when the user clicks the 'Copy' menu.
def on_copy_menu_item_activate(self, menuitem, data=None):
buff = self.text_view.get_buffer();
buff.copy_clipboard (gtk.clipboard_get());
# Called when the user clicks the 'Paste' menu.
def on_paste_menu_item_activate(self, menuitem, data=None):
buff = self.text_view.get_buffer();
buff.paste_clipboard (gtk.clipboard_get(), None, True);
def on_select_all_menu_item_activate(self, menuitem, data=None):
buff = self.text_view.get_buffer();
buff.select_range(buff.get_start_iter(),buff.get_end_iter())
# Called when the user clicks the 'Delete' menu.
def on_delete_menu_item_activate(self, menuitem, data=None):
buff = self.text_view.get_buffer();
gone = buff.get_text(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
self.log('deleting %s'%(gone))
buff.delete_selection (False, True);
def on_find_prev_selected_menu_item_activate(self,menuitem,data=None):
buff = self.text_view.get_buffer()
selected = buff.get_text(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
iter = self.get_find_iter(buffer=buff, backwards=True,limit_iter=None)
results = self.do_find_with_iter(iter=iter, search_for=selected,backwards=True,case_sensitive=False,whole_word=False)
try:
buff.select_range(results[0],results[1])
self.text_view.scroll_to_mark(mark=buff.get_insert(),within_margin=0.33,use_align=True,xalign=0.5,yalign=0.5)
except:
self.log("\'%s\' not found."%(selected))
def on_find_next_selected_menu_item_activate(self,menuitem,data=None):
buff = self.text_view.get_buffer()
selected = buff.get_text(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
iter = self.get_find_iter(buffer=buff, backwards=False,limit_iter=None)
results = self.do_find_with_iter(iter=iter, search_for=selected,backwards=False,case_sensitive=True,whole_word=False)
try:
buff.select_range(results[0],results[1])
self.text_view.scroll_to_mark(mark=buff.get_insert(),within_margin=0.33,use_align=True,xalign=0.5,yalign=0.5)
except:
self.log("\'%s\' not found."%(selected))
def on_search_case_sensitive_checkbox_toggled(self, data=None):
#self.search_case_sensitive = togglebutton.get_active()
self.search_case_sensitive = not self.search_case_sensitive
def on_search_whole_word_checkbox_toggled(self, data=None):
#self.search_is_whole_word = togglebutton.get_active()
self.search_is_whole_word = not self.search_is_whole_word
def search_find_field_changed(self, data=None):
self.search_string = self.search_field.get_text()
def search_replace_field_changed(self, data=None):
self.replacement_string = self.replace_field.get_text()
def on_show_find_menu_item_activate(self,menuitem,data=None):
#TODO: could load search UI from xml string here
if self.search_window.get_property("visible"):
self.search_window.present()
return
buff = self.text_view.get_buffer()
if self.search_string == "":
if buff.get_selection_bounds():
self.search_string = buff.get_slice(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
self.search_field.set_text(self.search_string)
self.replace_field.set_text(self.replacement_string)
self.search_field.grab_focus()
self.search_window.show_all()
def on_search_window_delete_event(self, widget=None, event=None, data=None):
self.search_window.hide_all()
return True
def on_find_next_menu_item_activate(self, sender, data=None):
buff = self.text_view.get_buffer();
if self.search_string == "":
if buff.get_selection_bounds():
self.search_string = buff.get_slice(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
self.search_field.set_text(self.search_string)
else:
self.error_message("Enter the search string first.")
self.on_show_find_menu_item_activate(sender)
next_results = self.do_find(buffer=buff,iter=None,backwards=False,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)
#highlight next results
#offer to wrap
#find backwards
def on_find_previous_menu_item_activate(self, menuitem, data=None):
buff = self.text_view.get_buffer();
if self.search_string == "":
if buff.get_selection_bounds():
self.search_string = buff.get_slice(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
self.search_field.set_text(self.search_string)
else:
self.error_message("Enter the search string first.")
self.on_show_find_menu_item_activate(menuitem)
prev_results = self.do_find(buffer=buff,iter=None,backwards=True,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)
#highlight prev results
#offer to wrap
def on_find_all_menu_item_activate(self, menuitem, data=None):
buff = self.text_view.get_buffer();
if self.search_string == "":
if buff.get_selection_bounds():
self.search_string = buff.get_slice(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
self.search_field.set_text(self.search_string)
else:
self.error_message("Enter the search string first.")
self.on_show_find_menu_item_activate(menuitem)
return
self.do_find_all(buffer=buff)
def on_replace_and_find_menu_item_activate(self, menuitem, data=None):
if self.search_string == "":
self.error_message("Enter the search string first.")
return
if self.replacement_string == '':
self.error_message("Enter the replacement string first.")
self.on_show_find_menu_item_activate(menuitem)
return
buff = self.text_view.get_buffer()
replace_iter = self.get_find_iter(buffer=buff, backwards=buff.get_has_selection(),limit_iter=None) #LAST
next_results = self.do_find(buffer=buff,iter=replace_iter,backwards=False,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)
if next_results == None:
return
else:
result_iter = next_results[0]
#ask for confirm here
self.do_replace_next(buffer=buff,iter=result_iter,search_for=self.search_string,replace_with=self.replacement_string,limit=next_results[1])
def on_replace_all_menu_item_activate(self, menuitem, data=None):
if self.search_string == "":
self.error_message("Enter the search string first.")
return
if self.replacement_string == "":
self.error_message("Enter the replacement string first.")
return
buff = self.text_view.get_buffer()
self.do_replace_all(buff,self.search_string,self.replacement_string)
# Called when the user clicks an item from the 'Wrap' menu.
def on_wrap_none_menu_item_activate(self, menuitem, data=None):
self.wrapping = 'None'
self.text_view.set_wrap_mode(gtk.WRAP_NONE);
def on_wrap_char_menu_item_activate(self, menuitem, data=None):
self.wrapping = 'Character'
self.text_view.set_wrap_mode(gtk.WRAP_CHAR);
def on_wrap_word_menu_item_activate(self, menuitem, data=None):
self.wrapping = 'Word'
self.text_view.set_wrap_mode(gtk.WRAP_WORD);
def on_select_font_menu_item_activate(self,menuitem, data=None):
if not self.font_dialog:
window = gtk.FontSelectionDialog("Font Selection Dialog")
self.font_dialog = window
window.set_position(gtk.WIN_POS_MOUSE)
window.set_transient_for(self.window)
window.connect("destroy", self.font_dialog_destroyed)
window.ok_button.connect("clicked", self.font_selection_ok)
window.cancel_button.connect_object("clicked", lambda wid: wid.destroy(), self.font_dialog)
current_font = self.text_view.get_pango_context().get_font_description().to_string()
window.set_font_name(current_font)
window = self.font_dialog
if not (window.flags() & gtk.VISIBLE):
window.show()
else:
window.destroy()
self.font_dialog = None
def font_selection_ok(self, button):
self.font = self.font_dialog.get_font_name()
if self.window:
font_desc = pango.FontDescription(self.font)
if font_desc:
self.text_view.modify_font(font_desc)
self.font_dialog.destroy()
def font_dialog_destroyed(self, data=None):
self.font_dialog = None
#emitted when the modified flag of the buffer changes
def on_text_buffer_modified_changed(self, buff, data=None):
self.mark_window_status(self.window,buff.get_modified())
#emitted BEFORE the text is actually inserted into the buffer
def on_text_buffer_insert_text(self, buff, iter, text, length, data=None):
# for change highlighting. just save the edit location and length here.
# the actual highlight is applied in on_text_buffer_changed()
self.edit_loc = iter.get_offset()
self.edit_len = length
# remove any find highlighting we've done
if buff.get_tag_table().lookup("search_results"):
buff.remove_tag_by_name('search_results', buff.get_start_iter(), buff.get_end_iter())
#emitted AFTER text is inserted into the buffer
#here we retrieve the location and apply the highlight tag
def on_text_buffer_changed(self, buff,data=None):
# we set edit_loc to -1 on deletions
if self.edit_loc >=0:
start_iter = buff.get_iter_at_offset(self.edit_loc)
start_mark = buff.create_mark(None,start_iter)
end_iter = buff.get_iter_at_offset(self.edit_loc + self.edit_len)
end_mark = buff.create_mark(None,end_iter)
self.mark_range_as_changed(buff,start_mark,end_mark)
buff.delete_mark(start_mark)
buff.delete_mark(end_mark)
#set the edit location to -1 here so on_text_buffer_changed() doesn't
#try to set attributes on a deleted range
def on_text_buffer_delete_range(self,buff,start,end, data=None):
self.edit_loc = -1
#def on_text_buffer_mark_set(self, textbuffer, iter, textmark, data=None):
# if textmark.get_name() == 'insert':
# self.move_replace_cursor(textbuffer,iter)
# We call error_message() any time we want to display an error message to
# the user. It will both show an error dialog and log the error to the
# terminal window.
def error_message(self, message):
# log to terminal window
self.log(message)
# create an error message dialog and display modally to the user
dialog = gtk.MessageDialog(None,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message)
dialog.run()
dialog.destroy()
# This function will check to see if the text buffer has been
# modified and prompt the user to save if it has been modified.
def check_for_save (self):
ret = False
buff = self.text_view.get_buffer()
if buff.get_modified():
# we need to prompt for save
message = "Do you want to save the changes you have made?"
dialog = gtk.MessageDialog(self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
message)
dialog.set_title("Save?")
if dialog.run() == gtk.RESPONSE_NO: ret = False
else: ret = True
dialog.destroy()
return ret
# We call get_open_filename() when we want to get a filename to open from the
# user. It will present the user with a file chooser dialog and return the
# filename or None.
def get_open_filename(self):
filename = None
chooser = gtk.FileChooserDialog("Open File...", self.window,
gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN, gtk.RESPONSE_OK))
response = chooser.run()
if response == gtk.RESPONSE_OK: filename = chooser.get_filename()
chooser.destroy()
return filename
# We call get_save_filename() when we want to get a filename to save from the
# user. It will present the user with a file chooser dialog and return the
# filename or None.
def get_save_filename(self):
filename = None
chooser = gtk.FileChooserDialog("Save File...", self.window,
gtk.FILE_CHOOSER_ACTION_SAVE,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_SAVE, gtk.RESPONSE_OK))
response = chooser.run()
if response == gtk.RESPONSE_OK: filename = chooser.get_filename()
#if response == gtk.RESPONSE_CANCEL: print "save cancelled"
chooser.destroy()
return filename
###
# do_* = controller methods called from on_* actions
###
# find
def get_find_iter(self,buffer=None, backwards=False,limit_iter=None):
find_iter = None
selection = buffer.get_selection_bounds()
if selection == ():
find_iter = buffer.get_iter_at_mark(buffer.get_insert())
if find_iter == None:
find_iter = buffer.get_bounds()[int(backwards)]
else:
find_iter = selection[int(not backwards)]
return find_iter
def do_find(self,buffer=None,iter=None,backwards=False,case_sensitive=False,whole_word=False):
if self.search_string == "":
# show the ui
self.on_show_find_menu_item_activate(buffer)
return
find_iter = iter
if find_iter == None:
find_iter = self.get_find_iter(buffer=buffer, backwards=backwards,limit_iter=None)
results = self.do_find_with_iter(find_iter, self.search_string, backwards=backwards, case_sensitive=case_sensitive,whole_word=whole_word)
if results == None:
#nothing else found. ask to wrap.
self.log( "\'%s\' not found"%(self.search_string))
if self.ask_for_wrap(backwards=backwards):
self.log("wrapping")
wrapped_iter = buffer.get_bounds()[int(backwards)]
results = self.do_find_with_iter(wrapped_iter, self.search_string, backwards=backwards, case_sensitive=case_sensitive,whole_word=whole_word)
if results:
ins, bound = results
buffer.select_range(ins,bound)
self.text_view.scroll_to_mark(mark=buffer.get_insert(),within_margin=0.33,use_align=True,xalign=0.5,yalign=0.5)
else:
# really not found. beep.
gtk.gdk.beep()
else:
#happily select and highlight
ins, bound = results
buffer.select_range(ins,bound)
self.text_view.scroll_to_mark(mark=buffer.get_insert(),within_margin=0.33,use_align=True,xalign=0.5,yalign=0.5)
return results
def do_find_with_iter(self, iter=None, search_for=None,backwards=False,case_sensitive=False,whole_word=False):
def next_match():
results = None
search_flags = 0
if using_source_view:
if not self.search_case_sensitive:
search_flags = gtksourceview2.SEARCH_CASE_INSENSITIVE
if backwards:
results = gtksourceview2.iter_backward_search(iter, search_for, flags=search_flags)#, limit=limit_iter)
else:
results = gtksourceview2.iter_forward_search(iter, search_for, flags=search_flags)#, limit=limit_iter)
else:
if backwards:
results = iter.backward_search(search_for, flags=search_flags)#, limit=limit_iter)
else:
results = iter.forward_search(search_for, flags=search_flags)#, limit=limit_iter)
return results
#test for whole word , case
def next_whole_word():
word_hit = None
whole_word_hit = False
while not whole_word_hit:
word_hit = next_match()
if word_hit:
if word_hit[0].starts_word() and word_hit[1].ends_word():
#hit = result
whole_word_hit = True
return word_hit
else:
if not backwards:
iter.forward_chars(len(search_for))
else:
iter.backward_chars(len(search_for))
#print iter.get_offset()
word_hit = next_match()
else:
return None
return word_hit
result = next_match()
# test substring result for conditions
if self.search_is_whole_word:
hit = next_whole_word()
else:
hit = result
return hit
def ask_for_wrap(self,backwards=False):
ret = False
direction = 'beginning'
if backwards:
direction = 'end'
message = "\'%s\' not found. Wrap and start from %s of document?"%(self.search_string,direction)
dialog = gtk.MessageDialog(self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
message)
dialog.set_title("Wrap Search?")
if dialog.run() == gtk.RESPONSE_NO: ret = False
else: ret = True
dialog.destroy()
return ret
def ask_for_replace(self):
ret = False
message = "Replace?"
dialog = gtk.MessageDialog(self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
message)
dialog.set_title("Replace?")
if dialog.run() == gtk.RESPONSE_NO: ret = False
else: ret = True
dialog.destroy()
return ret
def do_find_all(self,buffer=None,case_sensitive=False,whole_word=False):
start_iter = buffer.get_start_iter()
end_iter = buffer.get_end_iter()
begin_mark = buffer.create_mark(None,start_iter)
end_mark = buffer.create_mark(None,end_iter)
search_results = self.find_all_in_range(buffer, self.search_string, begin_mark,end_mark)
buffer.delete_mark(begin_mark)
buffer.delete_mark(end_mark)
if not buffer.get_tag_table().lookup("search_results"):
preview_tag = buffer.create_tag("search_results", background_set="True",background=CONF_FIND_HIGHLIGHT)
else:
buffer.remove_tag_by_name('search_results', start_iter, end_iter)
if search_results:
for range in search_results:
if self.search_is_whole_word:
if range[0].starts_word() and range[1].ends_word():
buffer.apply_tag_by_name("search_results",range[0],range[1])
else:
buffer.apply_tag_by_name("search_results",range[0],range[1])
def do_replace_next(self,buffer=None,iter=None,search_for='',replace_with='',limit=None):
buffer.remove_tag_by_name('search_results', buffer.get_start_iter(), buffer.get_end_iter())
replace_iter = iter
range = self.do_find(buffer=buffer,iter=replace_iter,backwards=False,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)
if range:
if not buffer.get_tag_table().lookup("search_results"):
preview_tag = buffer.create_tag("search_results", background_set="True",background=CONF_FIND_HIGHLIGHT)
#apply tag
buffer.apply_tag_by_name("search_results",range[0],range[1])
if not self.ask_for_replace():
self.log("replace declined")
return
replace_begin = buffer.create_mark(None,range[0])
replace_end = buffer.create_mark(None,range[1])
replace_list = [(replace_begin,replace_end)]
replaced_range = self.replace_text(buffer,replace_with,replace_list)
self.text_view.scroll_to_mark(replace_end,0)
buffer.delete_mark(replace_begin)
buffer.delete_mark(replace_end)
def do_replace_all(self,buffer,search_for,replace_with):
#search_results = self.search_for_text(buffer,search_for,buffer.get_start_iter(),buffer.get_end_iter())
replace_begin = buffer.create_mark(None,buffer.get_start_iter())
replace_end = buffer.create_mark(None,buffer.get_end_iter())
search_results = self.find_all_in_range(buffer,search_for,replace_begin,replace_end)
result_marks = []
for iter_pair in search_results:
this_mark_pair = []
for iter in iter_pair:
this_mark = buffer.create_mark(None,iter)
this_mark_pair.append(this_mark)
self.edit_loc = iter_pair[0]#wft
result_marks.append(this_mark_pair)
self.replace_text(buffer,replace_with,result_marks)
for that_mark_pair in result_marks:
for that_mark in that_mark_pair:
buffer.delete_mark(that_mark)
buffer.delete_mark(replace_begin)
buffer.delete_mark(replace_end)
def find_all_in_range(self, buff, search_string, start_mark, end_mark):
start_iter = buff.get_iter_at_mark(start_mark)
end_iter = buff.get_iter_at_mark(end_mark)
search_results = []
next_result = self.do_find_with_iter(iter=start_iter,search_for=self.search_string,backwards=False,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)
while next_result:
#self.search_for_text(buff,search_string,start_iter,end_iter)
search_results.append(next_result)
next_result = self.do_find_with_iter(iter=next_result[1],search_for=self.search_string,backwards=False,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)
return search_results
def guess_encoding(self,data):
"""
Given a byte string, attempt to decode it.
Tries the standard 'UTF8' and 'latin-1' encodings,
Plus several gathered from locale information.
The calling program *must* first call
locale.setlocale(locale.LC_ALL, '')
If successful it returns
(decoded_unicode, successful_encoding)
If unsuccessful it raises a ``UnicodeError``
"""
import locale
successful_encoding = None
# we make 'utf-8' the first encoding
encodings = ['utf-8']
#
# next we add anything we can learn from the locale
try:
encodings.append(locale.nl_langinfo(locale.CODESET))
except AttributeError:
pass
try:
encodings.append(locale.getlocale()[1])
except (AttributeError, IndexError):
pass
try:
encodings.append(locale.getdefaultlocale()[1])
except (AttributeError, IndexError):
pass
#
# we try 'latin-1' last
encodings.append('latin-1')
for enc in encodings:
# some of the locale calls
# may have returned None
if not enc:
continue
try:
decoded = unicode(data, enc)
successful_encoding = enc
except (UnicodeError, LookupError):
pass
else:
break
if not successful_encoding:
raise UnicodeError(
'Unable to decode input data. Tried the following encodings: %s.'
% ', '.join([repr(enc) for enc in encodings if enc]))
else:
return (decoded, successful_encoding)
#thanks to http://www.pyzine.com/Issue008/Section_Articles/article_Encodings.html
def decode_text(self,the_text=None):
# adapted from io.py
# in the docutils extension module
# see http://docutils.sourceforge.net
import codecs
import locale
import sys
# uses the guess_encoding function from above
bomdict = {
codecs.BOM_UTF8 : 'UTF8',
codecs.BOM_UTF16_BE : 'UTF-16BE',
codecs.BOM_UTF16_LE : 'UTF-16LE' }
# check if there is Unicode signature
for bom, encoding in bomdict.items():
if the_text.startswith(bom):
the_text = the_text[len(bom):]
break
else:
bom = None
encoding = None
if encoding is None: # there was no BOM
try:
unicode_text, encoding = self.guess_encoding(the_text)
except UnicodeError:
print "Sorry - we can't work out the encoding."
raise
else:
# we found a BOM so we know the encoding
unicode_text = the_text.decode(encoding)
# now you have your Unicode text.. and can do with it what you will
# now we want to re-encode it to a byte string
# so that we can write it back out
# we will reuse the original encoding, and preserve any BOM
if bom is not None:
if encoding.startswith('UTF-16'):
# we will use the right 'endian-ness' for this machine
encoding = 'UTF-16'
bom = codecs.BOM_UTF16
byte_string = unicode_text.encode(encoding)
if bom is not None:
byte_string = bom + byte_string
# now we have the text encoded as a byte string, ready to be saved to a file
return byte_string
# We call load_file() when we have a filename and want to load it into the
# buffer for the GtkTextView. The previous contents are overwritten.
def load_file(self, filename):
# add Loading message to status bar and ensure GUI is current
self.statusbar.push(self.statusbar_cid, "Loading %s" % filename)
while gtk.events_pending():
gtk.main_iteration()
buff = self.text_view.get_buffer()
if using_source_view:
try:
buff.begin_not_undoable_action()
except AttributeError:
pass
try:
# get the file contents
fin = open(filename, "r")
text_data = fin.read()
fin.close()
# disable the text view while loading the buffer with the text
self.text_view.set_sensitive(False)
#unicode?
#buff.set_text(text)
the_text,the_encoding = self.guess_encoding(text_data)
buff.set_text(the_text)
self.text_encoding = the_encoding
print "encoding: %s"%self.text_encoding
# reenable text view and set everything up for the user
buff.set_modified(False)
self.text_view.set_sensitive(True)
# now we can set the window's filename since loading was a success
self.filename = filename
except:
# error loading file, show message to user
self.error_message ("Could not open file: %s\n%s" % (filename,sys.exc_info()[0]))
raise
# syntax highlighting
if using_source_view:
if CONF_HIGHLIGHT_SYNTAX:
buffer = self.text_view.get_buffer()
f = gio.File(filename)
path = f.get_path()
info = f.query_info("*")
mime_type = info.get_content_type()
language = None
if mime_type:
language = self.get_language_for_mime_type(mime_type)
if language:
self.language = language.get_name()
if not language:
#self.error_message('No language found for mime type "%s"' % mime_type)
self.language = 'None'
else:
self.error_message('Couldn\'t get mime type for file "%s"' % filename)
try:
buffer.set_language(language)
buffer.set_highlight_syntax(CONF_HIGHLIGHT_SYNTAX)
except AttributeError:
pass
# move insertion point and scroll to top
buff.place_cursor(buff.get_start_iter())
if using_source_view:
try:
self.text_view.get_buffer().end_not_undoable_action()
except AttributeError:
pass
# clear loading status and restore default
self.statusbar.pop(self.statusbar_cid)
self.reset_default_status()
def write_file(self, filename):
# add Saving message to status bar and ensure GUI is current
if filename:
self.statusbar.push(self.statusbar_cid, "Saving %s" % filename)
else:
self.statusbar.push(self.statusbar_cid, "Saving %s" % self.filename)
while gtk.events_pending(): gtk.main_iteration()
try:
# disable text view while getting contents of buffer
buff = self.text_view.get_buffer()
self.text_view.set_sensitive(False)
text = buff.get_text(buff.get_start_iter(), buff.get_end_iter())
if self.text_encoding == None:
self.text_encoding = "utf-8"
#text_data = unicode(text)
text_data = text.encode(self.text_encoding)
self.text_view.set_sensitive(True)
buff.set_modified(False)
# set the contents of the file to the text from the buffer
if filename: fout = open(filename, "w")
else: fout = open(self.filename, "w")
fout.write(text_data)
fout.close()
if filename: self.filename = filename
except:
# error writing file, show message to user
self.error_message ("Could not save file: %s" % filename)
# clear saving status and restore default
self.statusbar.pop(self.statusbar_cid)
self.reset_default_status()
######################################################################
##### Note this function is silly and wrong, because it ignores mime
##### parent types and subtypes.
# i don't care i'm using it anyway
def get_language_for_mime_type(self,mime):
lang_manager = gtksourceview2.language_manager_get_default()
lang_ids = lang_manager.get_language_ids()
for i in lang_ids:
lang = lang_manager.get_language(i)
for m in lang.get_mime_types():
if m == mime:
return lang
return None
def mark_window_status(self,window,changed):
if changed:
if not window.get_title()[0] == "*":
window.set_title("*" + window.get_title())
class StatusBarManager:
def __init__(self):
self.filename = "(UNTITLED)"
self.language = "(NONE)"
self.encoding = "utf-8" #get default encoding
self.status_line = {}
self.statusbar = None
def StatusBarManager(self, statusbar=None, language=None,filename=None,encoding=None):
self.set_language(language)
self.set_filename(filename)
self.set_encoding(encoding)
self.set_statusbar(statusbar)
def set_language(self,language=None):
self.language = language
self.status_line['Language'] = self.language
self.update_status()
def set_encoding(self,encoding=None):
self.encoding = encoding
self.status_line['Encoding'] = self.encoding
self.update_status()
def set_filename(self,filename=None):
self.filename = filename
self.status_line['File'] = self.filename
self.update_status()
def set_statusbar(self,statusbar=None):
self.statusbar = statusbar
def update_status(self):
cid = self.statusbar.get_context_id("Edile GTK+ Text Editor")
self.statusbar.push(cid,self.get_status())
def reset_status(self):
self.update_status()
def set_status(self,string):
cid = self.statusbar.get_context_id("Edile GTK+ Text Editor")
self.statusbar.push(cid,string)
def get_status(self):
status_string = "File: %s | Language: %s | Encoding: %s"%(self.filename,self.language,self.encoding)
return status_string
class PluginInterface:
def set_parent(self,parent=None):
self.parent = parent
def replace_selection(self,new_text=None):
#create marks
range = self.get_selected_range()
buff = self.get_buffer()
start = buff.create_mark(None,range[0])
end = buff.create_mark(None,range[1])
repl = self.parent.replace_text(buff,new_text,[(start,end)])
buff.select_range(buff.get_iter_at_mark(start),buff.get_iter_at_mark(end))
buff.delete_mark(start)
buff.delete_mark(end)
def insert(self,text=None):
if text:
self.parent.text_view.get_buffer().insert_at_cursor(text)
def select(self,start,end):
self.get_buffer().select_range(end,start)
def get_selection(self):
range = self.parent.text_view.get_buffer().get_selection_bounds()
return self.parent.text_view.get_buffer().get_text(range[0],range[1])
def get_selected_range(self):
return self.parent.text_view.get_buffer().get_selection_bounds()
def open_file(self,path=None):
self.parent.spawn(path)
return
def get_text(self):
buf = self.parent.text_view.get_buffer()
return buf.get_text(buf.get_start_iter(),buf.get_end_iter())
def message(self,string=None):
self.parent.error_message(string)
def get_language(self):
return self.parent.language
def get_buffer(self):
return self.parent.text_view.get_buffer()
def get_filename(self):
return self.parent.filename
def on_drag_motion(self,wid, context, x, y, time):
context.drag_status(gtk.gdk.ACTION_COPY, time)
#path = context.drag_get_selection()
path = ""
self.statusbar_manager.set_status('Open: %s'%(path))
return True
# def on_drag_drop(self,wid, context, x, y, time):
# self.text_view.get_buffer().set_text('\n'.join([str(t) for t in context.targets]))
# #load file
# wid.drag_get_data()
# context.finish(True, False, time)
# return True
def on_drag_end(self,widget, drag_context, data):
self.statusbar_manager.reset_status()
def on_drag_data_received(self,widget, drag_context, x, y, selection, target_type, time, data):
if target_type == 80:
uri = selection.data.strip('\r\n\x00')
uri_splitted = uri.split() # we may have more than one file dropped
#for uri in uri_splitted:
#print uri_splitted
uri = uri_splitted[0]
path = self.get_file_path_from_dnd_dropped_uri(uri)
#path = urlparse.urlparse(uri).path
#print path
if os.path.isfile(path):
if self.check_for_save():
self.on_save_menu_item_activate(None, None)
# if user cancelled save
if self.filename == None: return
self.load_file(path)
drag_context.finish(success=True, del_=False, time=time)
#drag_context.drop_finish(True,time)
else:
#drag_context.drop_finish(False,time)
drag_context.finish(success=False, del_=False, time=time)
def get_file_path_from_dnd_dropped_uri(self,uri):
# get the path to file
path = ""
if uri.startswith('file:\\\\\\'): # windows
path = uri[8:] # 8 is len('file:///')
elif uri.startswith('file://'): # nautilus, rox
path = uri[7:] # 7 is len('file://')
elif uri.startswith('file:'): # xffm
path = uri[5:] # 5 is len('file:')
path = urllib.url2pathname(path) # escape special chars
path = path.strip('\r\n\x00') # remove \r\n and NULL
return path
def running_as_root(self):
return (os.name == 'posix') and (os.geteuid() == 0)
def reset_default_status(self):
if self.filename:
status = "File: %s" % self.filename #os.path.basename(self.filename)
self.window.set_title("%s | Edile Text Editor" % os.path.basename(self.filename))
else:
status = "File: (UNTITLED)"
self.window.set_title("Untitled | Edile Text Editor")
#check for root
#if platform.system() is not "Windows":
if self.running_as_root():
old_title = self.window.get_title()
new_title = old_title + " | RUNNING AS ROOT"
self.window.set_title(new_title)
self.statusbar.pop(self.statusbar_cid)
self.statusbar.push(self.statusbar_cid, status)
self.statusbar_manager.set_filename(self.filename)
self.statusbar_manager.set_language(self.language)
self.statusbar_manager.set_encoding(self.text_encoding)
buff = self.text_view.get_buffer()
buff.remove_tag_by_name("change_highlight",buff.get_start_iter(),buff.get_end_iter())
#self.move_replace_cursor(buff,buff.get_start_iter())
self.text_view.grab_focus()
def search_for_text(self, txtbuf, text, begin, end):
"""
Search for text within a specified text buffer.
This function searches for the text within the boundaries specified by begin
and end in a specified text buffer. If matches are found the function
returns the position of the found matches represented by pairs of
gtk.TextMarks. Otherwise, the functions returns an empty List.
@param txtbuf: Reference to the text buffer to search.
@type editor: A gtk.TextBuffer object.
@param text: The string to search for in the text editor's buffer.
@type text: A String object.
@param begin: The position in the buffer to begin searching for text.
@type begin: A gtk.TextIter object.
@param end: The position in the buffer to stop searching for text.
@type end: A gtk.TextIter object.
@return: The position of found matches in the buffer.
@rtype: A List object containing pairs of gtk.TextIter or None.
"""
found_matches = []
from gtk import TEXT_SEARCH_VISIBLE_ONLY
while True:
result = begin.forward_search(text, TEXT_SEARCH_VISIBLE_ONLY, end)
if result:
found_matches.append((result[0], result[1]))
begin = result[1]
else:
break
return found_matches
def mark_range_as_changed(self,txtbuf,start_mark,end_mark):
if self.highlight_changes:
begin = txtbuf.get_iter_at_mark(start_mark)
end = txtbuf.get_iter_at_mark(end_mark)
txtbuf.apply_tag_by_name("change_highlight",begin,end)
def replace_text(self, txtbuf, text, positions):
"""
Replace text at specified positions in a text buffer with one specified as
a parameter.
@param txtbuf: Reference to a text buffer where replacement should occur.
@type txtbuf: A gtk.TextBuffer object.
@param text: Text to insert into the text buffer.
@type text: A String object.
@param positions: Positions in the text buffer to replace text.
@type positions: A List object containing pairs of gtk.TextMarks.
"""
replaced_ranges = []
for marks in positions:
begin = txtbuf.get_iter_at_mark(marks[0])
end = txtbuf.get_iter_at_mark(marks[1])
txtbuf.delete(begin, end)
begin = txtbuf.get_iter_at_mark(marks[0])
txtbuf.insert(begin, text)
replaced_ranges.append((marks[0],marks[1]))
return replaced_ranges
#spawn a new edile instance
def spawn(self, file=None):
exe = __file__
exepath = os.path.realpath(exe)
if file:
path = os.path.realpath( file )
else:
path = ""
#doesnt use subprocess
#os.system("%s \"%s\"&"% (exepath,path))
child_pid = subprocess.Popen([exepath, path]).pid
self.log("new %s instance pid %d"%(os.path.basename(exepath),child_pid))
def plugin_load_from_url(self,url):
plugin_string = urllib.urlopen(url).read()
#plugin_file = tempfile.NamedTemporaryFile()
#plugin_file.write(plugin_string)
#print plugin_file.name
return plugin_string
def plugin_getfunctions(self,module):
l = []
for key, value in module.__dict__.items():
if type(value) is types.FunctionType:
l.append(value)
return l
def plugin_get_plugins(self):
plugin_tree = {}
if CONF_PLUGIN_LOCATION == "": return
plugin_string = self.plugin_load_from_url(CONF_PLUGIN_LOCATION)
if plugin_string:
plugin_hash = hashlib.sha256(plugin_string).hexdigest()
if (plugin_hash == CONF_PLUGIN_SHA256) or (CONF_PLUGIN_SHA256 == ''):
plugins = imp.new_module("plugins")
exec(plugin_string, plugins.__dict__)
#print plugins.__dict__['Tools'].shortcuts
else:
self.error_message("plugin authentication failed")
quit()
#pl = imp.load_source("plugins",plugin_file) #path only
for thing in plugins.__dict__:
if thing[:2] != "__":
#if type(thing) is types.MethodType:
pl_key = (thing,plugins.__dict__[thing])
plugin_tree[pl_key] = self.plugin_getfunctions(plugins.__dict__[thing])
#print plugin_tree
return plugin_tree
def plugin_create_menus(self,plugins=None):
menus = []
for each in plugins.keys():
the_plugin = plugins[each]
the_menubar_item = gtk.MenuItem(each[0],False)
the_menu = gtk.Menu()
#the_menu.set_title(each)
#kbd shortcutss
for each_function in the_plugin:
item_title = string.capwords(each_function.__name__.replace('_',' '))
#create menu item
menu_item = gtk.MenuItem(item_title,False)
#connect() to function
menu_item.connect('activate',each_function,self.plugin_interface)
agr = gtk.AccelGroup()
self.window.add_accel_group(agr)
# i totally forgot how this works, but it does.
# doesn't look too complicated.
if hasattr(each[1],"shortcuts"):
if each[1].shortcuts.has_key(each_function):
shortcut = each[1].shortcuts[each_function]
key, mod = gtk.accelerator_parse(shortcut)
if key:
menu_item.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE)
#add to the menu
the_menu.append(menu_item)
the_menubar_item.set_submenu(the_menu)
menus.append(the_menubar_item)
return menus
def plugin_add_menus(self,menubar=None,plugin_menus=None):
#insert plugin menus into the menu bar before the Help menu
[menubar.insert(every_menu,4) for every_menu in plugin_menus]
# output in a friendly fashion
def log(self,string):
instance_name = self.window.get_title()
pid = '[%d]'%(os.getpid())
print "%s %s %s"%(instance_name,pid,string)
def load_plugins(self):
should_load = CONF_LOAD_PLUGINS and CONF_PLUGIN_LOCATION != ""
if should_load:
if CONF_LOAD_PLUGINS_AS_ROOT == False:
should_load = not self.running_as_root()
if should_load:
pluginlist = self.plugin_get_plugins()
plugin_menus = self.plugin_create_menus(pluginlist)
self.plugin_add_menus(self.menubar,plugin_menus)
self.window.show_all()
# setter methods for options
def set_auto_indent(self, indent=CONF_AUTO_INDENT):
self.text_view.set_auto_indent(CONF_AUTO_INDENT)
def set_highlight_changes(self, highlight=CONF_HIGHLIGHT_CHANGES_SINCE_SAVE):
self.highlight_changes = highlight
def set_highlight_current_line(self, highlight=CONF_HIGHLIGHT_CURRENT_LINE):
self.text_view.set_highlight_current_line(highlight)
def set_show_line_numbers(self, number=CONF_SHOW_LINE_NUMBERS):
self.text_view.set_show_line_numbers(number)
def set_show_right_margin(self, margin=CONF_SHOW_RIGHT_MARGIN):
self.text_view.set_show_right_margin(margin)
def set_insert_spaces_instead_of_tabs(self, insert=CONF_SPACES_INSTEAD_OF_TABS):
self.text_view.set_insert_spaces_instead_of_tabs(insert)
def set_tab_width(self, width=CONF_TAB_WIDTH):
self.text_view.set_tab_width(width)
def set_indent_width(self, width=CONF_INDENT_WIDTH):
self.text_view.set_indent_width(width)
def set_smart_home_end(self, type=CONF_SMART_HOME_END_TYPE_MAP[CONF_SMART_HOME_END_TYPE[0]]):
self.text_view.set_smart_home_end(type)
def set_indent_on_tab(self, indent=CONF_INDENT_ON_TAB):
self.text_view.set_indent_on_tab(indent)
def set_right_margin_position(self, pos=CONF_RIGHT_MARGIN_POSITION):
self.text_view.set_right_margin_position(pos)
def set_highlight_syntax(self, highlight=CONF_HIGHLIGHT_SYNTAX):
self.text_view.get_buffer().set_highlight_syntax(highlight)
def set_highlight_matching_brackets(self, highlight=CONF_HIGHLIGHT_MATCHING_BRACKETS):
self.text_view.get_buffer().set_highlight_matching_brackets(highlight)
def set_max_undo_levels(self, levels=CONF_MAX_UNDO_LEVELS):
self.text_view.get_buffer().set_max_undo_levels(levels)
def set_wrap_mode(self, mode=CONF_WRAP_MAP['None']):
mode = CONF_WRAP_MAP[self.wrapping]
self.text_view.set_wrap_mode(mode)
def set_wrapping(self, wrap=CONF_WRAP[0]):
self.wrapping = wrap
def set_font(self, font=CONF_FONT):
self.text_view.modify_font(pango.FontDescription(font))
def set_overwrite(self, overwrite=CONF_OVERWRITE):
self.text_view.set_overwrite(overwrite)
def set_style_scheme(self, scheme=CONF_STYLE_SCHEME[0]):
mgr = gtksourceview2.style_scheme_manager_get_default()
style_scheme = mgr.get_scheme(scheme)
if style_scheme:
self.text_view.get_buffer().set_style_scheme(style_scheme)
# We use the initialization of the Edile class to establish
# references to the widgets we'll need to work with in the callbacks for
# various signals. This is done using the XML in the U_I string
def __init__(self):
# Default values
self.filename = None
self.language = None
self.text_encoding = None
self.about_dialog = None
self.search_string = ""
self.replacement_string = ""
self.font_dialog = None
self.edit_loc = 0
self.edit_len = 0
self.search_did_wrap = False
self.plugin_interface = Edile.PluginInterface()
self.plugin_interface.set_parent(self)
# search options
self.search_case_sensitive = False
self.search_is_whole_word = False
import locale
locale.setlocale(locale.LC_ALL, '')
self.set_highlight_changes(CONF_HIGHLIGHT_CHANGES_SINCE_SAVE)
# use GtkBuilder to build our interface from the XML file
try:
builder = gtk.Builder()
#need to specify len() to work around pygtk <2.13 bug.
builder.add_from_string(U_I,len(U_I))
builder.add_from_string(SEARCH_UI,len(SEARCH_UI))
except:
self.error_message("Failed to load UI")
sys.exit(1)
# get the widgets which will be referenced in callbacks
self.window = builder.get_object("window")
self.statusbar = builder.get_object("statusbar")
self.text_view = builder.get_object("text_view")
self.scroll_view = builder.get_object("scrolledwindow")
self.search_window = builder.get_object('search_window')
self.search_field = builder.get_object("search_find_field")
self.replace_field = builder.get_object("search_replace_field")
self.menubar = builder.get_object("menubar1")
###
# command line options
###
parser = OptionParser()
parser.add_option("-p", "--plain-text", action="store_false", dest="using_source_view", default=True, help="don't use GTKSourceView if available")
parser.add_option("-q", "--quiet", action="store_false", dest="verbose", default=True, help="don't print status messages to stdout")
(options, args) = parser.parse_args()
using_source_view = options.using_source_view
#if parser.has_option("plain-text"):
# to switch off option parser
#args = sys.argv
#gtksourceview is installed and we should use it, set up dev stuff
if (using_source_view):
#if 0:
self.scroll_view.remove(self.text_view)
self.text_view = gtksourceview2.View()
self.set_auto_indent(CONF_AUTO_INDENT)
self.set_highlight_current_line(CONF_HIGHLIGHT_CURRENT_LINE)
self.set_show_line_numbers(CONF_SHOW_LINE_NUMBERS)
self.set_show_right_margin(CONF_SHOW_RIGHT_MARGIN)
self.set_insert_spaces_instead_of_tabs(CONF_SPACES_INSTEAD_OF_TABS)
self.set_tab_width(CONF_TAB_WIDTH)
self.set_indent_width(CONF_INDENT_WIDTH)
self.set_right_margin_position(CONF_RIGHT_MARGIN_POSITION)
self.set_smart_home_end(CONF_SMART_HOME_END_TYPE_MAP[CONF_SMART_HOME_END_TYPE[0]])
self.set_indent_on_tab(CONF_INDENT_ON_TAB)
# make a buffer for the view
self.text_view.set_buffer(gtksourceview2.Buffer())
# configure the source view buffer
self.set_highlight_syntax(CONF_HIGHLIGHT_SYNTAX)
self.set_highlight_matching_brackets(CONF_HIGHLIGHT_MATCHING_BRACKETS)
self.set_max_undo_levels(CONF_MAX_UNDO_LEVELS)
self.text_view.show()
self.text_view.set_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
self.text_view.set_left_margin(2)
self.text_view.set_right_margin(2)
self.scroll_view.add(self.text_view)
self.set_style_scheme()
# non-gtksrcview-exclusive options
# set the text view font
self.set_font(CONF_FONT)
self.set_overwrite(CONF_OVERWRITE)
# set the text view wrapping
self.set_wrapping(CONF_WRAP[0])
if not self.wrapping: self.wrapping = 'None'
self.set_wrap_mode(CONF_WRAP_MAP[self.wrapping])
wrap_none_menu_item = builder.get_object("wrap_none_menu_item")
wrap_char_menu_item = builder.get_object("wrap_char_menu_item")
wrap_word_menu_item = builder.get_object("wrap_word_menu_item")
if self.wrapping == 'None' : wrap_none_menu_item.set_active(True)
if self.wrapping == 'Character' : wrap_char_menu_item.set_active(True)
if self.wrapping == 'Word' : wrap_word_menu_item.set_active(True)
self.statusbar_manager = Edile.StatusBarManager()
self.statusbar_manager.set_statusbar(self.statusbar)
buff = self.text_view.get_buffer()
self.change_highlight = buff.create_tag("change_highlight", background_set="True",background=CONF_CHANGE_HIGHLIGHT)
buff.connect("modified-changed", self.on_text_buffer_modified_changed,None)
buff.connect("insert-text",self.on_text_buffer_insert_text,None)
buff.connect("delete-range",self.on_text_buffer_delete_range,None)
buff.connect("changed",self.on_text_buffer_changed,None)
#buff.connect("mark-set",self.on_text_buffer_mark_set,None)
# connect signals
builder.connect_signals(self)
# setup to accept file drags
TARGET_TYPE_URI_LIST = 80
dnd_list = [ ( 'text/uri-list', 0, TARGET_TYPE_URI_LIST ) ]
#dnd_list = [("text/uri-list", 0, 25)]
self.statusbar.drag_dest_set( gtk.DEST_DEFAULT_MOTION |
gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
dnd_list, gtk.gdk.ACTION_COPY)
#self.text_view.drag_dest_set(0, [], 0)
self.statusbar.connect('drag_motion', self.on_drag_motion)
#self.text_view.connect('drag_drop', self.on_drag_drop)
self.statusbar.connect('drag_data_received', self.on_drag_data_received,None)
self.statusbar.connect('drag_end', self.on_drag_end,None)
#self.text_view.connect('drag_data_received', self.on_drag_data_received,None)
#self.text_view.connect('drag_end', self.on_drag_end,None)
# set the default icon to the GTK "edit" icon
gtk.window_set_default_icon_name(gtk.STOCK_EDIT)
# setup and initialize our statusbar
self.statusbar_cid = self.statusbar.get_context_id("Edile GTK+ Text Editor")
# setup search window
self.search_window.set_destroy_with_parent(True)
self.search_window.set_transient_for(self.window)
self.search_window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
self.search_window.set_title('Find | Edile Text Editor')
self.search_window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
###
# load plugins
###
self.load_plugins()
# figure out what to put in the window and set the filename to
should_load_default_file = (CONF_DEFAULT_FILE != "")
# open file from command line or stdin/pipe
if len(args) > 0:
# first file from command line
self.filename = args[0]
# don't load the default document
should_load_default_file = False
if len(args) > 2:
# there are other filenames. spawn instances for them
other_filenames = sys.argv[2:]
[self.spawn(other_file) for other_file in other_filenames]
if os.path.exists(self.filename):
# open first file
self.load_file(self.filename)
else:
# file doesn't exist. create it on save.
self.reset_default_status()
# let user know what's going on
if os.access(os.path.dirname(self.filename),os.W_OK):
print "\nfile %s will be created."%(self.filename)
else:
print "\nfile %s not writable!"%(self.filename)
if not sys.stdin.isatty():
# open from pipe
#self.filename = None
#^you can log output this way
buffer = self.text_view.get_buffer()
buffer.set_text(sys.stdin.read())
# move insertion point and scroll to top
buffer.place_cursor(buffer.get_start_iter())
self.reset_default_status()
else:
# open default document if appropriate
if should_load_default_file:
if (os.path.exists(CONF_DEFAULT_FILE)) & (os.access(CONF_DEFAULT_FILE,os.W_OK)):
self.load_file(CONF_DEFAULT_FILE)
else:
print "\n%s doesn't exist. not opening\n"%(CONF_DEFAULT_FILE)
self.reset_default_status()
else:
self.reset_default_status()
#tv_dnd_list = self.text_view.drag_dest_get_target_list()
#tv_dnd_list.extend(dnd_list)
#self.text_view.drag_dest_set_target_list(tv_dnd_list)
# Run main application window
def main(self):
self.window.show_all()
gtk.main()
if __name__ == "__main__":
editor = Edile()
editor.main()
# This is an example plugin definition file for the Edile text editor.
# http://edile.googlecode.com
# Edile loads plugins by simply importing a string loaded from
# a file:/// or http:// url. This code should define classes, the names
# of which will be displayed in Edile's menu bar as menus. The classes
# should define methods, which will be shown and run as menu items. The
# method name will be used for the menu item title, with underscores
# replaced by spaces and the resultant string titlecased. The plugin
# methods are provided with an interface object by the host application.
# This object defines the following methods to be used as callbacks:
# get_text(): returns the window's entire text
# get_selection(): returns the selected text
# get_selected_range(): returns a tuple of gtk text iter objects pointing to the start and end of the selection, respectively
# replace_selection(string): replaces the current selection with the passed in string
# insert(string): inserts the string as if the user had typed it
# open_file(path): opens the specified path in a new Edile instance
# new_window(): opens a new blank Edile instance
# message(string): displays a dialog window showing the specified string
# get_language(): the language of the current file
# get_filename(): the current file's path
# get_buffer(): returns the gtk text buffer object
# Plugin loading is controlled by the CONF_LOAD_PLUGINS and
# CONF_LOAD_PLUGINS_AS_ROOT configuration variables located at the top
# of the Edile script. Set them to False to not load plugins if you like.
# Plugins can set keyboard shorcuts by defining a 'shortcuts' dictionary
# in the top level class. This dict takes the function objects as keys
# and a gtk.accelerator_parse()-compatible tuple for the shortcut.
# This code is exec'd so be careful about the top level.
class Tools:
def document_statistics(self,interface):
doc = interface.get_text()
chars = len(doc)
words = len(doc.split())
lines = len(doc.split('\n'))
interface.message("Lines:\t%d\nWords:\t%d\nChars:\t%d"%(lines,words,chars))
def open_selection(self,interface):
sel = interface.get_selection()
interface.open_file(sel)
def insert_path(self,interface):
interface.insert(interface.get_filename())
def fetch_url(self,interface):
import urllib
url = interface.get_selection()
url_contents = urllib.urlopen(url).read()
interface.replace_selection(url_contents)
#shortcuts = {document_statistics:("F"),open_selection:("6"),insert_path:("C","2")}
def comment_after(self,interface):
buff = interface.get_buffer()
line_end = buff.get_iter_at_mark(buff.get_insert())
line_end.forward_to_line_end()
interface.select(line_end,line_end)
interface.insert('\t# ')
shortcuts = {open_selection:"<Ctrl><alt>o",document_statistics:"<Control>i", comment_after:"<alt><control>3"}
class Test:
def do_some_stuff(self,interface):
print interface.get_filename()
print interface.get_language()
interface.message("hi there")
interface.open_file()
def hexdump_window(self,interface):
import os
os.system('hexdump -C "%s" | edile'%(interface.get_filename()))
shortcuts = {do_some_stuff:("")}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment