## $Id: externaleditor.py,v 1.2 2001/06/11 21:34:48 jdhildeb Exp $
## System modules
from gtk import *
import gnome.zvt
import gnome.ui
import os, os.path, stat, tempfile, signal
from time import sleep
import string
## Local modules
import editor
# here we list the different kinds of hints that
# we know about for external editors. To add the hints for some editor,
# simply add it to the lists here. The tricky bit is figuring out the
# hex/octal codes for non-printing characters. I used the following
# procedure:
# $ cat >out (to send standard input to a file)
# type characters, followed by CTRL-D to end input
# $ xdd out (to get a hex dump of the file, or use od to get octal dump)
# another hint: use ESC for Meta key in Emacs
# another hint: use vim to create a file contain the special characters;
# they can be composed by using CTRL-V and then the character, then hex dump
# the file.
# the list of hints that appear in the preferences, with corresponding id's
hintlist = [ ('No editor hints', 'none'),
('Vi hints', 'vi'),
('Emacs hints', 'emacs') ]
# the hint sets
vicmds = { 'default-args': '',
'insert-file-at-top': '\0331GO\033:r %s\n',
'insert-file-at-cursor': '\033O\033:r %s\n',
'insert-file-at-bottom': '\033G:r %s\n',
'write-file':'\033:w\n',
'write-file-as':'\033:w %s\n',
'before-insert-text': '\033i',
'after-insert-text': '\033',
'jump-to-top': '\0331G',
'jump-to-bottom': '\033G',
'format-current-paragraph': "\033}mj{:.,'j!fmt -w %d\n",
'exit': '\033:q\n' }
emacscmds = { 'default-args': '-nw', # tell emacs not to open its own window
'insert-file-at-top': '\033<\x18i\x01\x0b%s\n',
'insert-file-at-cursor': '\x18i\x01\x0b%s\n',
'insert-file-at-bottom': '\033>\x05\n\x18i\x01\x0b%s\n',
'write-file':'\x18\x13',
'write-file-as':'\x18\x17\x01\x0b%s\n',
'before-insert-text': '',
'after-insert-text': '',
'jump-to-top': '\033<',
'jump-to-bottom': '\033>',
'exit': '\x18\x03' }
# mapping from id to hint set
hints = { 'none':{}, 'vi':vicmds, 'emacs':emacscmds }
class ExternalEditor(editor.Editor):
# create the widget and return it
def widget( self ):
return self.box
# create the widget, use the given font if possible
def __init__( self, prefs, font ):
cmd = string.split( prefs.externaledentry )[0]
if not os.access( cmd, 5 ):
exc = "BadEditorPath"
raise exc, cmd
self.hints = hints[ prefs.editorhints ]
self.bincmd = prefs.externaledentry
font_name = prefs.externaled_font
# set TERM=xterm if not set; otherwise most keybindings won't work
if not os.environ.has_key('TERM') or os.environ['TERM'] in ['','dumb']:
os.environ['TERM'] = 'xterm'
self.term = gnome.zvt.ZvtTerm(80, 25)
self.term.set_blink( FALSE )
self.term.set_scrollback(0)
self.term.set_font_name(font_name);
self.term.connect("child_died", self.child_died_event )
self.term.connect("button_press_event", self.button_event )
self.term.connect("focus_in_event", self.focus_in_event )
self.term.connect("focus_out_event", self.focus_out_event )
self.term.show()
self.box = GtkVBox()
self.box.pack_start( self.term, expand=TRUE)
self.box.show()
#charwidth = term.charwidth
#charheight = term.charheight
#win.set_geometry_hints(geometry_widget=term,
#min_width=2*charwidth, min_height=2*charheight,
#base_width=charwidth, base_height=charheight,
#width_inc=charwidth, height_inc=charheight)
# don't start the editor yet. Wait for insert_text to be
# called, and then start the editor on that file. This allows
# us to have at least minimal support for pretty much any editor
# create a temporary dir in which we can save our files
self.tempdir = tempfile.mktemp();
os.mkdir( self.tempdir, 0700 )
self.child_alive = 0
self.finished = 0 # we're not done editing yet
def start_editor( self ):
pid = self.term.forkpty()
if pid == -1:
print "Couldn't fork"
os._exit(1)
if pid == 0:
# break up the command and the arguments
cmd = string.split( self.bincmd )[0]
args = string.split( self.bincmd )[1:]
# if no arguments were specified explicitly, add any that
# we have from our hints
if args == [] and self.hints.has_key( "default-args" ):
args = string.split( self.hints["default-args"] )
args.insert( 0, cmd )
args.append( self.tfilename )
os.execvp( cmd, args )
print "Couldn't exec external editor"
os._exit(1)
self.child_alive = 1
# release all resources
def destroy( self ):
self.finished = 1
# politely ask the child to exit
if( self.child_alive ):
self.use_hint( "exit" )
try:
sleep( .3 )
except IOError:
# sometimes I get "interrupted system call" here, presumably
# because a signal is sent to our process when the child exits
pass
while events_pending():
# if the child exits, the child_died_event will be called,
# so we need to give it a chance to run
mainiteration(FALSE)
# if child is still alive, kill it
if( self.child_alive ):
self.term.killchild( signal.SIGHUP )
# unlink all files in temp dir, ignoring errors
# and delete the directory, too
try:
files = os.listdir( self.tempdir )
for f in files:
os.unlink( os.path.join( self.tempdir, f ) )
os.rmdir( self.tempdir )
except OSError:
pass
def child_died_event( self, button=None ):
# if we're not finished, restart the editor
if( self.finished ):
self.child_alive = 0
else:
self.start_editor()
# jump to top or bottom of text
# pos='top' or pos='bottom'
def set_scroll_position( self, pos ):
if pos == 'top':
self.use_hint( 'jump-to-top' )
elif pos == 'bottom':
self.use_hint( 'jump-to-bottom' )
def button_event( self, button, arg ):
self.grab_focus()
def focus_in_event( self, button, arg ):
self.term.set_blink( TRUE )
def focus_out_event( self, button, arg ):
self.term.set_blink( FALSE )
def grab_focus( self ):
self.term.grab_focus()
def freeze( self ):
pass
def thaw( self ):
pass
def redraw( self ):
pass
# insert text. Possible values for 'pos'
# are 'top', 'bottom', and 'cursor'
def insert_text( self, text, pos='cursor' ):
# if it's a one-line string, the editor already has a file open,
# and we have a hint for inserting the text directly, then do it
if self.child_alive and string.find( text, '\n' ) == -1 \
and self.hints.has_key('before-insert-text'):
self.use_hint( "before-insert-text" )
self.term.writechild( text )
self.use_hint( "after-insert-text" )
return
# otherwise save text into a file
txtfname = self.mktemp();
txtfile = open( txtfname, 'w' )
txtfile.write( text )
txtfile.close()
# if the editor hasn't been started, start it on this file
if( not self.child_alive ):
self.tfilename = txtfname
self.start_editor()
return
self.insert_file( txtfname, pos='cursor' )
def cut_clipboard( self ):
pass
def copy_clipboard( self ):
pass
def paste_clipboard( self ):
pass
def insert_file( self, fname, pos ):
# cause the editor to insert the file at the correct position
if pos == 'top':
self.use_hint( "insert-file-at-top", fname )
elif pos == 'bottom':
self.use_hint( "insert-file-at-bottom", fname )
elif pos == 'cursor':
self.use_hint( "insert-file-at-cursor", fname )
def format_paragraph( self, width ):
self.use_hint( 'format-current-paragraph', width )
def get_text( self ):
self.write_file()
# load the file into a string and return it
file = open( self.tfilename, 'r' )
msg = file.read()
file.close()
return msg
def write_file( self ):
# if we can't tell the editor how to save the file, we assume
# that the user has saved it manually.
if not self.hints.has_key( "write-file" ):
return
# cause editor to write the file
self.use_hint( "write-file" )
if self.hints.has_key( "write-file-as" ):
# cause the editor to write a second file, so
# we can tell when the first has been written
f2 = self.tfilename + ".2"
self.use_hint( "write-file-as", f2 )
while not os.path.isfile( f2 ):
sleep( 0.25 )
os.unlink( f2 )
else:
# if we can't tell the editor to write a second file,
# we'd better just wait a bit and hope for good luck
sleep( 3 )
def get_length( self ):
self.write_file()
size = os.stat( self.tfilename )[stat.ST_SIZE]
return size
# the following functions are private
def use_hint( self, key, args=None ):
if( self.hints.has_key( key ) ):
# turn bell off to eliminate beeps
self.term.set_bell( FALSE )
if( args ):
self.term.writechild( self.hints[key] % args )
else:
self.term.writechild( self.hints[key] )
self.term.set_bell( TRUE )
def mktemp( self ):
# name the file with a .txt extension so that emacs knows doesn't
# pick an inappropriate mode
s = tempfile.tempdir
tempfile.tempdir = self.tempdir
f = tempfile.mktemp() + ".txt"
tempfile.tempdir = s
return f
syntax highlighted by Code2HTML, v. 0.9.1