#!/usr/bin/env python
r''' faulkner's scriptable text editor.
imports configuration variables from config.flauxtext when imported.
also tries to import psyco, a python JIT compiler: http://psyco.sf.net
flauxtext is released under the GNU General Public License Version 2.
if you should need my full legal name [which does not contain anything like 'faulkner'], you may contact me at
faulkner@users.sf.net
usage:
flauxtext [-] [--help|-h] [--test|-t] [--version|-v] [--profile [file_to_write]] \
[--noalpha] [--text|-T|-x] [file_to_open [file_to_open [...]]] \
[dirname [dirname [...]]] [--regexp|-r "regexp" dirname [dirname [...]]]
- read stdin until EOF, put it into the first buffer
--help, -h print this help message and exit
--test run doctest on this module [ignores any other commandline opts] then exit
--profile [file_to_write]
run profile on this module [if file_to_write does not yet exist, i will write the profile output to it]
when this opens flauxtext, play around with a few tools and functions and whatnot
--version, -v print my version number and exit
--noalpha ignore transparency setting in config.flauxtext, and be completely opaque
transparency only works on winXP with the win32all package anyway
to get the same effect, you could set "transparency" in config.flauxtext to False
--regexp, -r open all files matching regexp in all dirnames following -r|--regexp; you don't need to quote regexp
this must be the last option
handiest values for regexp:
"(?i).*\.pyw?$" # this matches all files ending in .py, .pyw, .PY, .PYW
"(?i).*\.(c|cpp|h)$" # open all files ending in .c, .cpp, .h, .C, .CPP, .H, .Cpp, .cPp, .cpP
".*~$" # open all files ending in ~ [tilde]
note: you must quote the pattern.
--text, -T, -x open only text files of those specified on the commandline; do not try to open binary files
--notrack, -n ignore the instancetracking configuration variable, do not alert previous instances, do open a new instance
fair warning: i may hang for a while if you pass me too many dirnames, or dirname is an exceptionally large dir.
command-line examples:
flauxtext .
opens all files in current directory
flauxtext -x /
opens all text files in root directory on POSIX systems [for windows, reverse the slash ;-]
flauxtext --profile %home%\f%date%.pro
writes a log of my profile to a file in your %HOME% directory, whose name contains the current date
flauxtext -t > /tmp/f.test
writes a log of my doctest to that temp file [the pipe and slashes indicate a POSIX compliant OS]
flauxtext -nxr ".*~$" .
DOES NOT WORK
flauxtext's handling of options is primitive, so you must expand "-nxr" out to "-n -x -r"
python command-line examples:
>>> ft = flauxtext.flauxtext(['C:\\autoexec.bat','c:\\program files\\flauxtext\\config.flauxtext'])
>>> ft.open('C:\\wepkey.txt')
>>> assert ft.start == ft.__call__
>>> ft()
'''
try:
import psyco # a JIT compiler
psyco.profile()
except: pass
import gtk, pango, gobject, os, sys, re, string, fnmatch, copy, threading, time, xml.parsers.expat
try:
from subprocess import Popen as subprocess_Popen
Popen = lambda cmd: subprocess_Popen(cmd, shell=True)
except:
import threading
Popen = lambda cmd: threading.Thread(target=os.popen, args=(cmd,)).start()
if gtk.pygtk_version < (2,4):
print "i'm sorry, but i need PyGTK 2.4 or later.\nyou can download the latest version of pygtk here:\nhttp://www.pygtk.org/"
sys.exit(1)
PATH = [os.path.dirname(os.path.abspath(sys.argv[0])), os.path.abspath('.'), os.path.abspath('..')]
DND_TARGETS = [('application/octet-stream', 0, 8), # octet=8
('TEXT', 0, 23), # string.lowercase.index('x')=23
('STRING', 0, 6), # len('string')=6
('text/uri', 0, 43), # len('text')=4,len('uri')=3
]
SEARCH_ENGINES = {
'Google':'"http://www.google.com/search?q',
'GoogleImgs':'"http://images.google.com/images?q',
'Freshmeat':'"http://freshmeat.net/search/?q',
'Wikipedia':'"http://en.wikipedia.org/w/wiki.phtml?search',
'OED':'"http://dictionary.oed.com/cgi/findword?query_type=word&queryword',
'MWThes':'"http://www.m-w.com/cgi-bin/thesaurus?va',
'MW':'"http://www.m-w.com/cgi-bin/dictionary?va',
'SF':'"http://sourceforge.net/search/?words',
}
WIN32_PRINTERS_LEVEL = 5
ESC_KEYVAL = 65307
TAB_KEYVAL = 65289
SHIFT_TAB_KEYVAL = 65056
CAPS_LOCK_KEYVAL = 65509
UNTITLED_FILENAME = 'untitled'
gtk.STOCK_NONE = None
FIND_FIND = 1
FIND_FIND_ALL = 2
FIND_REPLACE = 3
FIND_REPLACE_ALL = 4
FIND_HERE_FORWARD = 0
FIND_AFTER_START = 1
FIND_HERE_BACKWARD = 2
FIND_BEFORE_END = 3
BINARY_TEXT_FILTER_RATIO = 0.3
txtchars = ''.join(map(chr, range(32, 127))) + '\n\r\t\b'
def istextfile(path, blocksize=512):
''' returns True if path is a text file, False otherwise, based on the first blocksize chars in path.
'''
if not os.path.isfile(path):
return False
s = open(path).read(blocksize)
if "\0" in s:
return False
if not s:
return True # empty files are text
if float(len(s.translate(string.maketrans('', ''), txtchars)))/float(len(s)) > BINARY_TEXT_FILTER_RATIO:
return False
return True
xml_encode = lambda s: ''.join([('%d;' % ord(x)) for x in s])
html_color_to_gdk = lambda s: gtk.gdk.Color(*tuple([256 * int(s[i:i+2], 16) for i in range(0, 5, 2)]))
def imp0rt(*nyms):
''' convenience function for safely importing modules into the global namespace, doesn't catch ImportErrors
'''
for nym in nyms:
try:
eval(nym)
except:
exec 'global %s; import %s' % (nym.split('.')[0], nym)
def getmyfile(fnym, hugebool=False, readlen=1023, mk=True):
''' takes a file basename, looks in my global PATH list, returns a full pathname.
if fnym is not in PATH and hugebool evaluates to True, uses
'cd \\ && dir /s /b ' on nt, or 'locate ' on posix to get a list of candidates.
if 'flauxtext' is in the first readlen bytes of a file in this list,
that candidate's pathname is returned.
if all that fails and mk evaluates to True, make the file in
os.path.dirname(__file__), and return the new path.
'''
for pth in PATH:
if os.path.isfile(os.path.join(pth, fnym)): return os.path.join(pth, fnym)
if hugebool:
if os.name=='nt': lookcmd='cd \\ && dir /s /b '
else: lookcmd='locate '
for lin in os.popen(lookcmd+fnym):
if os.path.isfile(lin) and 'flauxtext' in open(lin).read(readlen): return lin
if mk:
newfnym=os.path.join(os.path.dirname(__file__),fnym)
open(newfnym,'w').close() # create an empty file
return newfnym
configfn = getmyfile('config.flauxtext')
contactsfn = getmyfile('contacts.flauxtext')
mrufn = getmyfile('history.flauxtext')
specialsfn = getmyfile('specials.flauxtext')
mruxmlfn = getmyfile('mru.xml')
uixmlfn = getmyfile('ui.xml') # User Interface eXtended Markup Language FileName--yay, acronyms!
specialsxmlfn = getmyfile('specials.xml')
uixmlf = open(uixmlfn)
uixmldata = uixmlf.read()
uixmlf.close()
mruxmlf = open(mruxmlfn)
mruxmldata = mruxmlf.read()
mruxmlf.close()
specialsxmlf = open(specialsxmlfn)
specialsxmldata = specialsxmlf.read()
specialsxmlf.close()
del uixmlf, mruxmlf, specialsxmlf
execfile(configfn, globals())
class config: execfile(configfn, globals(), locals())
instancetrackingfn = os.path.join(tempdir, 'instancetracking.flauxtext')
def imp0rt_plugin(plg):
''' first tries to evaluate the name in plg
if that fails, it __import__s plg into the global namespace after changing sys.path to [plugindir]
'''
if plg.startswith('plugin'):
plg = plg[6:]
try:
eval('plugin' + plg)
except:
try:
real_path = sys.path
sys.path = [plugindir] + sys.path
globals()['plugin' + plg] = __import__(plg)
eval('plugin' + plg).flauxtext = Namespace()
sys.path = real_path
except Exception, e:
print '\nerror loading plugin:', plg, '...', e
def __imp0rt_plugin__(plg):
''' equivalent of __import__ for plugins.
'''
imp0rt_plugin(plg)
if not plg.startswith('plugin'):
plg = 'plugin' + plg
return eval(plg)
if proxyserver != '':
imp0rt('urllib2')
authinfo = urllib2.HTTPBasicAuthHandler()
authinfo.add_password(None, proxyserver, proxyname, proxypass)
urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler({'http':proxyserver}),
authinfo, urllib2.CacheFTPHandler))
def respond_dialog_on_escape(d, r=0):
''' send response r to dialog d when you hit the escape key inside it.
'''
def callback(wid, evt):
if evt.keyval == ESC_KEYVAL:
wid.response(r)
d.connect('key-press-event', callback)
class Namespace:
''' allows you to treat this global namespace as an object with fields as opposed to a dictionary.
passed to plugins so they can treat the application as if it were a module like any other.
'''
def __init__(self):
self.__dict__ = globals()
class UndoStackEntry:
def __init__(self, *a, **kw):
self.was_inserted = kw.get('was_inserted', True)
self.data = kw.get('data', '')
self.offset = kw.get('offset', 0)
def __repr__(self):
return '<%s %d>%s' % (['deleted', 'inserted'][self.was_inserted], self.offset, self.data)
def adjust_offset(self, d_offset):
self.offset += d_offset
def append_data(self, new_data):
self.data += new_data
class UndoStack:
def __init__(self):
self.stack = []
self.pointer = -1
def previous(self):
if self.pointer > 0:
return self.stack[pointer - 1]
def current(self):
try:
return self.stack[self.pointer]
except:
return None
def next(self):
if self.pointer < len(self.stack):
return self.stack[self.pointer + 1]
def push(self, entry):
self.stack.append(entry)
self.pointer += 1
def __len__(self):
return len(self.stack)
def __getitem__(self, index):
return self.stack[index]
def __setitem__(self, index, value):
self.stack[index] = value
def __eq__(self, o):
return self.__dict__ == o.__dict__
class FlauxText:
''' self.vbox contains self.notebk [a gtk.Notebook], self.menubar,
optional self.toolbar, 4 optional status bars in self.statbarhbox.
Example:
>>> from flauxtext import * # import all the modules i import, along with this class
>>> f = FlauxText()
>>> f.w = gtk.Window(); f.w.add(f.vbox)
>>> f.w.show_all(); gtk.main()
if, in your own pygtk app, you want my hotkeys to work,
you need to connect "f.uiman" to your window.
open my source and find "def __call__" to see how to do that.
if you want FlauxText to manage your window's title and icon
[and parent the modal dialogs, etc.], set your toplevel window as
the "w" attribute of FlauxText, as i did above.
the muggle [non-magical] methods are alphabetized in my source.
if you want to disconnect events in textbuffers or textviews from their handlers,
use the_widget.disconnect(self.handler_ids[self.notebk.get_current_page()]['event-name'])
methods beginning with 'callback_' correspond to actions in ui.xml.
to learn about what i do in __init__ without grokking the source:
>>> import flauxtext, pprint
>>> ft=flauxtext.FlauxText()
>>> pprint.pprint(ft.__dict__)
'''
# __init__ at end; start = __call__ in magic methods
# find "# XXX" to find sad/angry callbacks
clipboard = gtk.clipboard_get()
# MAGIC METHODS!
# not alphabetized like all other methods; grouped logically
def __call__(self, filenames=()):
''' makes an otherwise empty window, adorns it, and puts an instance of FlauxText in it.
oh, and it opens files passed from the command line with open().
besides a list of strings, "filenames" can also be a single string representing
a pathname.
'''
def house_keeping():
''' called every half second or so, keeps house:
open files in instancetracking.flauxtext if necessary
update self.statbar_cursor with the current cursor position
'''
# open files in instancetracking
if self.tempfilemtime < os.path.getmtime(instancetrackingfn):
insff = open(instancetrackingfn)
filestoopen = re.split('\r*\n*', insff.read())
insff.close()
for i in filestoopen:
if os.path.isfile(i):
self.open(i)
self.tempfilemtime = os.path.getmtime(instancetrackingfn)
self.w.present()
# update line, column numbers and insert/overwrite
tv = self.current_textview()
buf = tv.get_buffer()
ti = buf.get_iter_at_mark(buf.get_mark("insert"))
self.statbar_cursor.pop(1)
self.statbar_cursor.push(1, statbarcursorformat % (ti.get_line()+1,ti.get_visible_line_offset()))
self.statbar_insovr.pop(1)
self.statbar_insovr.push(1, (tv.get_overwrite() and "ovr" or "ins"))
return True # tell gobject.timeout_add to keep calling this method
self.w = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.w.connect('destroy-event', self.callback_quit)
self.w.connect('delete-event', self.callback_quit)
if usectrltabs:
self.w.connect('key-press-event', self.cycle_tabs__)
self.w.set_title(windowtitle % self.current_filename())
self.w.set_icon(gtk.gdk.pixbuf_new_from_file(getmyfile('icon.xpm')))
self.w.add(self.vbox)
gobject.timeout_add(updatetitlemilliseconds, house_keeping)
self.w.add_accel_group(self.accelgroup)
if winderpos and isinstance(winderpos, (list, tuple)) and len(winderpos) == 2:
self.w.move(*winderpos)
if windersize and isinstance(windersize, (list, tuple)) and len(windersize) == 2:
self.w.resize(*windersize)
if maxwindowatstart:
self.w.maximize()
self.w.show()
execfile(os.path.join(plugindir, '__call__.py'), globals(), locals())
# open files requested on the cli
if isinstance(filenames, (str, unicode)):
filenames = [filenames]
elif not isinstance(filenames, (list, tuple)):
print "the only argument to my constructor has to be either a string or an iterable series of strings. [unicodes are strings.]",
print "i don't understand", filenames
filenames = []
if '-' in filenames:
def pipe():
txt = sys.stdin.readline()
textbuffer = self.current_buffer()
textbuffer.filename = 'sys.stdin'
while txt != '':
textbuffer.insert(textbuffer.get_end_iter(), txt)
txt = sys.stdin.readline()
imp0rt('threading')
threading.Thread(target=pipe).start()
filenames.remove('-')
for i in filenames:
if i in os.listdir('.'):
i = os.path.abspath(i)
if os.path.isfile(i):
self.open(i)
elif os.path.isdir(i):
map(self.open, [os.path.join(i, j) for j in os.listdir(i) if istextfile(os.path.join(i, j))])
self.current_textview().grab_focus()
gtk.main()
return 0
def __contains__(self, i=UNTITLED_FILENAME):
for b in self:
if (b.filename == i) or (i == os.path.basename(b.filename)):
return True
return False
def __str__(self, which=None):
''' return the text in which buffer. if which==None:return text in current buffer.
'''
if which == None:
which = self.notebk.get_current_page()
return self[which].get_all_text()
def __list__(self):
''' return a list of my buffers.
'''
return [b for b in self]
def __iter__(self):
''' yields self[i] for i in range(len(self))
'''
for i in xrange(len(self)):
yield self[i]
def __getitem__(self, whch=Ellipsis):
''' allows you to treat an instance of this class as a list, dict, or something else entirely:
>>> flauxtext.FlauxText("C:\\autoexec.bat")[0]
>>> flauxtext.FlauxText("C:\\autoexec.bat")['C:\\autoexec.bat']
returns a gtk.TextBuffer containing the contents of c:\\autoexec.bat
>>> flauxtext.FlauxText(glob.glob('*.txt'))[...]
returns a list of all my gtk.TextBuffer()s
>>> flauxtext.FlauxText(glob.glob('*.txt'))[[1,3]]
>>> flauxtext.FlauxText(glob.glob('*.txt'))[(1,3)]
both return lists of my first two TextBuffers
'''
if whch == Ellipsis:
return list(self)
elif whch == None:
return self.current_buffer()
elif isinstance(whch, slice):
return [self[i] for i in range(whch.start, whch.stop, isinstance(whch.step, int) and whch.step or 1)]
elif isinstance(whch, (int, long)) and (0 <= whch < len(self)):
return self.notebk.get_nth_page(whch).get_child().get_buffer()
elif isinstance(whch, str):
for i, b in enumerate(self):
if whch in [b.filename, os.path.basename(b.filename)]:
return self[i]
elif isinstance(whch, (tuple, list)):
return [self[i] for i in whch]
def __getslice__(self, *args):
''' args are apply()d to range
'''
return [self[i] for i in range(*args)]
def __len__(self):
return self.notebk.get_n_pages()
def __le__(self, other):
return (type(self) == type(other)) and (False not in [(i in other.all_files()) for i in self.all_files()])
def __ge__(self, other):
return (type(self) == type(other)) and other.__le__(self)
def __eq__(self, other):
return (type(self) == type(other)) and (self >= other) and (self <= other)
def __lt__(self, other):
return (type(self) == type(other)) and not (self >= other)
def __gt__(self, other):
if type(self) != type(other):
return False
else:
return not self <= other
def __ne__(self, other):
if type(self) != type(other):
return False
else:
return not self == other
def __add__(self, otherself, v=True, expand=True, fill=True, padding=0):
''' usage:
newselfvbox = ft + flauxtext.FlauxText()
takes 2 instances of FlauxText, packs both their main vboxes into a new VBox [if v; else: HBox],
and returns that new box.
'''
if v:
self.vbox.pack_start(otherself.vbox,expand,fill,padding)
return otherself.vbox
else:
h=gtk.HBox()
h.show()
h.pack_start(self.vbox())
h.pack_start(otherself.vbox)
return h
def __iadd__(self, otherself, *a):
''' ft += flauxtext.FlauxText()
is the in-place version of
ft.vbox = ft + flauxtext.FlauxText()
'''
self.vbox = self.__add__(otherself, *a)
def __radd__(self,otherself,v=True,expand=True,fill=True,padding=0):
''' proxy for __add__; does exactly the same thing, only Reversed
'''
if v:
otherself.vbox.pack_start(self.vbox,expand,fill,padding)
return otherself.vbox
else:
h=gtk.HBox()
h.show()
h.pack_start(otherself.vbox())
h.pack_start(self.vbox)
return h
def __mul__(self, many, v=True):
''' usage:
newselfvbox=self*many
if you want newselfvbox to hold only an HBox which holds many selves, use:
newselfvbox=self.__rmul__(many,False)
'''
if many < 1:
many = 1
if v:
b = gtk.VBox()
else:
b = gtk.HBox()
imp0rt('copy')
for i in range(many - 1):
b.pack_start(copy.deepcopy(self.vbox))
return self.vbox
def __imul__(self, many, v=True):
''' self *= many
'''
self.vbox = self.__mul__(many, v)
def __rmul__(self, many, v=True):
''' usage:
newselfvbox=many*self
if you want newselfvbox to hold only an HBox which holds many selves, use:
newselfvbox=self.__rmul__(many,False)
this method is a proxy for __mul__
'''
return self.__mul__(many,v)
def __pow__(self, side):
''' makes a square grid of side ** 2 me's
'''
v = gtk.VBox()
v.show()
h = gtk.HBox()
h.show()
h.pack_start(self.vbox)
imp0rt('copy')
for i in range(side-1):
h = gtk.HBox()
h.show()
v.pack_start(h)
for i in range(side):
h.pack_start(copy.deepcopy(self.vbox))
return v
def __ipow__(self, side):
'''
'''
self.vbox = self ** side
def __rpow__(self,side):
''' nonsensical, but usage:
gridselfvbox=side**self
this method is a proxy for __pow__, for consistency
'''
return self.__pow__(side)
# end magic methods
def all_files(self):
return [b.filename for b in self]
def autoindent(self, txtb):
''' called whenever you change the text in txtb [the current gtk.TextBuffer]
can't be put in registerwithundo for strange scheduling reasons. :-P
'''
curundoentry = self.current_undo_entry()
curfn = self.current_filename()
insitr = txtb.get_iter_at_mark(txtb.get_insert())
ins_str = ''
if self.autoident and (curundoentry != None) and (curundoentry.was_inserted) and (curundoentry.data[-1] == '\n'):
prevline = txtb.get_text(txtb.get_iter_at_line(insitr.get_line()-1),
txtb.get_iter_at_mark(txtb.get_insert()))[:-1]
ins_str = re.split('[^ \t]*', prevline)[0]
for key, val in commentchars.items():
if fnmatch.fnmatch(curfn, key) and isinstance(val, str):
prevline = prevline.split(val)[0].rstrip()
break
if (len(prevline) > 0) and (prevline[-1] in ':{(['):
ins_str += spaceselectedinsertstring
if rightedge and (insitr.get_line_offset() >= rightedgecol) and (len(ins_str) < rightedgecol-1):
ins_str = '\n' + ins_str
txtb.insert_at_cursor(ins_str)
def callback_add_remove_special(self, action):
''' either adds or removes the current file from the specials list.
'''
fn = self.current_filename()
f = open(specialsfn)
ls = re.split('\r?\n?', f.read())
f.close()
if fn in ls:
ls.remove(fn)
self.specialfilelist = ls
f = open(specialsfn,'w')
f.write('\n'.join(ls))
else:
self.specialfilelist.append(fn)
f = open(specialsfn,'a') # append filename
f.write('\n'+fn)
f.close()
self.uiman.remove_ui(self.uiman.merge_ids['specials'])
specials_xml_data = ''
for i in xrange(len(self.specialfilelist)):
if os.path.isfile(self.specialfilelist[i]):
specials_xml_data += '' % (
i, (i + 1), xml_encode(os.path.basename(self.specialfilelist[i].replace('_', '__'))), xml_encode(self.specialfilelist[i])
)
self.xml_to_ui(specialsxmldata.replace('', specials_xml_data), 'specials')
def callback_chtheme(self, action=None):
''' Popen(gtkchthemecmd)
'''
Popen(gtkchthemecmd)
def callback_close_all_but_current(self, action=None):
''' loops backwards through my pages:
closes the page if it isn't current
'''
cur = self.notebk.get_current_page()
for i in range(self.notebk.get_n_pages() - 1, -1, -1):
if i != cur:
self.close_buffer(None, i)
def callback_close_current(self, *a):
''' alias of close_buffer.
'''
self.close_buffer(*a)
def callback_colors(self, action=None):
''' opens a dialog containing a gtk.ColorSelection widget and some buttons,
lets you change the text and background colors, and insert rgb values in various formats into the current buffer:
#rrggbb, (rr,gg,bb,aa), (rr,gg,bb), <-- hex
#rrrgggbbb, (rrr,ggg,bbb,aaa), (rrr,ggg,bbb), <-- decimal
'''
curbuf = self.current_buffer()
d = self.mk_dialog("what is your favorite color?", ('done', 0))
c = gtk.ColorSelection()
d.vbox.pack_start(c)
c.set_has_palette(True)
c.set_has_opacity_control(True)
c.set_previous_alpha(65535)
c.set_current_alpha(65535)
try:
c.set_current_color(html_color_to_gdk(self.current_selection_bounds_or_all(True).strip('#')))
except:
pass
def set_all_text_color(btn):
for txtb in list(self):
t = gtk.TextTag('becky')
t.set_property('foreground-gdk', c.get_current_color())
try:
txtb.get_property('tag-table').remove(curbuf.get_property('tag-table').lookup('becky'))
except:
pass
txtb.get_tag_table().add(t)
curbuf.apply_tag_by_name('becky', curbuf.get_start_iter(), curbuf.get_end_iter())
def set_text_color(btn):
t = gtk.TextTag('becky')
t.set_property('foreground-gdk', c.get_current_color())
try: curbuf.get_property('tag-table').remove(curbuf.get_property('tag-table').lookup('becky'))
except: pass
curbuf.get_tag_table().add(t)
curbuf.apply_tag_by_name('becky', curbuf.get_start_iter(),curbuf.get_end_iter())
def set_all_background_color(btn):
for txtb in list(self):
t=gtk.TextTag('bg-gdk')
t.set_property('foreground-gdk',c.get_current_color())
try: txtb.get_property('tag-table').remove(curbuf.get_property('tag-table').lookup('bg-gdk'))
except: pass
txtb.get_tag_table().add(t)
curbuf.apply_tag_by_name('becky',curbuf.get_start_iter(),curbuf.get_end_iter())
def set_background_color(btn):
t=gtk.TextTag('bg-gdk')
t.set_property('background-gdk',c.get_current_color())
try:
curbuf.get_property('tag-table').remove(curbuf.get_property('tag-table').lookup('bg-gdk'))
except:
pass
curbuf.get_tag_table().add(t)
curbuf.apply_tag_by_name('bg-gdk',curbuf.get_start_iter(),curbuf.get_end_iter())
def insert(btn):
if 'a' in btn.get_label():
alpha = c.get_current_alpha()/256
else:
alpha = False
tuple_bool = ('(' in btn.get_label() and ',' in btn.get_label()) # :-)
hex_bool = (not 'rrr' in btn.get_label() and not 'bbb' in btn.get_label())
gdk_color = c.get_current_color()
r = gdk_color.red / 256
g = gdk_color.green / 256
b = gdk_color.blue / 256
if alpha is False:
a = ''
else:
a = alpha
if hex_bool:
r, g, b = map(lambda x: hex(x)[2:], [r, g, b])
rj = 2
try:
a = hex(a)[2:]
except:
pass
else:
r, g, b, a = map(str, [r, g, b, a])
rj = 3
r, g, b = map(lambda x: x.rjust(rj).replace(' ', '0'), [r, g, b])
if len(a) > 0:
a = a.rjust(rj).replace(' ', '0')
if tuple_bool:
s = '(' + ','.join((r, g, b, a)) + ')'
else:
s = '#' + r + g + b + a
curbuf.insert_at_cursor(s.replace(',)', ')'))
for button_set in [
[('set text color', set_text_color), ('set all text color', set_all_text_color)],
[('set background color', set_background_color), ('set all background color', set_all_background_color)],
[('insert #rrggbb', insert), ('insert #rrrgggbbb', insert)],
[('insert (rr,gg,bb)', insert), ('insert (rrr,ggg,bbb)', insert)],
[('insert (rr,gg,bb,aa)', insert), ('insert (rrr,ggg,bbb,aaa)', insert)],
]:
hbox = gtk.HBox()
d.vbox.pack_start(hbox)
for button_text, callback in button_set:
button = gtk.Button(button_text)
button.connect('clicked', callback)
hbox.pack_start(button)
d.run()
def callback_copy_current_path_to_clipboard(self, action=None):
''' copy the current file's full pathname to your clipboard.
'''
self.clipboard.set_text(self.current_filename())
def callback_cvs_commit(self, action=None):
''' commit current file to your CVS repository
'''
if self.current_filename() == UNTITLED_FILENAME:
self.flash_info("YOU MUST SAVE/OPEN A FILE BEFORE YOU CAN COMMIT IT TO CVS.", 4e3, 'cvs_commit_bad_filename')
return
d = self.mk_dialog('cvs options?', (gtk.STOCK_OK, 1, gtk.STOCK_CANCEL, 0))
d.vbox.pack_start(gtk.Label("change current working directory to"))
c = gtk.Entry()
d.vbox.pack_start(c)
c.set_text(os.path.dirname(self.current_filename()))
d.vbox.pack_start(gtk.Label("then run\n'" + cvscmd + "' +"))
e = gtk.Entry()
d.vbox.pack_start(e)
e.set_text(self.cvsops)
e.connect('activate', lambda x=None,y=None: d.response(1))
d.vbox.pack_start(gtk.Label('CVSROOT = ' + os.environ.get('CVSROOT','[undefined]')))
d.vbox.pack_start(gtk.Label('CVS_RSH = ' + os.environ.get('CVS_RSH','[undefined]')))
self.cvsops = e.get_text()
if d.run():
os.chdir(c.get_text())
Popen(termcmd + cvscmd + self.cvsops)
def callback_delete(self, action=None):
''' permanently deletes the file in current buffer, then closes it.
'''
fn = self.current_filename()
d = self.mk_dialog('Are you sure?', (gtk.STOCK_NO,1,gtk.STOCK_YES,0))
d.vbox.pack_start(gtk.Label('do you want to permanently delete this file?\n'+fn))
if d.run(): return
rmcmd = ['rm ', 'del '][os.name == 'nt']
if 0 == os.system(rmcmd + fn):
self.flash_info(fn + ' SUCCESSFULLY DELETED', 4000, 'deleted')
else:
self.flash_info("i couldn't delete " + fn, 4000, 'cannot_delete')
self.manage_mru(fn)
self.current_buffer().set_text('')
self.notebk.set_tab_label_text(self.notebk.get_nth_page(self.notebk.get_current_page()), UNTITLED_FILENAME)
if len(self) == 1:
self.new_buffer()
self.close_buffer()
def callback_diff(self, action=None):
''' generate a diff file, open it in a new buffer, and offer to save it
'''
oldfn = self.current_filename()
d = gtk.FileChooserDialog("diff current_file with what file?",None,gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL,0,gtk.STOCK_OPEN,1))
d.set_property("show-hidden", openshowhidden)
d.set_default_response(1)
d.set_current_folder(os.path.dirname(oldfn))
r = d.run()
fn = d.get_filename()
d.destroy()
if r:
imp0rt('difflib')
self.new_buffer().set_text('\n'.join([
i for i in difflib.context_diff(
re.split('\r?\n?', self.current_text()),
re.split('\r?\n?', open(fn).read())
)
]
))
def callback_edit_my(self, what, *a):
''' depending on action.get_name(), opens one of my text files [source, config file, ui.xml, etc.].
'''
d = {
'config': 'configfn',
'contacts': 'contactsfn',
'history': 'mrufn',
'readme': 'getmyfile("readme.txt")',
'regexp': 'getmyfile("regexp.txt")',
'source': '__file__',
'specials': 'specialsfn',
'uixml': 'uixmlfn',
}
if what in d:
if what == 'source':
open(os.path.join(os.path.dirname(__file__),'backupsrc.pyw'),'w').write(open(__file__).read())
self.open(eval(d[what]))
else:
self.flash_info('sorry, but i forgot what that file was', 1e4, 'edit_my_what')
def callback_email_faulkner(self, action):
''' opens a Dialog giving you my email address and instructions on how to use FlauxText to email me.
'''
d = self.mk_dialog('help!', (gtk.STOCK_OK,1))
d.vbox.pack_start(gtk.Label("copy this address to your clipboard,"
+ " then open a new document and compose an email to the author of FlauxText."
+ "\nthis email should include a complete description of your trouble with his software, "
+ "\nany pertinent output to the terminal from which you started FlauxText,"
+ "\nversion numbers of python, GTK, PyGTK, and your operating system OR lavish praise and thanksgiving."
+ "\n\nwhen you've written this email, hit ^m [Control+m], or WebTools > Email."
+ " paste the address below into the 'to' field."
))
e = gtk.Entry()
e.set_text("faulkner@users.sf.net")
d.vbox.pack_start(e)
d.run()
def callback_exec(self, action=None):
''' exec's the current buffer
'''
try:
execfile(execrc)
except:
pass
try:
StringIO
except:
from StringIO import StringIO
imp0rt('traceback')
_stdout_ = sys.stdout
sys.stdout = sys.__stdout__ = StringIO()
try: exec self.current_text() in globals(), locals()
except: self.new_buffer().set_text('\n'.join(traceback.format_exception(*sys.exc_info())))
ss = sys.stdout
sys.stdout = sys.__stdout__ = _stdout_
oput = ss.getvalue()
ss.close()
if oput != '':
self.new_buffer().set_text(re.sub('[^%s]' % txtchars, '', oput))
def callback_filemanager(self, action=None):
''' changes working directory to that of the current file, and
calls either termcmd or filemanagercmd, depending on action.get_name().
set termcmd and filemanagercmd in either the [nt] or [posix] section of config.flauxtext, depending on your OS.
'''
if self.current_filename() == UNTITLED_FILENAME:
dirnym = os.sep # open a terminal at the root
else:
dirnym = os.path.dirname(self.current_filename())
try:
os.chdir(dirnym)
except OSError:
pass
Popen(filemanagercmd % tuple([dirnym]*(len(filemanagercmd.split('%s'))-1)))
def callback_find(self, action=None):
''' show the tip of the iceberg of the find function. your input will be
passed to self.find, which will sort all the data out and call one
of 4 other methods, all of whose names begin with the word "find".
you can change the layout of the dialog by moving around lines in
FlauxText.find() [in the source]
'''
completion_list = gtk.ListStore(str)
for string in self.find_history:
completion_list.append([string])
completion = gtk.EntryCompletion()
completion.set_model(completion_list)
completion.set_text_column(0)
# set up the widgets. scroll down to see how the format is created
d = gtk.Dialog('we seek the holy grail!', self.w, gtk.DIALOG_NO_SEPARATOR | gtk.DIALOG_DESTROY_WITH_PARENT,
('_find next', FIND_FIND, '_hilite all', FIND_FIND_ALL, '_replace', FIND_REPLACE, 'replace _all', FIND_REPLACE_ALL))
d.connect('key-press-event', lambda wid,evt:[wid.destroy() for i in [0] if evt.keyval==ESC_KEYVAL])
findl = gtk.Label("find")
finde = gtk.Entry()
finde.set_completion(completion)
finde.connect('activate',lambda x:d.response(1))
findre = gtk.CheckButton("re_gexp")
repl = gtk.Label("replace with")
completion = gtk.EntryCompletion()
completion.set_model(completion_list)
completion.set_text_column(0)
repe = gtk.Entry()
repe.set_completion(completion)
repe.connect('activate',lambda x:d.response(3))
repre = gtk.CheckButton("rege_xp")
if findcombobox: # midnight hack; apologies
cmbodirbx = gtk.combo_box_new_text()
cmbodirbx.append_text('start here, go forward')
cmbodirbx.append_text('start here, go backward')
cmbodirbx.append_text('start at the beginning')
cmbodirbx.append_text('start at the end')
cmbodirbx.set_active(0)
else:
directio = gtk.CheckButton("_backwards")
findcase = gtk.CheckButton("re._I")
findcase.set_active(True) # ignore case
findmlin = gtk.CheckButton("re._M")
findmlin.set_active(True) # multilin--'^' matches starts of lines
finddtal = gtk.CheckButton("re._S")
finddtal.set_active(True) # dotall--'.' also matches newline
findloca = gtk.CheckButton("re._L")
findloca.set_active(True) # locale sensitive
onesht = gtk.CheckButton("_1")
onesht.set_active(findoneshot)
onesht.connect("toggled", lambda *a: globals().__setitem__('findoneshot', not findoneshot))
fst_cb = gtk.CheckButton("fs_t")
fst_cb.set_active(fstinit)
fst_tv = gtk.TextView()
fst_tb = fst_tv.get_buffer()
fst_tb.set_text(fstinittext)
fst_tv.set_property('no-show-all', not fstinit)
fst_cb.connect('toggled', lambda fst_cb_=fst_cb: [fst_tv.set_property('visible', fst_cb.get_active()), d.resize(1, 1)])
# FORMAT OF TEH FIND DIALOG
mhbox = gtk.HBox()
d.vbox.pack_start(mhbox) # the biggest box; only one in d.vbox
lvbox = gtk.VBox()
mhbox.pack_start(lvbox) # left vertical box in the biggest horizontal box
rvbx = gtk.VBox()
mhbox.pack_start(rvbx) # right vbox in the biggest hbox
fhbx = gtk.HBox()
lvbox.pack_start(fhbx) # hbox holding the Find label, text entry, and regexp checkbutton
rhbx = gtk.HBox()
lvbox.pack_start(rhbx) # hbox holding the Replace label, text entry, and regexp checkbutton
tinyhbx = gtk.HBox() # small hbox in the top of the right vbox holding 4 checkbuttons
ittyhbx = gtk.HBox()
fhbx.pack_start(findl)
fhbx.pack_start(finde)
fhbx.pack_start(findre) # put the find text entry and find by regexp checkbutton in fhbx
rhbx.pack_start(repl)
rhbx.pack_start(repe)
rhbx.pack_start(repre) # put replace '''" in rhbx
rvbx.pack_start(tinyhbx) # put the tinyhbx in the right vbox
rvbx.pack_start(ittyhbx)
ittyhbx.pack_start(onesht)
if findcombobox: ittyhbx.pack_start(cmbodirbx) # put combobox in the right vbox
else: ittyhbx.pack_start(directio)
ittyhbx.pack_start(fst_cb)
tinyhbx.pack_start(findcase) # put the re flags checkbuttons in the tinyhbx
tinyhbx.pack_start(findmlin)
tinyhbx.pack_start(finddtal)
tinyhbx.pack_start(findloca)
d.vbox.pack_start(fst_tv)
d.add_button('_done', 0).connect("clicked",lambda b: d.destroy())
def call_find(dialog, response_id):
if fst_cb.get_active():
fst_code = fst_tb.get_text(fst_tb.get_start_iter(), fst_tb.get_end_iter())
else:
fst_code = None
if findcombobox:
start_direction = cmbodirbx.get_active()
else:
start_direction = directio.get_active()
if response_id != 0:
self.find(
find_str=finde.get_text(), replace_str=repe.get_text(),
search_as_regexp=findre.get_active(), replace_as_regexp=repre.get_active(),
directive=response_id, fst_code=fst_code, start_direction=start_direction,
re_flags={
re.I : findcase.get_active(),
re.M : findmlin.get_active(),
re.S : finddtal.get_active(),
re.L : findloca.get_active(),
},
)
d.connect('response', call_find)
d.show_all()
d.run()
if eval('findoneshot'):
d.destroy()
def callback_find_again(self, action=None):
'''
'''
d = self.findagaininfo
d['start_direction'] = FIND_HERE_FORWARD
return self.find(**d)
def callback_find_previous(self, action=None):
'''
'''
d = self.findagaininfo
d['start_direction'] = FIND_HERE_BACKWARD
return self.find(**d)
def callback_font(self, action=None):
''' opens a font dialog, allowing you to set the font of the current or all buffers.
'''
d = self.mk_dialog('pick a font, any font', ('Apply', 1, 'Apply to All', 2, 'Cancel', 0))
f = gtk.FontSelection()
d.vbox.pack_start(f)
f.set_font_name(font)
r = d.run()
if r:
newfont = f.get_font_name()
if r is 1:
self.current_textview().modify_font(pango.FontDescription(newfont))
elif r is 2:
for i in range(self.notebk.get_n_pages()):
self.notebk.get_nth_page(i).get_child().modify_font(pango.FontDescription(newfont))
def callback_font_resize(self, *a):
''' adds ((the first argument that can be coerced to an int) or 120) to the current font's size
'''
a = list(a) + [120]
for i in a:
try:
dsize = int(a[0])
break
except:
pass
v = self.current_textview()
f = v.get_property('style').font_desc
f.set_size(f.get_size() + dsize)
v.modify_font(f)
def callback_get_output(self, action=None):
''' open a tiny dialog asking for a command to run, open a new buffer containing the output from that command
'''
d = self.mk_dialog('command me', (gtk.STOCK_OK,1,gtk.STOCK_CANCEL,0))
respond_dialog_on_escape(d)
e = gtk.Entry()
e.connect('activate',lambda x:d.response(1))
d.vbox.pack_start(e)
d.vbox.pack_start(gtk.Label("whatever you enter here will be run\nas if you'd run it from the command line."))
if d.run():
self.new_buffer().set_text(os.popen(e.get_text()).read())
def callback_goto_line(self, action=None):
''' opens a Dialog asking you for an int or "end", then calls getlinewcb.
'''
d = self.mk_dialog('line', (gtk.STOCK_OK,1,gtk.STOCK_CANCEL,0))
e = gtk.SpinButton(gtk.Adjustment(1, 0, self.current_buffer().get_line_count(), 1, 10), 1)
e.connect('activate', lambda x=None: d.response(1))
d.vbox.pack_start(e)
if d.run():
lnn = e.get_value_as_int()
tv = self.current_textview()
bfr = tv.get_buffer()
bfr.place_cursor(bfr.get_iter_at_line(lnn-1))
tv.scroll_to_iter(bfr.get_iter_at_line(lnn-1), 0.0, True, 0.0, 1.0)
def callback_goto_paren(self, action=None):
'''
'''
tv = self.current_textview()
b = tv.get_buffer()
i = b.get_iter_at_offset(self.find_matching_paren(b.get_iter_at_mark(b.get_insert()).get_offset()))
b.place_cursor(i)
tv.scroll_to_iter(i)
def callback_homepage(self, action):
''' opens __url__ in your webbrowser.
'''
Popen('%s"%s"' % (webbrowsercmd, __url__))
def callback_iconify(self, action=None):
'''
'''
self.w.iconify()
def callback_insert_file(self, action=None, *a):
''' opens a FileChooserDialog, and inserts at cursor the file you pick.
'''
txtbuf = self.current_buffer()
if isinstance(action, str) and os.path.exists(action):
txtbuf.insert_at_cursor(open(action, 'r').read())
self.flash_info("inserted " + filename, 4e3, 'inserted_file')
else:
dialog = gtk.FileChooserDialog("Open..",None,gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN, gtk.RESPONSE_OK))
dialog.set_default_response(gtk.RESPONSE_OK)
response = dialog.run()
filename = dialog.get_filename()
dialog.destroy()
if response == gtk.RESPONSE_OK:
txtbuf.insert_at_cursor(open(filename, 'r').read())
self.flash_info(None, "inserted " + filename)
def callback_insert_time_date(self, action=None):
''' inserts into the current buffer the current time and date
using the format string "timeformat" in the [DEFAULT] section of config.flauxtext.
'''
self.current_buffer().insert_at_cursor(
time.strftime(timeformat, time.localtime())
.replace('\3p0k', str(time.time()))
.replace('\3P0K', str(time.time() - time.timezone))
)
def callback_insert_unicode(self, *a):
''' open a new buffer, disconnect its signals, set it to wrap_char, and try to insert all unicode16 chars into it.
'''
tb = self.new_buffer()
tb.disconnect(tb.handler_ids['insert-text'])
tb.disconnect(tb.handler_ids['delete-range'])
tb.disconnect(tb.handler_ids['changed'])
self.current_textview().set_wrap_mode(gtk.WRAP_CHAR)
for i in xrange(1, 256 * 256):
try:
tb.insert_at_cursor(unichr(i))
except:
pass
def callback_macro(self, *args):
''' for all strings in args, [in order,] call the corresponding callback_ method [where the argument separator is ',' instead of '__'].
'''
namespace = locals()
for callback_name in args:
if isinstance(callback_name, (str, unicode)):
try:
parts = callback_name.split(',')
eval('self.callback_' + parts[0], namespace)(*(parts[1:] + [None]))
except Exception, e:
print e
def callback_make(self, name, action):
''' make a new buffer containing the value of action.get_name()[6:]+'_stub'
'''
self.new_buffer().set_text(eval(name + '_stub'))
def callback_man_page(self, action=None):
''' opens a Dialog asking you for the name of a man page, then opens it in either a new buffer or a terminal.
'''
d = self.mk_dialog('man page', (gtk.STOCK_OK,1,gtk.STOCK_CANCEL,0))
e = gtk.Entry()
d.vbox.pack_start(e)
e.connect('activate',lambda x: d.response(1))
e.set_text(self.lastmanpage)
c = gtk.CheckButton('open this man page in a terminal')
d.vbox.pack_start(c)
if d.run():
self.lastmanpage = e.get_text()
if c.get_active():
os.popen3(termcmd + "man " + self.lastmanpage)
else:
s = os.popen(
'man -P "'
+ pycmd
+ "-c 'from sys import stdin;print stdin.read()'\" "
+ self.lastmanpage
).read()
# use transducers to remove "bold" and "underlined" text, and non-ascii characters common in man pages
s = re.sub('\_\x08(?P.)', lambda m: m.groups()[0][0], s)
s = re.sub('(?P.)\x08(?P=a)', lambda m: m.groups()[0][0], s)
self.new_buffer().set_text(s.replace('\x08','').replace('\xb7',''))
def callback_maximize(self, action=None):
''' if self.w is maximized, calls self.w.unmaximize();
else vice-versa.
'''
if self.w.maximize_initially:
self.w.unmaximize()
else:
self.w.maximize()
def callback_mru(self, number, *a):
''' opens selected previous file
'''
self.open(self.histry[int(number)])
def callback_multiple_copy(self, action=None):
''' writes selected text to the first empty multiplecopypaste file.
'''
for i in range(10):
fname = os.path.join(tempdir, 'multiplecopypaste%d.flauxtext' % i)
if not (os.path.exists(fname) and len(open(fname).read())>0):
break
bufr = self.current_buffer()
try:
start, end = bufr.get_selection_bounds()
except:
start, end = bufr.get_start_iter(), bufr.get_end_iter()
open(fname, 'w').write(bufr.get_text(start, end))
self.flash_info('wrote current/all text to '+fname, 4e3, 'copyn')
def callback_multiple_paste(self, num, action):
''' inserts at cursor the contents of the multiplecopypaste file associated with action.get_name().
'''
filename = os.path.join(tempdir, 'multiplecopypaste%s.flauxtext' % num)
if istextfile(filename):
self.current_buffer().insert_at_cursor(open(filename).read())
def callback_new(self, action=None):
''' alias of new_buffer
'''
self.new_buffer(action)
self.w.set_title(windowtitle % self.current_filename())
def callback_next_n_tab(self, action=None):
'''
'''
if ntabn > self.notebk.get_n_pages() - self.notebk.get_current_page():
self.notebk.set_current_page(ntabn % self.notebk.get_n_pages())
else:
self.notebk.set_current_page(self.notebk.get_current_page() + ntabn)
try:
self.actiongroup.get_action('toggle_editable').set_active(self.current_textview().get_editable()) # update the "Editable" toggleaction
except:
pass
self.update_window_title()
def callback_next_tab(self, action=None):
'''
'''
if self.notebk.get_current_page() end.get_offset():
st, end = end, st
fn = bufr.get_text(st, end)
if os.path.isfile(fn):
self.open(fn)
else:
self.flash_info('please select a path', 3e3, 'bad_path_selected')
def callback_open_url(self, action):
''' passes the selected url to your webbrowser.
'''
bufr = self.current_buffer()
start, end = bufr.get_selection_bounds()
if start.get_offset() < end.get_offset():
Popen(webbrowsercmd + bufr.get_text(start, end))
elif start.get_offset() > end.get_offset():
Popen(webbrowsercmd + bufr.get_text(end, start))
else:
self.flash_info('please select a url and try again.', 1e4, 'no_url_selected')
def callback_open_url_source(self, action):
''' callback for Tools > Open Selected URL Source, calls openurlsrc.
'''
imp0rt('urllib2')
bufr = self.current_buffer()
start, end = bufr.get_selection_bounds()
if start.get_offset() < end.get_offset():
self.new_buffer().set_text(urllib2.urlopen(bufr.get_text(start, end)).read())
elif start.get_offset() > end.get_offset():
self.new_buffer().set_text(urllib2.urlopen(bufr.get_text(end, start)).read())
else:
self.flash_info('please select a url and try again.', 1e4, 'no_url_selected')
def callback_permissions(self, action):
''' opens a dialog asking for the permissions you want to set to the current file.
'''
d = self.mk_dialog('permissions', (gtk.STOCK_OK, 1, gtk.STOCK_CANCEL, 0))
e = gtk.Entry()
d.vbox.pack_start(e)
d.vbox.pack_start(gtk.Label('when you click "OK", this will run\n'
+'os.popen3("chmod "+text you enter above+" "+current filename).'))
s = gtk.CheckButton("i don't own this file, and you need my root password in order to chmod it.")
d.vbox.pack_start(s)
if d.run():
if s.get_active(): runthis=termcmd+'sudo '+term+'chmod '+e.get_text()+' '+self.current_filename()
else: runthis=termcmd+'chmod '+e.get_text()+' '+self.current_filename()
Popen(runthis)
def callback_preview_mcp(self, action=None):
''' opens a dialog containing textbuffers containing the contents of my multiplecopypaste files.
'''
d = self.mk_dialog('what are YOU doing?', (gtk.STOCK_OK, 1), response_on_esc=1)
d.connect('key-press-event', lambda x=None, y=None, z=None: d.resize(10,10))
saveme = gtk.ToggleButton('Save each buffer when changed')
saveme.set_active(True)
d.action_area.pack_start(saveme)
t = gtk.Table(10,3)
d.vbox.pack_start(t)
t.set_col_spacings(3);t.set_row_spacings(3)
vs = gtk.VSeparator()
t.attach(vs,1,2,0,10)
def savethisnow(b, fn, s):
if s.get_active():
f = open(fn, 'w')
f.write(b.get_text(b.get_start_iter(), b.get_end_iter()))
f.close()
for i in range(10):
fname = os.path.join(tempdir,'multiplecopypaste%d.flauxtext'%i)
t.attach(gtk.Label(fname), 0, 1, i, i+1)
if os.path.exists(fname):
a = open(fname).read()
else:
a = '[file does not yet exist.]'
tv = gtk.TextView()
t.attach(tv, 2, 3, i, i + 1)
tv.get_buffer().set_text(a)
tv.get_buffer().connect('changed', savethisnow, fname, saveme)
d.run()
def callback_previous_n_tab(self, action=None):
'''
'''
if ntabn > self.notebk.get_current_page():
self.notebk.set_current_page(self.notebk.get_n_pages() - ntabn + self.notebk.get_current_page())
else:
self.notebk.set_current_page(self.notebk.get_current_page() - ntabn)
try:
self.actiongroup.get_action('toggle_editable').set_active(self.current_textview().get_editable()) # update the "Editable" toggleaction
except:
pass
self.update_window_title()
def callback_previous_tab(self, action=None):
'''
'''
self.notebk.set_current_page(self.notebk.get_current_page()-1)
try:
self.actiongroup.get_action('toggle_editable').set_active(self.current_textview().get_editable()) # update the "Editable" toggleaction
except:
pass
self.update_window_title()
def callback_print(self, action=None):
''' print to a hardcopy
on NT, this is basically open(printer_PATH, 'w').write(self.current_text())
on POSIX, this is os.popen('lpr','w').write(self.current_text())
'''
d = self.mk_dialog('print to where?', (gtk.STOCK_OK, 1, gtk.STOCK_CANCEL, 0))
d.vbox.pack_start(gtk.Label((os.name=='nt') and
'print to what printer?\n[start typing an entry in printer in config.flauxtext]' or
'pipe to what command?'))
e = gtk.Entry()
d.vbox.pack_start(e)
if isinstance(printer, list):
ec = gtk.EntryCompletion()
ec.set_text_column(0)
ecl = gtk.ListStore(str)
for i in printer:
ecl.append([i])
try:
imp0rt('win32print')
for i in win32print.EnumPrinters(WIN32_PRINTERS_LEVEL):
ecl.append([i[2]])
except:
pass
ec.set_model(ecl)
e.set_completion(ec)
elif isinstance(printer, str):
e.set_text(printer)
e.connect('activate', lambda x=None,y=None: d.response(1))
if d.run():
try:
if os.name=='nt':
open(e.get_text(), 'w').write(self.current_text())
else:
os.popen(e.get_text(), 'w').write(self.current_text())
self.flash_info('go check the printer ;-)', 3e3, 'done_printing')
except Exception, e:
self.flash_info('ERROR PRINTING: '+reduce(lambda x,y: str(x)+str(y), e.args), 5e3, 'cannot_print')
def callback_plugin(self, *a):
''' alias for self.run_plugin.
'''
return self.run_plugin(*a)
def callback_pygtkref(self, action):
''' opens the pygtk reference in your webbrowser
'''
Popen(webbrowsercmd + pygtkrefurl)
def callback_python_documentation(self, action):
''' opens a Dialog asking you for the name of a python module,
then opens that module's help documentation in either a new buffer or a terminal.
'''
d = self.mk_dialog('help(me)', (gtk.STOCK_OK,1,gtk.STOCK_CANCEL,0))
e = gtk.Entry()
d.vbox.pack_start(e)
e.connect('activate',lambda x: d.response(1))
e.set_text(self.lasthelppy)
c=gtk.CheckButton('open this documentation in a terminal')
d.vbox.pack_start(c)
if d.run():
self.lasthelppy=e.get_text()
if c.get_active():
runthis=termcmd+pycmd+' -c "exec \\"import %s\\nhelp(%s)\\""'%tuple([self.lasthelppy]*2)
Popen(runthis)
else:
t=os.popen(pycmd + ' -c "help(\'%s\')"' % self.lasthelppy).read()
if "Help on" in t:t=t[t.index("Help on"):]
buf=self.new_buffer()
buf.set_text(t)
self.current_textview().scroll_to_iter(buf.get_start_iter(),0.0)
buf.place_cursor(buf.get_start_iter())
self.notebk.set_tab_label_text(self.notebk.get_nth_page(self.notebk.get_current_page()), self.lasthelppy)
def callback_quit(self, action=None):
''' when you hit ^q, if your config allows, i will ask you if you want to save any modified open files.
'''
if checkforunsavedbeforequitting:
numlen = self.notebk.get_n_pages()
for i in range(self.notebk.get_n_pages()):
j = numlen - 1 - i # work backwards from the end
jthbfr = self[j]
jthtabtxt = self.notebk.get_tab_label_text(self.notebk.get_nth_page(j))
if ((self[j].filename == UNTITLED_FILENAME) and
len(jthbfr.get_all_text()) > 0
) or (jthtabtxt[0] == jthtabtxt[-1] == '*'):
d = self.mk_dialog("save buffer before closing?", (gtk.STOCK_YES,1,gtk.STOCK_NO,2,gtk.STOCK_CANCEL,3))
response = d.run()
if response == 3:
return False
else:
if response == 1:
self.notebk.set_current_page(i)
self.save()
curpagen = self.notebk.get_current_page()
self.notebk.remove_page(curpagen)
else:
curpagen = self.notebk.get_current_page()
self.notebk.remove_page(curpagen)
if self.notebk.get_n_pages() < 1:
gtk.main_quit()
else:
gtk.main_quit()
def callback_redo(self, action=None):
'''
'''
buffer = self.current_buffer()
pagen = self.notebk.get_current_page()
curundostack = buffer.undostack
curundoentry = curundostack.next()
if len(curundostack) >= curundostack.pointer and isinstance(curundoentry, UndoStackEntry):
buffer.disconnect(buffer.handler_ids['insert-text'])
buffer.disconnect(buffer.handler_ids['delete-range'])
buffer.disconnect(buffer.handler_ids['changed'])
if curundoentry.was_inserted:
buffer.insert(buffer.get_iter_at_offset(curundoentry.offset), curundoentry.data)
else:
buffer.delete(buffer.get_iter_at_offset(curundoentry.offset), buffer.get_iter_at_offset(curundoentry.offset + len(curundoentry.data)))
buffer.handler_ids['insert-text'] = buffer.connect('insert-text', self.registerwithundo)
buffer.handler_ids['delete-range'] = buffer.connect('delete-range', self.registerwithundo)
buffer.handler_ids['changed'] = buffer.connect('changed', self.autoindent)
curundostack.pointer += 1
buffer.place_cursor(buffer.get_iter_at_offset(curundoentry.offset))
chil = self.notebk.get_nth_page(pagen)
tabtxt = self.notebk.get_tab_label_text(chil)
if (tabtxt[0] != '*') and (tabtxt[-1] != '*') and (self.current_filename() != UNTITLED_FILENAME): # update the tabtext
if self.autosave:
self.save()
else:
self.notebk.set_tab_label_text(chil, '** ' + tabtxt + ' **')
self.w.set_title((windowtitle % self.current_filename()) + windowtitlemodified)
self.statbar_info.set('')
else:
self.flash_info('nothing to redo O_o', 5e3, 'cannot_redo')
def callback_reload(self, action=None):
''' closes current buffer, reopens it.
'''
n = self.notebk.get_current_page()
fn = self[n].filename
if self.notebk.get_n_pages() == 1:
self.new_buffer()
self.close_buffer(None, n)
self.open(fn)
def callback_reposition(self, action=None):
'''
'''
self.w.move(*winderpos[:2])
def callback_resize(self, action=None):
'''
'''
self.w.resize(*windersize[:2])
def callback_run(self, action=None):
''' handles all F5 menuitems
fnmatch()es current filename versus keys in runcmds [in config.flauxtext],
runs the associated value in runcmds.
opens a dialog for arguments if "wargs" in action.get_name().
opens output in a new buffer if "GetOutput" in action.get_name()
if the appropriate runcmds entry is iterable, all but the last are executed sequentially,
then the last is passed through the 'wargs' and 'GetOutput' filters [and/or passed to Popen]
'''
runthis = self.run_what(self.current_filename())
if runthis:
self.flash_info(runthis, 2e3, 'running')
Popen(runthis)
def callback_run_args(self, action=None):
'''
'''
runthis = self.run_what(self.current_filename())
if runthis:
d = self.mk_dialog('args', (gtk.STOCK_OK, 1, gtk.STOCK_CANCEL, 0))
d.vbox.pack_start(gtk.Label("i'll run\n'"+runthis+"' what you enter here"))
e = gtk.Entry()
e.connect('activate',lambda x:d.response(1))
d.vbox.pack_start(e)
if d.run():
runthis += ' ' + e.get_text()
else:
return
self.flash_info(runthis, 2e3, 'running')
Popen(runthis)
def callback_run_get_output(self, action=None):
'''
'''
runthis = self.run_what(self.current_filename())
if runthis:
self.flash_info(runthis, 2e3, 'running')
fs = os.popen3(runthis)
put = ''
out = fs[1].read()
err = fs[2].read()
if err != '':
put += 'STDERR:\n' + err
if out != '':
put += '\n\nSTDOUT:\n' + out
self.new_buffer().set_text(put)
def callback_run_args_get_output(self, action=None):
'''
'''
runthis = self.run_what(self.current_filename())
if runthis:
d = self.mk_dialog('args', (gtk.STOCK_OK, 1, gtk.STOCK_CANCEL, 0))
d.vbox.pack_start(gtk.Label("i'll run\n'"+runthis+"' what you enter here"))
e = gtk.Entry()
e.connect('activate',lambda x:d.response(1))
d.vbox.pack_start(e)
if d.run():
runthis += ' ' + e.get_text()
else:
return
self.flash_info(runthis, 2e3, 'running')
fs = os.popen3(runthis)
put = ''
out = fs[1].read()
err = fs[2].read()
if err != '':
put += 'STDERR:\n' + err
if out != '':
put += '\n\nSTDOUT:\n' + out
self.new_buffer().set_text(put)
def callback_save(self, action=None):
''' alias of save
'''
return self.save()
def callback_saveas(self, action=None):
''' prompt for a filename for the current buffer
'''
fn = self.current_filename()
d = gtk.FileSelection("Save As...")
respond_dialog_on_escape(d)
d.set_filename([fn, os.path.abspath(defaultsavepath)][fn == UNTITLED_FILENAME])
d.ok_button.connect('clicked', lambda w: (
not os.path.isdir(d.get_filename()) and (d.response(1) or True)
or self.flash_info('you must choose a filename', 3e3, 'bad_save_filename')))
d.cancel_button.connect('clicked', lambda w: d.response(0))
if d.run():
fn = d.get_filename()
self.w.set_title(windowtitle % fn)
bfr = self.current_buffer()
bfr.filename = fn
self.save_file(fn, bfr.get_all_text())
try:
self.manage_mru(fn, True)
except IndexError:
pass
d.destroy()
def callback_search(self, action='Google'):
''' searches for selected text in the chosen search engine [whose name is either action or action.get_name()]
using the webbrowsercmd set in [os.name] in my configfile.
'''
txtb = self.current_buffer()
selectedtext = ''
try:
starts, ends = txtb.get_selection_bounds()
anym = (isinstance(action, str) and action != '') and action or action.get_name()
selectedtext = txtb.get_text(starts, ends).replace(' ','+')
if anym not in SEARCH_ENGINES.keys():
self.flash_info('i forgot how to use ' + anym + ', so i just used google. :-/', 3e3, 'bad_search_engine')
anym = 'Google'
runthis = webbrowsercmd + SEARCH_ENGINES[anym] + '=' + selectedtext + '"'
self.flash_info(runthis, 2e3, 'running')
Popen(runthis)
except ValueError:
self.flash_info('select something first.', 3e3, 'bad_search_terms')
def callback_special_file(self, number, evt):
''' opens the special file corresponding to number
'''
self.open(self.specialfilelist[int(number)])
def callback_split_horizontally(self, action=None):
'''
'''
self.split(False).add2(FlauxText(self.all_files(), w=self.w).vbox)
def callback_split_vertically(self, action=None):
'''
'''
self.split().add2(FlauxText(self.all_files(), w=self.w).vbox)
def callback_statistics(self, action=None):
''' open a dialog containing only a gtk.Table which in turn contains
statistics such as the number of chars, lines, and words in the current
buffer, and the numbers of times the most common words and chars appear.
set the number of common words and chars i show you in my configfile at the
variable "statlen" under "DEFAULT".
'''
buff = self.current_buffer()
if len(buff.get_selection_bounds()) > 1: # if user has selected something, look only at that
(start,end)=buff.get_selection_bounds()
text=buff.get_text(start,end)
else: # otherwise, look at everything
text = buff.get_all_text()
char_dicto = {}
char_count = len(text)
for i in string.printable:
char_dicto[i] = len(text.split(i)) - 1
word_dicto = {}
words = re.split('\W*',text)
for i in range(words.count('')):
words.remove('')
word_count = len(words)
for word in words:
#word_dicto[word] = word_dicto.get(word, 0) + 1 # actually slower than try...except
try:
word_dicto[word] += 1
except:
word_dicto[word] = 1
statable = gtk.Table(7, 17)
statable.set_col_spacings(3)
#0-----------1----2------3----4----5---6----7---8----9----0------1---2----3----4----5--------6----7---8
#| lines | vs | n | vs | vs | ( | vs | n | vs | vs | word | vs | n | vs | vs | letter | vs | n |
#1-----------|----|------|----|----|---|----|---|----|----|------|----|---|----|----|--------|----|---|
#| chars | vs | n | vs | vs | ) | vs | n | vs | vs | word | vs | n | vs | vs | letter | vs | n |
#2-----------|----|------|----|----|---|----|---|----|----|------|----|---|----|----|--------|----|---|
#| words | vs | n | vs | vs | { | vs | n | vs | vs | word | vs | n | vs | vs | letter | vs | n |
#3-----------|----|------|----|----|---|----|---|----|----|------|----|---|----|----|--------|----|---|
#| ' | vs | n | vs | vs | } | vs | n | vs | vs | word | vs | n | vs | vs | letter | vs | n |
#4-----------|----|------|----|----|---|----|---|----|----|------|----|---|----|----|--------|----|---|
#| " | vs | n | vs | vs | [ | vs | n | vs | vs | word | vs | n | vs | vs | letter | vs | n |
#5-----------|----|------|----|----|---|----|---|----|----|------|----|---|----|----|--------|----|---|
#| <\w*> | vs | n | vs | vs | ] | vs | n | vs | vs | word | vs | n | vs | vs | letter | vs | n |
#6-----------|----|------|----|----|---|----|---|----|----|------|----|---|----|----|--------|----|---|
#| \w* > | vs | n | vs | vs | < | vs | n | vs | vs | word | vs | n | vs | vs | letter | vs | n |
#7-----------|----|------|----|----|---|----|---|----|----|------|----|---|----|----|--------|----|---|
#| = | vs | n | vs | vs | > | vs | n | vs | vs | word | vs | n | vs | vs | letter | vs | n |
#8-----------|----|------|----|----|---|----|---|----|----|------|----|---|----|----|--------|----|---|
#statable.attach(widget, left, right, top, bottom, xoptions=gtk.EXPAND|gtk.FILL,
# yoptions=gtk.EXPAND|gtk.FILL, xpadding=0, ypadding=0)
lrs = [(1, 2), (3, 4), (4, 5), (6, 7), (8, 9), (9, 10), (11, 12), (13, 14), (14, 15), (16, 17)]
for i in lrs:
vs = gtk.VSeparator()
statable.attach(vs, i[0], i[1], 0, 8)
html = '[\w=\?/:"\'\. ]+'
ls = ['chars','words','\\r,\\n',"'",'"','<'+html+'>',''+html+'>','=']
for i in range(len(ls)):
statable.attach(gtk.Label(ls[i]), 0, 1, i, i + 1)
ls = [str(char_count), str(word_count), str(len(text.split('\r'))-1)+","+str(len(text.split('\n'))-1),
str(len(text.split("'"))-1), str(len(text.split('"'))-1),
str(len(re.compile('<'+html+'>').findall(text))),
str(len(re.compile(''+html+'>').findall(text))),
str(len(re.compile('=').findall(text)))]
for i in range(len(ls)):
statable.attach(gtk.Label(ls[i]), 2, 3, i, i + 1)
ls = list('(){}[]<>')
for i in range(len(ls)):
statable.attach(gtk.Label(ls[i]), 5, 6, i, i + 1)
statable.attach(gtk.Label(str(len(text.split(ls[i]))-1)), 7, 8, i, i + 1)
wdi = word_dicto.items()
wdi.sort(key=lambda x: x[1])
wdi.reverse()
for i in range(len(wdi)):
if i > statlen:
break
else:
statable.attach(gtk.Label(wdi[i][0]), 10, 11, i, i + 1)
statable.attach(gtk.Label(wdi[i][1]), 12, 13, i, i + 1)
cdi = char_dicto.items()
cdi.sort(key=lambda x: x[1])
cdi.reverse()
for i in range(len(cdi)):
if i > statlen:
break
else:
statable.attach(gtk.Label(cdi[i][0].replace(' ','\\s').replace('\r','\\r').replace('\n','\\n')), 15, 16, i, i + 1)
statable.attach(gtk.Label(cdi[i][1]), 17, 18, i, i + 1)
def statsecb(entry, n, r, txt):
s = entry.get_text()
try: n.set_text(str(len(re.split(r.get_active() and s or re.escape(s), txt))-1))
except: pass
d = self.mk_dialog(self.current_filename(), ())
d.vbox.pack_start(statable)
e = gtk.Entry()
n = gtk.Entry()
rex = gtk.CheckButton('re')
n.set_property('editable',False)
n.set_property("width-request", 20)
e.set_property("width-request", 20)
rex.set_property("width-request", 10)
e.connect("changed", statsecb, n, rex,text)
d.action_area.set_property('homogeneous',False)
d.action_area.pack_start(rex, False, False)
d.action_area.pack_start(e, False, False)
d.action_area.pack_start(n, False, False)
dun = gtk.Button('_Done', gtk.STOCK_OK)
dun.connect('clicked', lambda x: d.response(0))
d.action_area.pack_start(dun, False, False)
e.grab_focus()
d.run()
def callback_swap_crlf_lf(self, action=None):
''' if there is at least one '\\r' [carraige-return] in the current file, this eliminates all carriage-returns.
if not, this exchanges all '\\n' [newlines] with '\\r\\n' [carriage-return line-feed].
'''
bfr = self.current_buffer()
txt = bfr.get_all_text()
reptxt = ''
if len(txt.split('\r\n'))>1:
reptxt = '\n'.join(txt.split('\r\n'))
self.flash_info("removed all \\r\\n from current buffer", 2e3, 'fixed_crlf')
else:
reptxt = '\r\n'.join(txt.split('\n'))
self.flash_info("replaced all \\n in current buffer with \\r\\n", 2e3, 'fixed_crlf')
bfr.set_text(reptxt)
def callback_terminal(self, action=None):
''' changes working directory to that of the current file, and
calls either termcmd or filemanagercmd, depending on action.get_name().
set termcmd and filemanagercmd in either the [nt] or [posix] section of config.flauxtext, depending on your OS.
'''
if self.current_filename() == UNTITLED_FILENAME:
dirnym = os.sep # open a terminal at the root
else:
dirnym = os.path.dirname(self.current_filename())
try:
os.chdir(dirnym)
except OSError:
pass
Popen(' '.join(termcmd.split(' ')[:-2]))
def callback_toggle_autoindentation(self, action):
self.autoident = not self.autoident
def callback_toggle_autosave(self, action=None):
self.autosave = not self.autosave
def callback_toggle_editable(self, tglaction=None):
''' toggle the "editable" status of the current textview.
'''
txtvw = self.current_textview()
if tglaction is None:
bul = not txtvw.get_editable()
try:
self.actiongroup.get_action('toggle_editable').set_active(bul)
except AttributeError:
pass
else:
bul = tglaction.get_active()
txtvw.set_editable(bul)
def callback_toggle_highlight_parens(self, action=None):
self.findparens = not self.findparens
def callback_toggle_keep_above(self, action=None):
self.w.set_keep_above(action.get_active())
def callback_toggle_linenums(self, action=None):
''' toggles line-number border-window size from 0 to 30 pixels.
'''
txtview = self.current_textview()
if linenumwindowsize in map(txtview.get_border_window_size, [gtk.TEXT_WINDOW_LEFT, gtk.TEXT_WINDOW_RIGHT]):
for parameter in [gtk.TEXT_WINDOW_LEFT, gtk.TEXT_WINDOW_RIGHT]:
if txtview.get_border_window_size(param) == linenumwindowsize:
txtview.set_border_window_size(param, 0)
else:
if linenums in ['Left', 'Both']:
txtview.set_border_window_size(gtk.TEXT_WINDOW_LEFT, linenumwindowsize)
if linenums in ['Right', 'Both']:
txtview.set_border_window_size(gtk.TEXT_WINDOW_RIGHT, linenumwindowsize)
def callback_toggle_right_edge(self, action=None):
global rightedge
rightedge = not rightedge
def callback_toggle_statusbar(self, action=None):
''' toggle visible property of self.statbarhbox.
'''
self.statbarhbox.set_property("visible", not self.statbarhbox.get_property("visible"))
def callback_toggle_tabbar(self,action):
''' toggles visible status of the tabs on self.notebk.
'''
self.notebk.set_show_tabs(not self.notebk.get_show_tabs())
def callback_toggle_tearoff_menus(self, action=None):
''' turn on/off menu tearing off
'''
self.uiman.set_add_tearoffs(not self.uiman.get_add_tearoffs())
def callback_undo(self, action=None):
'''
'''
buffer = self.current_buffer()
pagen = self.notebk.get_current_page()
curundostack = buffer.undostack
curundoentry = curundostack.current()
if len(curundostack) >= curundostack.pointer and isinstance(curundoentry, UndoStackEntry):
buffer.disconnect(buffer.handler_ids['insert-text'])
buffer.disconnect(buffer.handler_ids['delete-range'])
buffer.disconnect(buffer.handler_ids['changed'])
if curundoentry.was_inserted:
buffer.delete(buffer.get_iter_at_offset(curundoentry.offset), buffer.get_iter_at_offset(curundoentry.offset + len(curundoentry.data)))
else:
buffer.insert(buffer.get_iter_at_offset(curundoentry.offset), curundoentry.data)
buffer.handler_ids['insert-text'] = buffer.connect('insert-text', self.registerwithundo)
buffer.handler_ids['delete-range'] = buffer.connect('delete-range', self.registerwithundo)
buffer.handler_ids['changed'] = buffer.connect('changed', self.autoindent)
curundostack.pointer -= 1
buffer.place_cursor(buffer.get_iter_at_offset(curundoentry.offset))
chil = self.notebk.get_nth_page(pagen)
tabtxt = self.notebk.get_tab_label_text(chil)
if (tabtxt[0] != '*') and (tabtxt[-1] != '*') and (self.current_filename() != UNTITLED_FILENAME): # update the tabtext
if self.autosave:
self.save()
else:
self.notebk.set_tab_label_text(chil, '** ' + tabtxt + ' **')
self.w.set_title((windowtitle % self.current_filename()) + windowtitlemodified)
self.statbar_info.set('')
else:
self.flash_info('nothing to undo O_o', 5e3, 'cannot_undo')
def callback_view_undo_stack(self, action=None):
''' opens a dialog showing you the contents of my undostack.
'''
curundostack = self.current_undostack()
d = self.mk_dialog("undostack", (gtk.STOCK_OK, 1))
d.vbox.set_property('homogeneous', False)
d.vbox.pack_start(gtk.Label(self.current_filename() + '\n'), expand=False, fill=False)
sw = gtk.ScrolledWindow()
d.vbox.pack_start(sw) # expand with the window
sw.set_size_request(400, 200)
t = gtk.Table(len(curundostack), 6)
t.set_col_spacings(3)
sw.add_with_viewport(t)
for i in [1,3,5]:
t.attach(gtk.VSeparator(), i, i+1, 0, len(curundostack)+1)
t.attach(gtk.Label("char_offset"), 0, 1, 0, 1, xoptions=False, yoptions=False)
t.attach(gtk.Label("inserted?"), 2, 3, 0, 1, xoptions=False, yoptions=False)
t.attach(gtk.Label("text"), 4, 5, 0, 1, xoptions=False, yoptions=False)
t.attach(gtk.Label("index in stack"), 6, 7, 0, 1, xoptions=False, yoptions=False)
for i in range(1, len(curundostack)):
for j, x in enumerate([curundostack[i].offset, curundostack[i].was_inserted, curundostack[i].data]):
t.attach(gtk.Label(repr(x)), 2 * j, 2 * j + 1, 1 + i, 2 + i)
t.attach(gtk.Label(str(i)), 6, 7, 1 + i, 2 + i)
d.vbox.pack_start(gtk.Label('current index: ' + str(curundostack.pointer)), expand=False, fill=False)
sw.grab_focus()
d.run()
def callback_word_wrap(self, action=None):
''' rotates word wrap according to the list of constants in wordwraps set in config.flauxtext
'''
txtview = self.current_textview()
txtview.set_wrap_mode(wordwraps[(wordwraps.index(txtview.get_wrap_mode()) + 1) % len(wordwraps)])
def capslock_statbar_switch(self, widgt, event):
''' called whenever you press a key in one of my textview widgets,
updates self.statbar_capslock
'''
value = ['a', 'A'][bool(event.state & gtk.gdk.LOCK_MASK)]
if event.keyval == CAPS_LOCK_KEYVAL:
value = ['a', 'A'][self.statbar_capslock.value == 'a']
if value != self.statbar_capslock.value:
self.statbar_capslock.pop(1)
self.statbar_capslock.push(1, value)
self.statbar_capslock.value = value
def close_buffer(self, action=None, which=None):
''' if which file is unsaved, i'll offer to save it then close it; otherwise, i'll just close it.
if which is None, the current buffer.
'''
if which is None:
which = self.notebk.get_current_page()
# ask to save the buffer if it has been modified since last save, or if it is untitled and contains text.
bfr = self[which]
tabtext = self.notebk.get_tab_label_text(self.notebk.get_nth_page(which))
if ((self[which].filename == UNTITLED_FILENAME) and (len(bfr.get_all_text()) > 0)
) or (tabtext[0] == tabtext[-1]=='*'):
d = self.mk_dialog("save before closing?", (gtk.STOCK_YES, 1, gtk.STOCK_NO, 2, gtk.STOCK_CANCEL, 3))
# 1=yes, save buffer
# 2=no, don't save
# 3=don't close the buffer
response = d.run()
if response != 3:
if response == 1:
self.save()
self.notebk.remove_page(which)
else: # current buffer has been saved
self.notebk.remove_page(which)
if self.notebk.get_n_pages() == 0:
if newbufferoncloselast:
self.new_buffer()
else:
gtk.main_quit()
elif tabshideifone and (self.notebk.get_n_pages() == 1):
self.notebk.set_show_tabs(False)
try:
self.w.set_title(windowtitle % self.current_filename())
except:
# we might not have a current_filename
pass
return 0
def connect_menuitem_selections_statbar_info_recurse(self, p='', d={}):
''' a recursive method which takes a parent [str] and a dict describing children.
the parent is interpreted as a UIManager path, and the dict maps path elements to child path elements.
example usage:
self.connect_menuitem_selections_statbar_info_recurse('/MenuBar', {'/File':{'/New':''}})
of course, the dict used by FlauxText is dynamically generated by an XML parser, and is a good deal more involved.
'''
make_info_setter = lambda s: lambda *a: self.statbar_info.set(s) # closures are so metal.
for k in d.keys():
if isinstance(d[k], dict):
self.connect_menuitem_selections_statbar_info_recurse(p + k, d[k])
else:
#elif d[k] == '':
a = self.uiman.get_action(p + k)
if a == None:
continue
ttt = a.get_property("tooltip")
if not ttt:
ttt = " "
for w in a.get_proxies():
try:
w.connect("select", make_info_setter(ttt))
w.connect("deselect", make_info_setter(''))
except TypeError: # toolbar items don't have that signal, and don't need to be connected to it
pass
def current_buffer(self):
''' a one-liner used to simplify nearly every one of my other callbacks.
return the current buffer.
'''
return self.current_textview().get_buffer()
def current_filename(self):
''' a one-liner used to simplify most of my other callbacks.
return the current filename.
'''
return self[self.notebk.get_current_page()].filename
def current_scrolledwindow(self):
''' a one-liner used to simplify most of my callbacks.
return the current textview.
'''
return self.notebk.get_nth_page(self.notebk.get_current_page())
def current_selection_bounds_or_all(self, text=False, raise_if_no_selection=False):
''' find the pair of TextIters specifying the currently selected text.
if not raise_if_no_selection: default those iters to b.get_start_iter() and b.get_end_iter()
it text: return that text
else: return those iters
'''
b = self.current_buffer()
if raise_if_no_selection:
start, end = b.get_selection_bounds()
if start.get_offset() > end.get_offset():
start, end = end, start
else:
try:
start, end = b.get_selection_bounds()
if start.get_offset() > end.get_offset():
start, end = end, start
except ValueError:
start = b.get_start_iter()
end = b.get_end_iter()
if text:
return b.get_text(start, end)
else:
return start, end
def current_text(self):
''' return the contents of the current buffer
'''
return self.current_buffer().get_all_text()
def current_textview(self):
''' a one-liner used to simplify most of my other callbacks.
return the current textview.
'''
return self.current_scrolledwindow().get_child()
def current_undo_entry(self):
''' returns the current 3list in the undostack; used in some of my undo methods.
'''
return self.current_undostack().current()
def current_undostack(self):
''' returns the int-and-tuples list associated with the current page in the notebk
'''
return self.current_buffer().undostack
def cycle_tabs__(self, wid, evt):
''' optional feature [usectrltabs] allows you to use ctrl+tab to switch forwards
through tabs, ctrl+shift+tab to switch backwards.
[UIManager can't override gtk's silly default "ctrl+tab rotates
through each individual widget", so i have to do it by hand]
'''
if evt.state & gtk.gdk.CONTROL_MASK:
if evt.keyval == TAB_KEYVAL:
self.callback_next_tab()
elif evt.keyval == SHIFT_TAB_KEYVAL:
self.callback_previous_tab()
elif evt.keyval == 99:
# horrible hack to emulate copy
# 99 == ord('c')
tb = self.current_buffer()
start, end = tb.get_selection_bounds()
if start.get_offset() > end.get_offset():
start, end = end, start
text = tb.get_text(start, end)
self.clipboard.set_text(text)
def dnd_get_data(self, txtv, context, selection, info, timestamp):
''' do nothing
'''
pass
def dnd_received_data(self, txtv, context, x, y, selection, info, timestamp):
''' connected to the 'drag-data-received' signal of each textview
txtv is one of my gtk.TextView()s--the one on which you dropped dragged data
context is a gtk.gdk.DragContext; its targets attribute is a list of URIs
selection is a gtk.SelectionData describing what you dropped
if you dragged files on me from a file manager like explorer.exe,
its data attribute is a '\r\n' separated list of URL-encoded filenames
info is almost always 0 [zero]
if you drag multiple files or a directory onto me,
dragdropopentextonly in config.flauxtext determines whether or not i try to open binary files
'''
imp0rt('urllib.unquote')
if context.targets[0] == 'text/uri-list':
L = [urllib.unquote(fn).replace('file:///','') for fn in selection.data.split(os.linesep)]
if '\x00' in L:
L.remove('\x00')
for fn in L:
if (dragdropopentextonly and istextfile(fn)) or (not dragdropopentextonly and os.path.isfile(fn)):
self.open(fn)
elif os.path.isdir(fn):
for f in os.listdir(fn):
f = os.path.join(fn, f)
if (dragdropopentextonly and istextfile(f)) or (not dragdropopentextonly and os.path.isfile(f)):
self.open(f)
def find(self, find_str='', replace_str='', search_as_regexp=False, replace_as_regexp=False,
directive=FIND_FIND, re_flags={}, start_direction=FIND_HERE_FORWARD, fst_code=None, which_buffer=None
):
'''
'''
buffer = self[which_buffer]
if isinstance(re_flags, int):
re_flags_int = re_flags
else:
re_flags_int = 0
for flag, flag_mask in re_flags.iteritems():
if flag_mask:
re_flags_int |= flag
self.findagaininfo = {'find_str': find_str, 'replace_str': replace_str, 'directive': directive, 're_flags': re_flags_int, 'which_buffer': which_buffer,
'start_direction': start_direction, 'search_as_regexp': search_as_regexp, 'replace_as_regexp': replace_as_regexp, 'fst_code': fst_code
}
if fst_code is None:
self.fst = None
elif (not hasattr(self.fst, 'code')) or (self.fst.code != fst_code):
# you enabled fst and your function is different from the current self.fst
exec fst_code in locals()
self.fst = fst
self.fst.code = fst_code
if find_str not in self.find_history:
self.find_history.append(find_str)
if replace_str not in self.find_history:
self.find_history.append(replace_str)
if start_direction == FIND_AFTER_START:
buffer.move_mark_by_name('insert', buffer.get_start_iter())
elif start_direction == FIND_BEFORE_END:
buffer.place_cursor(buffer.get_end_iter())
if not search_as_regexp:
find_str = re.escape(find_str)
if replace_as_regexp:
replace_str = replace_str.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r').replace('\\f', '\f')
pattern = re.compile(find_str, re_flags_int)
if directive == FIND_FIND:
match = None
if start_direction in [FIND_HERE_FORWARD, FIND_AFTER_START]:
insitr = buffer.get_iter_at_mark(buffer.get_insert())
curoffs = insitr.get_offset() # offset of current TextIter
match = pattern.search(buffer.get_text(insitr, buffer.get_end_iter()))
if (match != None) and (match.start() == 0): # if i just re-found the text at the cursor, step forward
curoffs += 1
match = pattern.search(buffer.get_text(buffer.get_iter_at_offset(curoffs), buffer.get_end_iter()))
if match == None:
curoffs = 0
if findasktowrap:
d = self.mk_dialog('wrap?', (gtk.STOCK_OK, 1, gtk.STOCK_CANCEL, 0))
d.vbox.pack_start(gtk.Label("click OK to start again from the beginning.\n"
+ "click Cancel to stop searching.\n"
+ "if you want to automatically wrap, set findasktowrap in config.flauxtext to True."))
if d.run():
match = pattern.search(buffer.get_all_text())
else:
match = pattern.search(buffer.get_all_text())
if match != None:
buffer.select_range(
buffer.get_iter_at_offset(curoffs + match.start()),
buffer.get_iter_at_offset(curoffs+match.end())
)
else:
self.flash_info(find_str + ' not found', 2e3, 'not_found')
else: # find backwards
match = None
for match in pattern.finditer(buffer.get_text(buffer.get_start_iter(), buffer.get_iter_at_mark(buffer.get_insert()))):
pass # run through all the matches, keep only the last in the current namespace
if match == None:
if findasktowrap:
d = self.mk_dialog('wrap?', (gtk.STOCK_OK, 1, gtk.STOCK_CANCEL, 0))
d.vbox.pack_start(gtk.Label("click OK to start again from the end.\n"
+ "click Cancel to stop searching.\n"
+ "if you want to automatically wrap, set findasktowrap in config.flauxtext to True."))
if d.run():
for match in pattern.finditer(buffer.get_all_text()):
# keep the last match in the namespace
pass
else:
for match in pattern.finditer(buffer.get_all_text()):
# keep the last match in the namespace
pass
if match != None:
buffer.select_range(buffer.get_iter_at_offset(match.start()), buffer.get_iter_at_offset(match.end()))
else:
self.flash_info(find_str + ' not found', 2e3, 'not_found')
self.current_textview().scroll_to_mark(buffer.get_insert(),0.0,True)
elif directive == FIND_FIND_ALL:
try:
start, end = buffer.get_selection_bounds()
except ValueError:
start, end = buffer.get_start_iter(), buffer.get_end_iter()
for itr in pattern.finditer(buffer.get_text(start, end)):
buffer.apply_tag_by_name('becky',
buffer.get_iter_at_offset(itr.start() + start.get_offset()),
buffer.get_iter_at_offset(itr.end() + start.get_offset())
)
elif directive == FIND_REPLACE:
if start_direction in [FIND_HERE_FORWARD, FIND_AFTER_START]:
insert = buffer.get_iter_at_mark(buffer.get_insert())
match = pattern.search(buffer.get_text(insert, buffer.get_end_iter()))
curoffs = insert.get_offset() # offset of current TextIter
if (match is not None) and (match.start() == 0):
curoffs += 1
buffer.move_mark(buffer.get_insert(), buffer.get_iter_at_offset(curoffs))
match = pattern.search(buffer.get_text(buffer.get_iter_at_mark(buffer.get_insert()), buffer.get_end_iter()))
else: # replace backward
match = None
for match in pattern.finditer(buffer.get_text(buffer.get_start_iter(), buffer.get_iter_at_mark(buffer.get_insert()))):
pass # run through all the matches, keep only the last in the current namespace
curoffs = 0
if match is None:
self.flash_info(find_str + ' not found', 2e3, 'not_found')
return
else:
inso = curoffs + match.start()
endo = curoffs + match.end()
if self.fst:
replace_str = self.fst(match)
buffer.delete(buffer.get_iter_at_offset(inso), buffer.get_iter_at_offset(endo))
buffer.insert(buffer.get_iter_at_offset(inso), replace_str)
self.current_textview().scroll_to_mark(buffer.get_insert(), 0.0, True)
elif directive == FIND_REPLACE_ALL:
try:
start, end = buffer.get_selection_bounds()
except ValueError:
start, end = buffer.get_start_iter(), buffer.get_end_iter()
text = buffer.get_text(start, end)
if self.fst:
try:
reptext = re.sub(pattern, self.fst, text)
except Exception, e:
self.flash_info('YOUR FST CODE IS BUGGY: ' + str(e), 5e4, 'bad_fst_code')
return
else:
reptext = replace_str.join(pattern.split(text))
buffer.select_range(start, end)
buffer.delete_selection(False, True) # False=deletion is NOT caused by user interaction; True=buffer IS editable by default
buffer.insert_at_cursor(reptext)
self.current_textview().scroll_to_mark(buffer.get_insert(), 0.0, True) # textview may not correspond with buffer
else:
self.flash_info('invalid directive: %r' % directive, 'invalid_directive')
def find_matching_paren(self, idx):
''' return another idx [textiter.offset] pointing to the brother of the paren at the given idx.
[all in the current buffer]
relies on calling method to hilight the brother
supported 'paren's: (){}[]<>
basic strategy:
while nest_level > 0:
inc/decrement idx according to the type of the paren
if we hit the start/end of bfr, return None
inc/decrement nest_level if appropriate
return idx
'''
nest_level = 1
iter_time = 0
bfr = self.current_buffer()
paren = bfr.get_text(bfr.get_iter_at_offset(idx-1), bfr.get_iter_at_offset(idx))
if paren in '([{<': # search forward for the brother
direction = 1
elif paren in ')}]>': # search backward
direction = -1
else: # "paren" is not a paren
return None
if paren in '()':
prn_type = '()'
elif paren in '[]':
prn_type = '[]'
elif paren in '{}':
prn_type = '{}'
elif paren in '<>':
prn_type = '<>'
while nest_level > 0:
idx += direction
iter_time += 1
titr = bfr.get_iter_at_offset(idx)
if (direction == 1 and titr.is_end()) or (direction == -1 and titr.is_start()) or (
isinstance(maxparenlen, int) and (1 < maxparenlen < iter_time)):
# search failed: we haven't found a brother and we're still in loop
return None
cur_char = bfr.get_text(bfr.get_iter_at_offset(idx-1), titr)
if cur_char in prn_type:
if cur_char == paren:
nest_level += 1
else:
nest_level -= 1
return idx
def flash_info(self, msg='', wait=1000, lbl=None, n=1):
''' flashes msg in self.statbar_info n times, with a flash period of wait
milliseconds.
if n < 0: FLASH FOREVER!!11ONEONE
'''
if (lbl in infodialogwhen) or ('__ALL__' in infodialogwhen):
d = self.mk_dialog('alert!', (gtk.STOCK_OK, 0))
d.vbox.pack_start(gtk.Label(msg))
d.vbox.pack_start(gtk.Label("if you'd rather this flash on the status bar, remove\n"
+ lbl + "\nfrom 'infodialogwhen' in config.flauxtext."))
d.run()
else:
self.statbar_info.set(msg)
gobject.timeout_add(int(wait), lambda *a: self.statbar_info.set(''))
if n > 1:
gobject.timeout_add(wait * 2, self.flash_info, msg, wait, n - 1)
elif n < 0:
gobject.timeout_add(wait * 2, self.flash_info, msg, wait, n)
def hiliteparens(self, buf, ti, txtmark):
''' called whenever you move a TextMark txtmark [including the cursor]
if char before ti [TextIter at txtmark] is one of '[]{}()<>',
hilights it and its brother for 1 second, if appropriate.
'''
if (txtmark.get_name() != None) and (txtmark.get_name != 'insert'):
# ignore any textmarks but the cursor
return
itrb4ti = buf.get_iter_at_offset(ti.get_offset()-1)
curchar = buf.get_text(itrb4ti, ti)
if self.findparens and (curchar != '') and (curchar in '[]{}()<>'):
brotheridx = self.find_matching_paren(ti.get_offset())
if brotheridx==None:
tagnym = 'None'
buf.apply_tag_by_name(tagnym, itrb4ti, ti)
gobject.timeout_add(parenhilitems, self.unhiliteparens, buf, tagnym, itrb4ti.get_offset(), ti.get_offset())
else:
tagnym = curchar
brotheritr = buf.get_iter_at_offset(brotheridx)
itrb4brother = buf.get_iter_at_offset(brotheridx-1)
brother = buf.get_text(itrb4brother, brotheritr)
buf.apply_tag_by_name(brother, itrb4brother, brotheritr)
gobject.timeout_add(parenhilitems, self.unhiliteparens, buf, brother, brotheridx-1, brotheridx)
def line_numbers_expose(self, textview, event, user_data=None):
''' makes the line-numbers.
'''
left_win = textview.get_window(gtk.TEXT_WINDOW_LEFT)
right_win = textview.get_window(gtk.TEXT_WINDOW_RIGHT)
if event.window == left_win:
type = gtk.TEXT_WINDOW_LEFT
target = left_win
elif event.window == right_win:
type = gtk.TEXT_WINDOW_RIGHT
target = right_win
else:
return False
first_y = event.area.y
last_y = first_y + event.area.height
x, first_y = textview.window_to_buffer_coords(type, 0, first_y)
x, last_y = textview.window_to_buffer_coords(type, 0, last_y)
numbers = []
pixels = []
iter, top = textview.get_line_at_y(first_y)
# For each iter, get its location and add it to the arrays.
# Stop when we pass last_y
count = 0
size = 0
while not iter.is_end():
y, height = textview.get_line_yrange(iter)
pixels.append(y)
numbers.append(iter.get_line())
count += 1
if (y + height) >= last_y:
break
iter.forward_line()
layout = self.w.create_pango_layout("")
for i in range(count):
x, pos = textview.buffer_to_window_coords(type, 0, pixels[i])
strg = "%d" % (numbers[i]+1)
layout.set_text(strg)
self.w.style.paint_layout(target, self.w.state, False, None, self.w, None, 2, pos + 2, layout)
return False
def manage_mru(self, fn, add_bool=False):
''' removes fn from mrufn
if add_bool: put it back in at the top
'''
try:
self.histry.remove(fn)
except:
pass
f = open(mrufn)
histrytowrite = f.read()
f.close()
histrytowrite = re.split('\r?\n?', histrytowrite)
if fn in histrytowrite:
histrytowrite.remove(fn)
if add_bool:
histrytowrite = [fn] + histrytowrite
self.histry = [fn] + self.histry
histrytowrite = histrytowrite[:2*lenmru]
for i in range(len(histrytowrite)):
histrytowrite[i] = histrytowrite[i].strip() # pesky '\r'
for i in histrytowrite:
if len(i) < 2:
histrytowrite.remove(i)
histryfile = open(mrufn,'w')
histryfile.write('\n'.join(histrytowrite))
histryfile.close()
self.uiman.remove_ui(self.uiman.merge_ids['mru'])
mru_xml_data = ''
if mrunumbers:
mru_label = lambda i: '' % (
i, i, xml_encode(os.path.basename(self.histry[i].replace('_', '__'))), xml_encode(self.histry[i])
)
else:
mru_label = lambda i: '' % (
i, xml_encode(os.path.basename(self.histry[i].replace('_', '__'))), xml_encode(self.histry[i])
)
for i in xrange(len(self.histry)):
if os.path.isfile(self.histry[i]):
mru_xml_data += mru_label(i)
self.xml_to_ui(mruxmldata.replace('', mru_xml_data), 'mru')
def mk_dialog(self, title, buttons, kill_after=True, response_on_esc=0, modal=True):
d = gtk.Dialog(title, self.w, (gtk.DIALOG_NO_SEPARATOR | (modal and gtk.DIALOG_MODAL or 0)), buttons)
respond_dialog_on_escape(d, response_on_esc)
def newrun(d=d):
d.show_all()
r = gtk.Dialog.run(d)
if kill_after:
d.destroy()
return r
d.run = newrun
return d
def new_buffer(self, action=None):
''' adds an empty page to self.notebk, adorns and focuses it.
returns the new textbuffer, so you can say
ft.new_buffer().set_text(open(fn).read())
also appends a dict to self.buffer_handler_ids mapping event names to
handler_ids which can be passed to the "disconnect" method of
the indicated widget
textview # callback
"key-press-event" # capslock_statbar_switch
"populate-popup" # populatecontextmenu
"expose_event" # line_numbers_expose
"drag_data_received" # dnd_received_data
textbuffer # callback
"insert-text" # registerinsertionwithundo
"delete-range" # registerdeletionwithundo
"mark-set" # hiliteparens
where textview can be gotten with current_textview()
or self.notebk.get_nth_page(n).get_child()
and textbuffer can be gotten with current_buffer()
or textview.get_buffer()
'''
pagestokeepunmodified = []
for i in range(self.notebk.get_n_pages()):
tabtxt = self.notebk.get_tab_label_text(self.notebk.get_nth_page(i))
if (tabtxt[0] is not '*') and (tabtxt[-1] is not '*'):
pagestokeepunmodified.append(i)
# done cacheing state of current buffers, start making a new one
handler_ids = {}
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
textview = gtk.TextView()
textview.set_wrap_mode(wordwrappref)
textbuffer = textview.get_buffer()
textbuffer.get_all_text = lambda:textbuffer.get_text(textbuffer.get_start_iter(), textbuffer.get_end_iter())
handler_ids["expose_event"] = textview.connect("expose_event", self.line_numbers_expose)
handler_ids["mark-set"] = textbuffer.connect("mark-set", self.hiliteparens)
handler_ids["key-press-event"] = textview.connect("key-press-event", self.capslock_statbar_switch)
handler_ids["populate-popup"] = textview.connect("populate-popup", self.populatecontextmenu)
handler_ids["changed"] = textbuffer.connect("changed", self.autoindent)
handler_ids["insert-text"] = textbuffer.connect("insert-text", self.registerwithundo)
handler_ids["delete-range"] = textbuffer.connect("delete-range", self.registerwithundo)
try: # allow drag-n-dropping stuff on me--like files from explorer.exe----REQUIRES PYGTK 2.6
handler_ids["drag_data_received"] = textview.connect("drag_data_received", self.dnd_received_data)
textview.drag_dest_add_uri_targets()
except:
pass
textview.modify_font(pango.FontDescription(font))
found_tag = gtk.TextTag('becky') # found becky! :-)
for kee,val in foundtexttagstyle.items():
try: found_tag.set_property(kee, val)
except:
print kee, 'is an invalid gtk.TextTag property;',
print 'please change "foundtexttagstyle" in config.flauxtext accordingly'
textbuffer.tag_table.add(found_tag)
for tagnym, styledict in findparenstyle.items():
temptag = gtk.TextTag(tagnym)
for kee, val in styledict.items():
try:
temptag.set_property(kee, val)
except:
print kee, 'is an invalid gtk.TextTag property; please change "findparenstyle" in config.flauxtext accordingly'
textbuffer.tag_table.add(temptag)
if not self.notebk.get_show_tabs():
self.notebk.set_show_tabs(True)
if linenums in ['Right', 'Both']:
textview.set_border_window_size(gtk.TEXT_WINDOW_RIGHT, linenumwindowsize)
if linenums in ['Left', 'Both']:
textview.set_border_window_size(gtk.TEXT_WINDOW_LEFT, linenumwindowsize)
sw.add(textview)
sw.show()
textview.show()
self.notebk.append_page(sw, gtk.Label(UNTITLED_FILENAME))
textbuffer.undostack = UndoStack()
textbuffer.filename = UNTITLED_FILENAME
textbuffer.handler_ids = handler_ids
self.notebk.set_current_page(self.notebk.page_num(sw))
try:
self.actiongroup.get_action('toggle_editable').set_active(True)
except:
pass
# done making a new buffer, change the state of all pages to reflect cache [pagestokeepunmodified]
zothbfr = self.notebk.get_nth_page(0).get_child().get_buffer()
if ((self.notebk.get_n_pages() == 2) and
(self.notebk.get_tab_label_text(self.notebk.get_nth_page(0)) == self[0].filename == UNTITLED_FILENAME) and
(zothbfr.get_all_text() == '')):
# if you just opened FlauxText and haven't edited the initial textbuffer, i'll remove it
self.notebk.remove_page(0)
if self.notebk.get_n_pages()>1:
for i in pagestokeepunmodified:
pg_i = self.notebk.get_nth_page(i)
tab_txt = self.notebk.get_tab_label_text(pg_i)
if tab_txt[0] == tab_txt[-1] == '*':
self.notebk.set_tab_label_text(pg_i, tab_txt[3:-3])
if tabshideifone and self.notebk.get_n_pages()==1:
self.notebk.set_show_tabs(False)
if self.notebk.get_n_pages()>1 and not self.notebk.get_show_tabs():
self.notebk.set_show_tabs(True)
return textbuffer
def notebk_page_switched(self, notebk=None, page=None, page_num=None):
''' called whenever self.notebk's page number changes; updates certain things like View > Toggles > Editable.
'''
try:
self.actiongroup.get_action('toggle_editable').set_active(self.current_textview().get_editable())
except:
pass
def open(self, filename):
''' adds filename to self.notebk using new_buffer.
also manages my MRU, and does the createifdne logic.
'''
if neveropenbinary and not istextfile(filename):
self.flash_info(filename + ' is binary and neveropenbinary in my configfile is True', 4e4, 'binary_file')
return
fl = None
try:
fl = open(filename)
except IOError: # file DNE
if createifdne:
fl = open(filename,'w')
fl.close()
fl = open(filename)
else:
print "could not find", filename, "... removing it from the history file."
try:
self.manage_mru(filename)
return False
except Exception, e:
print e
try:
text = fl.read()
fl.close()
textbuffer = self.new_buffer()
textbuffer.set_text(text)
curpagen = self.notebk.get_current_page()
self.notebk.set_tab_label_text(self.notebk.get_nth_page(curpagen), os.path.basename(filename))
self.w.set_title(windowtitle % filename)
textbuffer.place_cursor(textbuffer.get_start_iter())
self.current_textview().scroll_to_iter(textbuffer.get_start_iter(),0.0)
textbuffer.filename = filename
textbuffer.undostack = UndoStack()
try:
self.manage_mru(filename, True)
except IndexError:
print i # shouldn't ever happen
except IOError:
self.flash_info("could not open %s for writing. you won't be able to open from the File > Previous menu." % (mrufn, filename), 3000, 'badmru')
except AttributeError:
pass
def populatecontextmenu(self, textview, menu):
''' adds the menuitems in the Edit > Selected submenu to the context menu when you right-click on a textview.
'''
submi = gtk.MenuItem('_Edit Selected')
subm = gtk.Menu()
submi.set_submenu(subm)
submi.connect('activate-item',lambda a:subm.popup(None,submi,None,-1,-1))
callback = lambda action_name: lambda menuitem: self.run_plugin('editselected', action_name)
for action_name in __imp0rt_plugin__('editselected').actions:
action = self.actiongroup.get_action('plugin__editselected__' + action_name)
if action is not None:
mi = gtk.MenuItem(action.get_property('label'))
mi.set_property('name', action_name)
mi.show()
mi.connect('activate', callback(action_name))
subm.append(mi)
del mi
submi.show()
subm.show()
menu.attach(submi, 0, 1, 0, 1)
def registerwithundo(self, tb, titr, txt, ln=None):
''' called when you insert/delete txt into/from tb [the current textbuffer] at the textiter at the cursor [titr].
i use ln as a flag;
the callback for 'insert-text' passes it as the length of the text entered, but that's accessible as len(txt)
the callback for 'delete-range' doesn't provide it, so (ln != None) is True if text was inserted
appends [tb.get_char_offset(),insbool,txt] to the undostack for the current buffer if any of:
len(txt) > 1 # if you insert/delete more than a single char at a time
ln == None # if you deleted text, i have no good way of adding it to previously deleted text
self.current_undo_list() == None # if that list would be the first for its buffer
(re.match('\W',txt) and re.search('\w', curundoentry.data)) # undo by words
# as a consequence of the way this bit of logic is phrased, the following statement would always pass
# assert re.match('\W*\w+', self.current_undo_list()[2])
((ln != None) != curundoentry.was_inserted) # if you're inserting/deleting now, and you'd just deleted/inserted
titr.get_offset() != self.current_undo_list()[1] + len(self.current_undo_list()[2]) # if you moved the cursor
if none of those conditions are met, txt is appended to the current_undo_list()'s string.
'''
curfn = tb.filename
curundostack = tb.undostack
curundoentry = curundostack.current()
offset = titr.get_offset()
stitr = tb.get_start_iter()
enditr = tb.get_end_iter()
if ln == None:
txt = tb.get_text(titr, txt)
if curundostack.pointer < (len(curundostack) - 1):
# if you undid, then edited the buffer: forget what you'd done before
curundostack.stack = curundostack.stack[:curundostack.pointer + 1]
for i in findparenstyle.keys():
tb.remove_tag_by_name(i, stitr, enditr)
current_scrolled_window = self.current_scrolledwindow()
tabtxt = self.notebk.get_tab_label_text(current_scrolled_window)
if (tabtxt[0] != '*') and (tabtxt[-1] != '*') and (curfn != UNTITLED_FILENAME):
if self.autosave:
self.save()
else:
self.notebk.set_tab_label_text(current_scrolled_window, '** ' + tabtxt + ' **')
self.w.set_title((windowtitle % curfn) + windowtitlemodified)
self.statbar_info.set('')
if ((len(txt) > 1)
or (ln == None)
or (curundoentry == None)
or (re.match('\W', txt) and re.search('\w', curundoentry.data))
or ((ln != None) != curundoentry.was_inserted)
or (offset != (curundoentry.offset + len(curundoentry.data)))
):
# make a new UndoStackEntry, increment the pointer
curundostack.push(UndoStackEntry(offset=offset, was_inserted=(ln != None), data=txt))
else: # you're typing regularly
curundoentry.data += txt
def run_plugin(self, plg, *a, **kw):
''' if plg is an instance of gtk.Action: plg is prepended to a; plg = plg.get_name()
calls the function named the value of plg in the plugin named plg, importing it as necessary.
self is dynamically injected into the plugin's global namespace.
flauxtext's global namespace is therefore accessible as 'self.globals'.
*a and **kw are passed on to it.
'''
if isinstance(plg, gtk.Action):
a = [plg] + list(a)
plg = plg.get_name()
imp0rt_plugin(plg)
if not plg.startswith('plugin'):
plg = 'plugin' + plg
eval(plg).self = self
try:
eval('%s.%s' % (plg, plg[6:]))(*a, **kw)
except Exception, e:
self.flash_info('malformed plugin: ' + plg[6:] + ' '*4 + str(e), 6e3, 'bad_plugin')
def run_what(self, filename):
''' convenience function used by the 'run...' menuitems.
fetches a command based on the filename and runcmds
'''
if filename == UNTITLED_FILENAME:
self.flash_info('you must save before you can run.', 3e3, 'unable_to_f5')
return
filenameopts = {'full': filename, 'dir': os.path.dirname(filename),
'ext': os.path.splitext(filename)[-1],
'base':os.path.splitext(os.path.basename(filename))[0]
}
runthis = ''
for kee in runcmds: # runcmds is a dict in the os portion of config.flauxtext mapping fnmatch patterns to commands
if fnmatch.fnmatch(filename,kee):
if isinstance(runcmds[kee], str):
# runthis=runcmds[kee]%tuple([filename]*(len(runcmds[kee].split('%s'))-1))
runthis = runcmds[kee] % filenameopts
elif isinstance(runcmds[kee], (tuple, list)):
for i in range(len(runcmds[kee])-1):
os.popen(runcmds[kee][i] % filenameopts)
runthis = runcmds[kee][-1] % filenameopts
if runthis == '':
runthis = termcmd + '"' + filename + '"'
return runthis
def save(self, which=None):
''' calls save_file if self.current_filename() != UNTITLED_FILENAME
else calls callback_saveas
'''
if which == None:
bfr = self.current_buffer()
fn = self.current_filename()
else:
bfr = self[which]
if isinstance(which, (str, unicode)):
fn = which
else:
fn = self[which].filename
if fn == UNTITLED_FILENAME:
self.callback_saveas(None)
else:
self.save_file(fn, bfr.get_all_text())
def save_file(self, filename, data):
''' opens filename for writing, writes data to filename, and relabels the current tab, or opens an error dialog.
'''
try:
open(filename, "w").write(data)
self.notebk.set_tab_label_text(self.notebk.get_nth_page(self.notebk.get_current_page()),os.path.basename(filename))
except:
d = self.mk_dialog('error! OMGOMG!', (gtk.STOCK_OK, 0))
d.vbox.pack_start(gtk.Label('unable to open '+filename+' for writing.'))
d.run()
def split(self, vertical=True, add2=False):
''' splits the main area into a (vertical and gtk.VPaned or gtk.HPaned),
reparents self.vbox into the (add2 and 'second' or 'first') half of the new Paned
appends the new Paned to self.panes, and returns it
if you don't particularly want resize-ability and horizontal split, just use self.vbox.add(your_widget)
'''
new_pane = (vertical and gtk.VPaned or gtk.HPaned)()
new_pane.show()
self.panes.append(new_pane)
new_panes_parent = self.vbox.get_parent()
new_panes_parent.remove(self.vbox)
new_panes_parent.add(new_pane)
if add2:
new_pane.add2(self.vbox)
else:
new_pane.add1(self.vbox)
new_panes_parent.show_all()
return new_pane
def split_from(self, direction, raise_if_sad=False):
''' call split in such a way as to return a pane on the direction side of the window,
where direction is in ['left', 'right', 'top', 'bottom'].
if direction is invalid and not raise_if_sad: direction = 'bottom'
'''
if direction not in ['left', 'right', 'top', 'bottom']:
if raise_if_sad:
raise TypeError("direction not in ['left', 'right', 'top', 'bottom']")
else:
direction = 'bottom'
return self.split(where in ['top', 'bottom'], where in ['left', 'top'])
start = __call__
def unhiliteparens(self, bfr, tagnym, stidx, enidx):
''' removes the tag "tagnym" from the given buffer between the given indices
used to suppress GtkWarnings while unhiliting pairs of parentheses
'''
bfr.remove_tag_by_name(tagnym, bfr.get_iter_at_offset(stidx), bfr.get_iter_at_offset(enidx))
def update_window_title(self):
''' convenience method to change self.w's title
'''
wintitle = windowtitle % self.current_filename()
tabtext = self.notebk.get_tab_label_text(self.notebk.get_nth_page(self.notebk.get_current_page()))
if tabtext[0] == tabtext[-1] == '*':
wintitle = wintitle + windowtitlemodified
if self.w.get_title() != wintitle:
self.w.set_title(wintitle)
def xml_to_ui(self, xml_str='', merge_id=None):
''' parses xml_str with xml.parsers.expat,
extracts action tuples and gtk's version of uixml, passing both to self.uiman.
also does the menustatbar logic.
'''
action_tuples = []
toggle_action_tuples = []
new_xml = []
ignore_next_end_tag = [False]
ignore_until_end_tag = []
def start_tag(tagtype, attrs={}):
tagtype = tagtype.lower()
if tagtype in ['separator', 'ui']:
new_xml.append('<%s>' % tagtype)
elif tagtype in ['menubar', 'toolbar']:
new_xml.append('<%s name="%s">' % (tagtype, attrs.get('name', '')))
elif tagtype in ['menu', 'menuitem', 'toolitem']:
if (len(ignore_until_end_tag) > 0) or (('if' in attrs) and not eval(attrs['if'], globals(), namespace)):
ignore_until_end_tag.append(tagtype)
else:
action_name = attrs.get('action', '')
#print action_name # XXX
if 'hotkey' in attrs:
hotkey = attrs['hotkey']
for modifier in ['control', 'shift', 'alt', 'meta', 'super', 'hyper']:
hotkey = re.sub('(?i)' + re.escape(modifier + '+'), '<%s>' % modifier, hotkey)
else:
hotkey = None
if tagtype == 'menu':
callback = None
else:
try:
if '__' in action_name:
action_name_parts = action_name.split('__')
callback_ = eval('self.callback_' + action_name_parts[0], namespace)
callback = lambda evt: callback_(*(action_name_parts[1:] + [evt]))
else:
callback = eval('self.callback_' + action_name, namespace)
except Exception, e:
print e
callback = None
action_tuples.append((
action_name,
eval('gtk.STOCK_' + attrs.get('icon', 'NONE').upper()),
attrs.get('label', action_name),
hotkey,
attrs.get('tooltip', None),
callback
))
if 'toggle_init' in attrs:
toggle_action_tuples.append(action_tuples.pop() + (eval(attrs['toggle_init']),))
new_xml.append('<%s action="%s">' % (tagtype, action_name))
else:
print 'error parsing uixml:', tagtype, attrs
new_xml.append('<%s %s>' % ((tagtype, ''.join([(k + '="%s"' % v) for k, v in attrs.items()]))))
def end_tag(tagtype):
if ignore_until_end_tag and (tagtype == ignore_until_end_tag[-1]):
ignore_until_end_tag.pop()
else:
new_xml.append('%s>' % tagtype)
if menustatbar:
old_start_tag, old_end_tag = start_tag, end_tag
d = {'/MenuBar':{}}
lvl = ['MenuBar']
def start_tag(tagtype, attrs={}):
old_start_tag(tagtype, attrs)
if 'action' in attrs:
if tagtype == 'menuitem':
statement = ('d["/%s"] = ""' % '"]["/'.join(lvl + [attrs['action']]))
exec statement in namespace
elif tagtype in ['menu', 'menubar']:
lvl.append(attrs['action'])
statement = ('d["/%s"] = {}' % '"]["/'.join(lvl))
exec statement in namespace
def end_tag(tagtype=''):
old_end_tag(tagtype)
if tagtype in ['menu', 'menubar']:
lvl.pop()
namespace = locals()
p = xml.parsers.expat.ParserCreate()
p.StartElementHandler = start_tag
p.EndElementHandler = end_tag
try:
p.Parse(xml_str, 1)
except Exception, e:
print 'could not parse xml:', e
return
self.actiongroup.add_actions(action_tuples)
self.actiongroup.add_toggle_actions(toggle_action_tuples)
if self.actiongroup in self.uiman.get_action_groups():
# if we find multiple xml files to merge, uiman freaks out if we add the same but different actiongroup again
self.uiman.remove_action_group(self.actiongroup)
self.uiman.insert_action_group(self.actiongroup, 0)
try:
if merge_id != None:
self.uiman.merge_ids[merge_id] = self.uiman.add_ui_from_string(''.join(new_xml))
else:
self.uiman.add_ui_from_string(''.join(new_xml))
except Exception, e:
# shouldn't ever happen unless you borked some xml
print 'badness10000:', ''.join(new_xml)
print e
self.uiman.ensure_update()
if menustatbar:
self.connect_menuitem_selections_statbar_info_recurse('', d)
##########################################################################################################
## INIT INIT INIT INIT INIT INIT INIT INIT INIT INIT INIT INIT INIT
##########################################################################################################
def __init__(self, **kwargs):
''' FlauxText constructor.
parses rc, loads MRU, loads ui.xml, makes a big
honkin' VBox and packs a menubar [and toolbar] into it from the UIManager,
packs a Notebook into the VBox, connects menubar menuitems to Actions to callbacks,
dynamically creates the MRU and Special files [and os-specific] menuitems
and connects those, puts initial page into the Notebook, and finally
creates 4 statusbars and packs those into an HBox at the bottom of the VBox.
'''
global uixmldata
gtk.rc_parse(getmyfile('rc.flauxtext'))
self.histry = map(str.strip, filter(os.path.isfile, re.split('\r?\n?', open(mrufn).read())))[:lenmru]
self.specialfilelist = map(str.strip, filter(os.path.isfile, re.split('\r?\n?', open(specialsfn).read())))
if not '' in uixmldata: # ui.xml was created in the getmyfile call
print "it needs to be named config.flauxtext, be INI-style, be in the same directory as 'flauxtext.pyw' [",
print os.path.dirname(__file__), "], and define all of my configuration variables."
print "if you accidentally deleted it, please download it again from"
print "http://sf.net/projects/fauxlkner"
sys.exit(2)
if not os.path.exists(instancetrackingfn):
# manage my poorly-named "instancetracking" file
open(instancetrackingfn, 'w').write('')
self.tempfilemtime = os.path.getmtime(instancetrackingfn)
# manage my pid file, which is more responsible for instance tracking
f = open(os.path.join(tempdir, 'pid.flauxtext'), 'w')
try:
f.write(str(os.getpgid(os.getpid())))
except:
f.write(str(os.getpid()))
f.close()
self.lasthelppy = 'flauxtext'
self.lastmanpage = 'bash'
self.findagaininfo = {}
self.autoident = autoindent
self.autosave = autosaveinit
self.cvsops = ''
self.transparency = transparency
self.findparens = findparens
self.find_history = []
self.smtpcontactslist = None # load those in the smtp plugin, which might never be loaded
self.panes = [] # list of gtk.Pane()s containing each other. the last Pane in this list holds self.vbox; see self.split
self.w = None
# save creating the top-level window for either start() or scripter's own methods
# set it to None now so modal dialogs work somewhat if i'm imported [as opposed to not at all]
self.vbox = gtk.VBox()
self.vbox.show()
self.uiman = gtk.UIManager()
self.uiman.merge_ids = {}
self.accelgroup = self.uiman.get_accel_group()
self.actiongroup = gtk.ActionGroup('UIManagr')
if tearoffmenus:
self.uiman.set_add_tearoffs(True)
self.notebk = gtk.Notebook()
self.notebk.show()
self.notebk.popup_enable()
self.notebk.homogeneous=True
self.notebk.set_scrollable(True)
self.notebk.set_show_border(False)
self.notebk.set_tab_pos(tabbarlocation)
# populate the first tab
textbuffer = self.new_buffer()
specials_xml_data = ''
bad_specials = []
for i in xrange(len(self.specialfilelist)):
if os.path.isfile(self.specialfilelist[i]):
specials_xml_data += '' % (
i, i + 1, xml_encode(os.path.basename(self.specialfilelist[i])), xml_encode(self.specialfilelist[i])
)
if openspecialsatstart:
self.open(self.specialfilelist[i])
else:
bad_specials.append(i)
self.specialfilelist = [x for i, x in enumerate(self.specialfilelist) if i not in bad_specials]
mru_xml_data = ''
bad_mru = []
if mrunumbers:
mru_label = lambda i: '' % (
i, i, xml_encode(os.path.basename(self.histry[i].replace('_', '__'))), xml_encode(self.histry[i])
)
else:
mru_label = lambda i: '' % (
i, xml_encode(os.path.basename(self.histry[i].replace('_', '__'))), xml_encode(self.histry[i])
)
for i in xrange(len(self.histry)):
if os.path.isfile(self.histry[i]):
mru_xml_data += mru_label(i)
else:
bad_mru.append(i)
self.specialfilelist = [x for i, x in enumerate(self.specialfilelist) if i not in bad_mru]
self.xml_to_ui(uixmldata, 'main')
self.xml_to_ui(mruxmldata.replace('', mru_xml_data), 'mru')
self.xml_to_ui(specialsxmldata.replace('', specials_xml_data), 'specials')
for fn in os.listdir(plugindir):
if os.path.splitext(fn)[1] == '.xml':
self.xml_to_ui(open(os.path.join(plugindir, fn)).read(), fn)
self.menubar = self.uiman.get_widget('/MenuBar')
self.toolbar = self.uiman.get_widget('/ToolBar')
if self.menubar == None:
print "error in ui.xml: can't find /MenuBar"
sys.exit(2)
else:
self.vbox.pack_start(self.menubar, False)
self.menubar.show()
if self.toolbar:
self.toolbar.set_icon_size(toolbariconsize)
self.toolbar.set_style(toolbarstyle)
self.toolbar.show()
if toolbarvert in ['Left', 'Right']:
self.tbarhbox = gtk.HBox(False)
self.tbarhbox.show()
self.vbox.pack_start(self.tbarhbox)
self.toolbar.set_property('orientation', gtk.ORIENTATION_VERTICAL)
self.tbarhbox.set_property('homogeneous', False)
if toolbarvert == 'Left':
self.tbarhbox.pack_start(self.toolbar, False, False)
self.tbarhbox.pack_start(self.notebk)
else:
self.tbarhbox.pack_start(self.notebk)
self.tbarhbox.pack_start(self.toolbar, False, False)
else:
self.vbox.pack_start(self.toolbar, False)
self.vbox.pack_start(self.notebk)
self.notebk_switch_page_handler_id = self.notebk.connect("switch-page", self.notebk_page_switched)
self.statbarhbox = gtk.HBox()
self.vbox.pack_start(self.statbarhbox, False)
if showstatbar:
self.statbarhbox.show()
# make the status bar objects and pack them into the statbarhbox
for nym in ['info', 'insovr', 'capslock', 'cursor']:
real_nym = 'self.statbar_' + nym
exec real_nym + ' = gtk.Statusbar()' in locals(), globals()
sb = eval(real_nym)
sb.show()
sb.set_property('has-resize-grip', nym in statbarresizegrip)
exec ('def set_this(s):\n try:self.statbar_%s.pop(1),self.statbar_%s.push(1, s)\n except:print s' % (nym, nym)) in locals()
# i'm sorry, but the TSQ, spaced-out version of the above def statement wouldn't compile
sb.set = set_this
# pack the statbars according to the order you set in config
for i in statbarorder:
s = "self.statbarhbox.pack_start(self.statbar_%s, False, False)" % i
s = s.replace("info, False, False", "info") # info is the only bar that should expand and receive extra space
eval(s)
# set sizes of 3 of the 4 statbars: w,h
# these sizes will never change because these widgets were packed into their HBox with fill=expand=False
# statbar_info will expand to fill the width of my toplevel window
self.statbar_capslock.set_size_request(12, 15)
self.statbar_insovr.set_size_request(30, 15)
self.statbar_cursor.set_size_request(120, 15)
# push starting strings to the statbars
self.statbar_info.push(1, '')
self.statbar_insovr.push(1, 'ins')
self.statbar_capslock.push(1, 'a')
self.statbar_capslock.value = 'a'
self.statbar_cursor.push(1, statbarcursorformat % (1, 0))
self.__dict__.update(kwargs)
# end __init__
# end class FlauxText
def alreadyrunning():
''' returns True if the contents of pid.flauxtext is in a tasklist [ps], False otherwise.
'''
if os.name == 'nt':
ps = os.popen('tasklist').read().lower()
else:
ps = os.popen('ps -A -o pgid').read().lower()
try:
f = open(os.path.join(tempdir, 'pid.flauxtext'))
myotherpid = f.read()
f.close()
except IOError: # couldn't open pid.flauxtext
return False
return myotherpid in ps
def test():
''' runs doctest.testmod()
'''
import doctest
doctest.testmod()
def parseargs(arg):
''' arg is a list of strings containing any number of the following mixed in with file/dirnames:
-r|--regexp, -x|-T|--text, --noalpha
return a list of filenames, and/or adjust my transparency global
'''
if "--noalpha" in arg:
transparency = False
arg.remove("--noalpha")
if "--regexp" in arg or "-r" in arg:
if "-r" in arg:
flag = '-r'
else:
flag = '--regexp'
rxp = arg[arg.index(flag) + 1]
for dirnym in arg[arg.index(flag) + 2:]:
arg.remove(dirnym)
for f in os.listdir(dirnym):
if os.path.isfile(f) and re.match(rxp,f):
arg.append(os.path.join(dirnym, f))
arg.remove(rxp)
arg.remove(flag)
if "-T" in arg or "-x" in arg or "--text" in arg:
for i in arg:
if os.path.isdir(i):
arg.remove(i)
arg.append([t for t in os.listdir(i) if istextfile(t)])
elif not istextfile(i):
arg.remove(i)
return [os.path.abspath(arg[i])
for i in range(len(arg))
if (os.path.isfile(arg[i]) or os.path.isdir(arg[i]) or (arg[i] == '-'))
]
__version__ = 112 # int version here; major releases on sf
__email__ = 'faulkner@users.sf.net'
__author__ = 'faulkner <' + __email__ + '>' # email me if you should need my full legal name
__license__ = 'GNU GPL 2'
__url__ = 'http://fauxlkner.sf.net'
__todo__ = [
'fix the saveas folder bug',
'fix plugins: collab, hexed, ubbpost, macro',
'make all names more consistent with the pygtk naming scheme',
'update all docstrings',
'pygtksourceview on windows',
'rename/move/eliminate: unhiliteparens, hiliteparens, dnd_get_data, dnd_received_data',
]
if __name__ == '__main__':
if "-h" in sys.argv or "--help" in sys.argv:
print __doc__
elif "--profile" in sys.argv:
import profile
arg = sys.argv[1:]
arg.remove("--profile")
profile.run('FlauxText()(parseargs(arg))')
elif "--test" in sys.argv:
test()
elif "--version" in sys.argv or "-v" in sys.argv:
print "release 2, revision", str(__version__)
else:
if (checkforalreadyrunning and not (("--notrack" in sys.argv) or ("-n" in sys.argv)) and
(len(sys.argv) > 1) and alreadyrunning()
):
open(os.path.join(tempdir, 'instancetracking.flauxtext'),'w').write(os.linesep.join(parseargs(sys.argv[1:])))
else:
FlauxText()(parseargs(sys.argv[1:]))