ped_core.editor_common module
module that implements the guts of the ped editor, the file abstraction and the editor
# Copyright 2009-2012 James P Goodwin ped tiny python editor """ module that implements the guts of the ped editor, the file abstraction and the editor """ import curses import curses.ascii import sys import os import shutil import tempfile import re import gc from ped_dialog.prompt_dialog import prompt from ped_dialog.message_dialog import message from ped_dialog.replace_dialog import replace,confirm_replace from ped_dialog.confirm_dialog import confirm from ped_dialog import file_dialog from ped_core import undo from ped_core import python_mode from ped_core import java_mode from ped_core import cpp_mode from ped_core import guess_mode import copy from ped_core import clipboard import subprocess from ped_core import cmd_names from ped_core import keytab from ped_core import keymap from ped_core import extension_manager from ped_core import changes import traceback import locale import codecs import tempfile import threading import logging import time locale.setlocale(locale.LC_ALL,'') def_encoding = locale.getpreferredencoding() class EditLine: """ Interface for each editable line in a file, a fly-weight object """ def __init__(self): """ should initialize any content or references to external objects """ pass def length(self): """ return the length of the line """ pass def flush(self): """ flush cached length if you have one """ pass def getContent(self): """ should return line representing this line in the source file """ pass class FileLine(EditLine): """ Instance of a line in a file that hasn't been changed, stored on disk """ def __init__(self, parent, pos, len = -1 ): """ FileLine(s) are pointers to a line on disk the EditFile reference and offset are stored """ EditLine.__init__(self) self.parent = parent self.pos = pos self.len = len def length(self): """ return length of line """ if self.len < 0: self.len = len(self.parent.expand_tabs(self.getContent())) return self.len def flush(self): """ flush cached length """ self.len = -1 def getContent(self): """ gets the file from its parent, seeks to position, reads line and returns it """ working = self.parent.getWorking() working.seek(self.pos,0) txt = working.readline().rstrip() return txt def __del__(self): self.parent = None class MemLine(EditLine): """ Instance of a line in memory that has been edited """ def __init__(self, content ): """ MemLine(s) are in memory strings that represent a line that has been edited, it is initialized from the original file content""" EditLine.__init__(self) self.content = content def length(self): """ return the length of the content """ return len(self.content) def flush(self): """ flush cached length """ pass def getContent(self): """ just return the string reference """ return self.content class ReadOnlyError(Exception): """ Exception when modification to readonly file attempted """ pass class EditFile: """ Object that manages one file that is open for editing, lines are either pointers to lines on disk, or in-memory copies for edited lines """ default_readonly = False default_backuproot = "~" def __init__(self, filename=None ): """ takes an optional filename to either load or create """ # store the filename self.filename = filename # the root of the backup directory self.backuproot = EditFile.default_backuproot # set the default tab stops self.tabs = [ 4, 8 ] # set the changed flag to false self.changed = False # read only flag self.readonly = EditFile.default_readonly # undo manager self.undo_mgr = undo.UndoManager() # change manager self.change_mgr = changes.ChangeManager() # modification reference incremented for each change self.modref = 0 # the file object self.working = None # the lines in this file self.lines = [] # load the file if filename: self.load() def __copy__(self): """ override copy so that copying manages file handles and intelligently copies the lists """ result = EditFile() result.filename = self.filename result.tabs = self.tabs result.changed = self.changed result.readonly = True result.undo_mgr = copy.copy(self.undo_mgr) result.change_mgr = copy.copy(self.change_mgr) result.modref = self.modref result.lines = [] for l in self.lines: if isinstance(l,MemLine): result.lines.append(copy.deepcopy(l)) elif isinstance(l,FileLine): result.lines.append(FileLine(result,l.pos,l.len)) result.working = None if self.working: result.working = open(self.working.name,"r",buffering=1,encoding="utf-8") return result def __del__(self): """ make sure we close file when we are destroyed """ self.undo_mgr = None self.change_mgr = None self.close() def set_tabs(self, tabs ): """ set the tab stops for this file to something new """ if tabs != self.tabs: self.tabs = tabs for l in self.lines: l.flush() def get_tabs(self): """ return the list of tab stops """ return self.tabs def getWorking(self): """ return the file object """ return self.working def getModref(self): """ modref is a serial number that is incremented for each change to a file, used to detect changes externally """ return self.modref def setUndoMgr(self,undo_mgr): """ sets the undo manager object for this EditFile, undo manager is used to record undo records to enable undo in the editor """ self.undo_mgr = undo_mgr def getUndoMgr(self): """ returns our undo_manager """ return self.undo_mgr def isChanged(self): """ true if there are unsaved changes, false otherwise """ return self.changed def isReadOnly(self): """ true if the file is read only, false otherwise """ return self.readonly def setReadOnly(self,flag = True): """ mark this file as read only """ self.readonly = flag def getFilename(self): """ get the filename for this file """ return self.filename def setFilename(self,filename): """ set the filename for this object """ self.filename = filename def numLines(self): """ get the number of lines in this file """ return len(self.lines) def open(self): """ open the file or create it if it doesn't exist """ abs_name = os.path.abspath(self.filename) abs_path = os.path.dirname(abs_name) if os.path.exists(abs_name): self.working = tempfile.NamedTemporaryFile(mode="w+",buffering=1,encoding="utf-8",prefix="ped_",dir=EditFile.get_backup_dir(self.backuproot)) shutil.copyfile(abs_name,self.working.name) if not self.readonly: self.setReadOnly(not os.access(abs_name,os.W_OK)) elif not self.readonly: self.working = tempfile.NamedTemporaryFile(mode="w+",buffering=1,encoding="utf-8",prefix="ped_",dir=EditFile.get_backup_dir(self.backuproot)) else: raise Exception("File %s does not exist!"%(self.filename)) self.filename = abs_name self.working.seek(0,0) def isModifiedOnDisk(self): """ return true if the file we're editing has been modified since we started """ if os.path.exists(self.filename): disk_stat = os.stat(self.filename) temp_stat = os.stat(self.working.name) return disk_stat.st_mtime > temp_stat.st_mtime else: return False def close(self): """ close the file """ if self.working: self.working.close() self.working = None self.lines = None def load(self): """ open the file and load the lines into the array """ self.open() self.lines = [] pos = 0 lidx = 0 while True: line = self.working.readline() if not line: break line = line.rstrip() lidx = lidx + 1 self.lines.append(FileLine(self,pos,len(self.expand_tabs(line)))) pos = self.working.tell() while len(self.lines) and not self.lines[-1].getContent().strip(): del self.lines[-1] if not len(self.lines): self.lines.append(MemLine("")) self.changed = False self.modref = 0 def hasChanges(self,view): """ return true if there are pending screen updates """ return self.change_mgr.has_changes(view) def isLineChanged(self,view,line): """ return true if a particular line is changed """ if self.change_mgr and line < len(self.lines): return self.change_mgr.is_changed(view,line) else: return True def flushChanges(self,view): """ reset the change tracking for full screen redraw events """ if self.change_mgr: self.change_mgr.flush(view) def _deleteLine(self,line,changed = True): """ delete a line """ if self.undo_mgr: self.undo_mgr.get_transaction().push(self._insertLine,(line,self.lines[line],self.changed)) del self.lines[line] self.changed = changed self.modref += 1 if self.change_mgr: self.change_mgr.changed(line,len(self.lines)) def _insertLine(self,line,lineObj,changed = True): """ insert a line """ if self.undo_mgr: self.undo_mgr.get_transaction().push(self._deleteLine,(line,self.changed)) self.lines.insert(line,lineObj) self.changed = changed self.modref += 1 if self.change_mgr: self.change_mgr.changed(line,len(self.lines)) def _replaceLine(self,line,lineObj,changed = True): """ replace a line """ if self.undo_mgr: self.undo_mgr.get_transaction().push(self._replaceLine,(line,self.lines[line],self.changed)) self.lines[line] = lineObj self.changed = changed self.modref += 1 if self.change_mgr: self.change_mgr.changed(line,line) def _appendLine(self,lineObj,changed = True): """ add a line """ if self.undo_mgr: self.undo_mgr.get_transaction().push(self._deleteLine,(len(self.lines),self.changed)) self.lines.append(lineObj) self.changed = changed self.modref += 1 if self.change_mgr: self.change_mgr.changed(len(self.lines)-1,len(self.lines)-1) def touchLine(self, line_start, line_end): """ touch a line so it will redraw""" if self.change_mgr: self.change_mgr.changed(min(line_start,line_end),max(line_start,line_end)) def length(self, line ): """ return the length of the line """ if line < len(self.lines): return self.lines[line].length() else: return 0 def getLine( self, line, pad = 0, trim = False ): """ get a line """ if line < len(self.lines): orig = self.lines[line].getContent() else: orig = "" if trim: orig = orig.rstrip() if pad > len(orig): orig = orig + ' '*(pad-len(orig)) return self.expand_tabs(orig) def getLines( self, line_start = 0, line_end = -1): """ get a list of a range of lines """ if line_end < 0: line_end = len(self.lines) if line_end > len(self.lines): line_end = len(self.lines) lines = [] while line_start < line_end: lines.append(self.expand_tabs(self.lines[line_start].getContent())) line_start += 1 return lines def deleteLine( self, line ): """ delete a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line < len(self.lines): self._deleteLine(line) def insertLine( self, line, content ): """ insert a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line >= len(self.lines): lidx = len(self.lines) while lidx <= line: self._appendLine(MemLine("")) lidx += 1 self._insertLine(line,MemLine(content)) def replaceLine( self, line, content ): """ replace a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line >= len(self.lines): lidx = len(self.lines) while lidx <= line: self._appendLine(MemLine("")) lidx += 1 self._replaceLine(line, MemLine(content)) @staticmethod def get_backup_dir( base = "~" ): """ get the backup directory, create it if it doesn't exist """ base = os.path.expanduser(base) if not os.path.exists(base): base = os.path.expanduser("~") pedbackup = os.path.join(base,".pedbackup") if not os.path.exists(pedbackup): os.mkdir(pedbackup) return pedbackup @staticmethod def make_backup_dir( filename, base = "~" ): """ make a backup directory under ~/.pedbackup for filename and return it's name """ pedbackup = EditFile.get_backup_dir( base ) (filepath,rest) = os.path.split(os.path.abspath(filename)) for part in filepath.split("/"): if part: pedbackup = os.path.join(pedbackup,part) if not os.path.exists(pedbackup): os.mkdir(pedbackup) return os.path.join(pedbackup,rest) def save( self, filename = None ): """ save the file, if filename is passed it'll be saved to that filename and reopened """ if filename: if filename == self.filename and self.isReadOnly(): raise ReadOnlyError() o = open(filename,"w",buffering=1,encoding="utf-8") for l in self.lines: txt = l.getContent()+'\n' o.write(txt) o.close() self.close() self.filename = filename self.load() else: if self.isReadOnly(): raise ReadOnlyError() if not self.changed: return o = open(self.filename+".sav","w",buffering=1,encoding="utf-8") for l in self.lines: txt = l.getContent()+'\n' o.write(txt) o.close() self.working.close() if os.path.exists(self.filename): fstat = os.stat(self.filename) backup_path = EditFile.make_backup_dir(self.filename,self.backuproot) retval = shutil.move(self.filename,backup_path) os.rename(self.filename+".sav",self.filename) os.chmod(self.filename,fstat.st_mode) else: os.rename(self.filename+".sav",self.filename) self.load() def get_tab_stop(self, idx, before=False ): """ return the next tab stop before or after a given offset """ prev = 0 for stop in self.tabs: if stop > idx: if before: return prev else: return stop prev = stop incr = self.tabs[-1]-self.tabs[-2] while stop <= idx: prev = stop stop += incr if before: return prev else: return stop def expand_tabs(self, content ): """ expand tabs in a line """ idx = 0 while idx < len(content): if content[idx] == '\t': stop = self.get_tab_stop(idx) content = content[0:idx] + ' '*(stop-idx) + content[idx+1:] idx += (stop-idx) else: idx += 1 return content class Editor: """ class that implements the text editor, operates on a file abstraction EditFile """ modes = [python_mode,cpp_mode,java_mode,guess_mode] def __init__(self, parent, scr, filename, workfile = None, showname = True, wrap = False ): """ takes parent curses screen we're popped up over, scr our curses window, filename we should edit, optionally an already open EditFile """ if workfile: self.workfile = workfile else: self.workfile = EditFile(filename) self.workfile.change_mgr.add_view(self) self.undo_mgr = self.workfile.getUndoMgr() self.parent = parent self.scr = scr if scr: self.max_y,self.max_x = self.scr.getmaxyx() else: self.max_y = 0 self.max_x = 0 self.line = 0 self.pos = 0 self.vpos = 0 self.left = 0 self.prev_cmd = cmd_names.CMD_NOP self.cmd_id = cmd_names.CMD_NOP self.home_count = 0 self.end_count = 0 self.line_mark = False self.span_mark = False self.rect_mark = False self.search_mark = False self.mark_pos_start = 0 self.mark_line_start = 0 self.last_search = None self.last_search_dir = True self.mode = None self.showname = showname self.wrap = wrap self.wrap_lines = [] self.unwrap_lines = [] self.wrap_modref = -1 self.wrap_width = -1 self.show_cursor = True self.prev_pos = (0,0) self.focus = True self.invalidate_all() curses.raw() curses.meta(1) def __copy__(self): """ override to just copy the editor state and not the underlying file object """ result = Editor(self.parent,self.scr,None,self.workfile,self.showname,self.wrap) result.line = self.line result.pos = self.pos result.vpos = self.vpos result.left = self.left result.prev_cmd = self.prev_cmd result.cmd_id = self.cmd_id result.home_count = self.home_count result.end_count = self.end_count result.line_mark = self.line_mark result.span_mark = self.span_mark result.rect_mark = self.rect_mark result.search_mark = self.search_mark result.mark_pos_start = self.mark_pos_start result.mark_line_start = self.mark_line_start result.last_search = self.last_search result.last_search_dir = self.last_search_dir result.mode = self.mode result.wrap_lines = copy.copy(self.wrap_lines) result.unwrap_lines = copy.copy(self.unwrap_lines) result.wrap_modref = self.wrap_modref result.wrap_width = self.wrap_width result.show_cursor = self.show_cursor result.focus = self.focus result.prev_pos = copy.copy(self.prev_pos) return result def __del__(self): """ if we're closing then clean some stuff up """ # let the mode clean up if it needs to if self.workfile and self.workfile.change_mgr: self.workfile.change_mgr.remove_view(self) if self.mode: self.mode.finish(self) self.mode = None self.workfile = None self.undo_mgr = None def close(self): """ by default it is a no-op but editors overriding this can hook the close to clean things up """ pass def pushUndo(self): """ push an undo action onto the current transaction """ self.undo_mgr.get_transaction().push(self.applyUndo,(self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap)) def applyUndo(self,*args): """ called by undo to unwind one undo action """ ( self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap ) = args self.invalidate_screen() self.invalidate_mark() def undo(self): """ undo the last transaction, actually undoes the open transaction and the prior closed one """ line = self.line left = self.left self.undo_mgr.undo_transaction() # undo the one we're in... probably empty self.undo_mgr.undo_transaction() # undo the previous one... probably not empty if self.line != line or self.left != left: self.invalidate_screen() def setWin(self,win): """ install a new window to render to """ self.scr = win def getModref(self): """ return the current modref of this editor """ return self.workfile.getModref() def getWorkfile(self): """ return the workfile that this editor is attached to """ return self.workfile def getFilename(self): """ return the filename for this editor """ return self.workfile.getFilename() def getUndoMgr(self): """ get the undo manager that we're using """ return self.undo_mgr def isChanged(self): """ returns true if the file we're working on has unsaved changes """ return self.workfile.isChanged() def isLineChanged(self, line, display=True ): """ return true if line is changed for the current revisions """ if self.workfile: if display: return self.workfile.isLineChanged( self, self.filePos(line,0)[0]) else: return self.workfile.isLineChanged( self, line ) else: return True def flushChanges( self ): """ flush change tracking to show we're done updating """ if self.workfile: self.workfile.flushChanges(self) def isMark(self): """ returns true if there is a mark set """ return (self.line_mark or self.span_mark or self.rect_mark or self.search_mark) def getCurrentLine(self,display=False): """ returns the current line in the file """ return self.getContent(self.getLine(display),display) def getPos(self,display=False): """ get the character position in the current line that we're at """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,self.left+self.pos) return r_pos return self.left+self.pos def getLine(self,display=False): """ get the line that we're on in the current file """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,0) return r_line return self.line+self.vpos def filePos(self, line, pos ): """ translate display line, pos to file line, pos """ if self.wrap: if line < len(self.wrap_lines): return (self.wrap_lines[line][0],self.wrap_lines[line][1]+pos) else: return (self.numLines()+(line-len(self.wrap_lines)),pos) else: return (line,pos) def scrPos(self, line, pos ): """ translate file pos to screen pos """ if self.wrap: nlines = len(self.unwrap_lines) if line >= nlines: r_line,r_pos = self.scrPos(self.numLines()-1,self.getLength(self.numLines()-1)-1) return (r_line+(line-self.numLines())+1,pos) sline = self.unwrap_lines[line] while sline < len(self.wrap_lines) and self.wrap_lines[sline][0] == line: if pos >= self.wrap_lines[sline][1] and pos < self.wrap_lines[sline][2]: return (sline,pos-self.wrap_lines[sline][1]) sline = sline + 1 else: return (sline-1,pos - self.wrap_lines[sline-1][1]) else: return (line,pos) def getContent(self, line, pad = 0, trim= False, display=False ): """ get a line from the file """ if self.wrap: if display: orig = "" if line < len(self.wrap_lines): orig = self.workfile.getLine(self.wrap_lines[line][0])[self.wrap_lines[line][1]:self.wrap_lines[line][2]] if trim: orig = orig.rstrip() if pad > len(orig): orig = orig + ' '*(pad-len(orig)) return orig orig = self.workfile.getLine(line,pad,trim) return orig def getLength(self, line, display=False ): """ get the length of a line """ length = 0 if self.wrap and display: if line < len(self.wrap_lines): length = self.workfile.length(self.wrap_lines[line][0]) else: length = self.workfile.length(line) return length def numLines(self,display=False): """ get the number of lines in the editor """ if self.wrap and display: return len(self.wrap_lines) return self.workfile.numLines() def rewrap(self, force = False): """ compute the wrapped line array """ if self.wrap and (force or self.workfile.getModref() != self.wrap_modref or self.wrap_width != self.max_x): self.wrap_modref = self.workfile.getModref() self.wrap_width = self.max_x self.wrap_lines = [] self.unwrap_lines = [] for l in range(0,self.workfile.numLines()): line_len = self.workfile.length(l) start = 0 self.unwrap_lines.append(len(self.wrap_lines)) if not line_len: self.wrap_lines.append((l,0,0)) else: while start < line_len: self.wrap_lines.append((l,start,min(line_len,start+self.wrap_width))) start += self.wrap_width self.invalidate_after_cursor() def addstr(self,row,col,str,attr = curses.A_NORMAL): """ write properly encoded string to screen location """ try: return self.scr.addstr(row,col,codecs.encode(str,"utf-8"),attr) except: return 0 def window_pos(self,line,pos): sc_line,sc_pos = self.scrPos(line,pos) return((sc_line-self.line)+1,sc_pos-self.left) def showcursor(self,state): """ set flag to turn cursor on or off """ old_cursor_state = self.show_cursor self.show_cursor = state return old_cursor_state def setfocus(self,state): """ set this editor to have focus or not """ old_focus_state = self.focus self.focus = state return old_focus_state def draw_cursor(self): """ worker function to draw the current cursor position """ if self.show_cursor: line = self.getLine() pos = self.getPos() if pos < self.getLength(line): cursor_ch = self.getContent(line)[pos] else: cursor_ch = ' ' sc_line,sc_pos = self.window_pos(line,pos) self.addstr(sc_line,sc_pos,cursor_ch,curses.A_REVERSE) def draw_mark(self): """ worker function to draw the marked section of the file """ if not self.isMark(): return (mark_top,mark_left) = self.scrPos(self.mark_line_start,self.mark_pos_start) mark_line_start = mark_top mark_pos_start = mark_left mark_right = self.getPos(True) mark_bottom = self.getLine(True) if (self.rect_mark or self.line_mark or (self.span_mark and mark_top == mark_bottom)) and mark_left > mark_right: mark = mark_left mark_left = mark_right mark_right = mark if mark_top > mark_bottom: mark = mark_bottom mark_bottom = mark_top mark_top = mark if mark_top < self.line: mark_top = self.line if self.span_mark: mark_left = 0 mark_right = mark_right + 1 s_left = mark_left - self.left s_left = max(0,s_left) s_right = mark_right - self.left s_right = min(self.max_x,s_right) s_top = (mark_top - self.line)+1 s_top = max(1,s_top) s_bottom = (mark_bottom - self.line)+1 s_bottom = min(self.max_y-1,s_bottom) mark_left = max(mark_left,self.left) mark_right = min(mark_right,self.left+self.max_x) if self.line_mark: s_right = self.max_x mark_right = self.left+s_right mark_left = max(mark_left,self.left) if s_top == s_bottom: if s_right > s_left: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) elif self.rect_mark: if mark_top < self.line: mark_top = self.line while s_top <= s_bottom: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) s_top += 1 mark_top += 1 elif self.span_mark: cur_line = mark_top while s_top <= s_bottom: if cur_line == mark_top: offset = s_left width = self.max_x-offset self.addstr(s_top, offset, self.getContent(cur_line, self.left+offset+width, True, True)[self.left+offset:self.left+offset+width], curses.A_REVERSE) elif cur_line == mark_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.getPos(True), True, True)[self.left:self.getPos(True)+1], curses.A_REVERSE) else: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1 elif self.line_mark: cur_line = mark_top while s_top <= s_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1 def resize(self): """ resize the editor to fill the window """ if self.scr: self.max_y,self.max_x = self.scr.getmaxyx() self.rewrap() bottom_y = max(min((self.numLines(True)-1)-self.line,(self.max_y-2)),0) if self.vpos > bottom_y: self.vpos = bottom_y right_x = self.max_x-1 if self.pos > right_x: self.left += self.pos-right_x self.pos = right_x self.invalidate_screen() def move(self): """ update the previous cursor position from the current """ self.prev_pos = (self.getLine(),self.getPos()) def prevPos(self): """ get the previous cursor position """ return self.prev_pos def redraw(self): """ redraw the editor as needed """ try: if not self.scr or keymap.is_playback(): return self.max_y,self.max_x = self.scr.getmaxyx() self.scr.keypad(1) if self.workfile.isChanged(): changed = "*" elif self.workfile.isReadOnly(): changed = "R" else: changed = " " if self.mode: changed = changed + " " + self.mode.name() filename = self.workfile.getFilename() if not self.showname: filename = "" status = "%d : %d : %d : %s : %s : %s"%(self.numLines(),self.getLine(),self.getPos(),changed,filename, "REC" if keymap.is_recording() else "PBK" if keymap.is_playback() else " " ) if len(status) < self.max_x: status += (self.max_x-len(status))*' ' if self.focus: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE|curses.A_BOLD) else: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE) # if the mode is rendering then don't do the default rendering as well mode_redraw = False if self.mode: mode_redraw = self.mode.redraw(self) if not mode_redraw: cursor_line,cursor_pos = self.window_pos(*self.prevPos()) y = 1 lidx = self.line while lidx < self.line+(self.max_y-1): try: line_changed = self.isLineChanged(lidx) is_cursor_line = (y == cursor_line) if line_changed or is_cursor_line: l = self.getContent(lidx,self.left+self.max_x,True,True) if line_changed: self.addstr(y,0,l[self.left:self.left+self.max_x]) else: self.addstr(y,cursor_pos,l[self.left+cursor_pos]) except Exception as e: pass y = y + 1 lidx = lidx + 1 self.draw_mark() self.move() self.draw_cursor() if mode_redraw: self.flushChanges() except: raise def insert(self, c ): """ insert a character or string at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert orig = self.getContent(self.getLine()).rstrip() offset = self.getPos() pad = "" if offset > len(orig): pad = " "*(offset - len(orig)) orig = orig[0:offset] + pad + c + orig[offset:] insert_line = self.getLine() self.workfile.replaceLine(insert_line,orig) self.rewrap() self.goto(insert_line,offset+len(c)) def delc(self): """ deletes one character at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return orig = self.getContent(self.getLine()) offset = self.getPos() if offset > len(orig): return elif offset == len(orig): next_idx = self.getLine()+1 if next_idx > self.numLines(): return next = self.getContent(next_idx) orig = orig[0:offset] + next self.workfile.replaceLine(self.getLine(),orig) self.workfile.deleteLine(next_idx) else: orig = orig[0:offset]+orig[offset+1:] self.workfile.replaceLine(self.getLine(),orig) self.rewrap() def backspace(self): """ delete a character at the cursor and move back one character """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return line = self.getLine() pos = self.getPos() if pos: if pos <= self.getLength(line): self.goto(line,pos-1) self.delc() else: self.goto(line,pos-1) elif line: pos = self.getLength(line-1)-1 self.goto(line-1,pos) self.delc() def goto(self,line, pos ): """ goto a line in the file and position the cursor to pos offset in the line """ self.pushUndo() self.invalidate_mark() if line < 0: line = 0 if pos < 0: pos = 0 (line,pos) = self.scrPos(line,pos) if line >= self.line and line <= self.line+(self.max_y-2): self.vpos = line - self.line elif line < self.line: self.line = line self.vpos = 0 self.invalidate_screen() elif line > self.line+(self.max_y-2): self.line = line - (self.max_y-2) self.vpos = (self.max_y-2) self.invalidate_screen() if pos >= self.left and pos < self.left+(self.max_x-1): self.pos = pos - self.left elif pos >= self.left+(self.max_x-1): self.left = pos-(self.max_x-1) self.pos = self.max_x-1 self.invalidate_screen() else: self.left = pos self.pos = 0 self.invalidate_screen() def endln(self): """ go to the end of a line """ self.pushUndo() self.invalidate_mark() orig = self.getContent(self.getLine()) offset = len(orig) self.goto(self.getLine(),offset) def endpg(self): """ go to the end of a page """ self.pushUndo() self.invalidate_mark() ldisp = (self.numLines(True)-1)-self.line self.vpos = min(self.max_y-2,ldisp) def endfile(self): """ go to the end of the file """ self.pushUndo() ldisp = (self.numLines(True)-1)-self.line if ldisp < self.max_y-2: return self.line = (self.numLines(True)-1) - (self.max_y-2) self.vpos = min(self.max_y-2,ldisp) self.invalidate_screen() def end(self): """ once go to end of line, twice end of page, thrice end of file """ self.pushUndo() if self.cmd_id == cmd_names.CMD_END and self.prev_cmd == cmd_names.CMD_END: self.end_count += 1 self.end_count = self.end_count % 3 else: self.end_count = 0 if self.end_count == 0: self.endln() elif self.end_count == 1: self.endpg() self.endln() elif self.end_count == 2: self.endfile() self.endln() def home(self): """ once to to start of line, twice start of page, thrice start of file """ self.pushUndo() self.invalidate_mark() if self.cmd_id == cmd_names.CMD_HOME and self.prev_cmd == cmd_names.CMD_HOME: self.home_count += 1 self.home_count = self.home_count % 3 else: self.home_count = 0 if self.home_count == 0: self.goto(self.getLine(),0) elif self.home_count == 1: self.vpos = 0 elif self.home_count == 2: self.line = 0 self.invalidate_screen() def pageup(self): """ go back one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line - (self.max_y-2) if offset < 0: offset = 0 self.line = offset self.invalidate_screen() def pagedown(self): """ go forward one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line + (self.max_y-2) if offset > self.numLines(True)-1: return self.line = offset ldisp = (self.numLines(True)-1)-self.line if self.vpos > ldisp: self.vpos = ldisp self.invalidate_screen() def cup(self): """ go back one line in the file """ self.pushUndo() self.invalidate_mark() if self.vpos: self.vpos -= 1 elif self.line: self.line -= 1 self.invalidate_screen() self.goto(self.getLine(),self.getPos()) def cdown(self,rept = 1): """ go forward one or rept lines in the file """ self.pushUndo() self.invalidate_mark() while rept: if self.vpos < min((self.numLines(True)-1)-self.line,(self.max_y-2)): self.vpos += 1 elif self.line <= self.numLines(True)-self.max_y: self.line += 1 self.invalidate_screen() rept = rept - 1 self.goto(self.getLine(),self.getPos()) def prev_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos >= len(orig): pos = len(orig)-1 if pos and pos < len(orig): pos -= 1 while pos and orig[pos] == ' ': pos -= 1 while pos and orig[pos-1] != ' ': pos -= 1 elif pos >= len(orig): pos = len(orig) else: pos = 0 self.goto(self.getLine(),pos) def next_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos < len(orig): if orig[pos] == ' ': while pos < len(orig) and orig[pos] == ' ': pos += 1 else: while pos < len(orig) and orig[pos] != ' ': pos += 1 while pos < len(orig) and orig[pos] == ' ': pos += 1 else: pos = len(orig) self.goto(self.getLine(),pos) def cleft(self,rept = 1): """ go back one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if pos >= rept: self.goto(line,pos-rept) return if self.wrap: if line: offset = self.getLength(line-1)-(rept-pos) self.goto(line-1,offset) else: self.goto(line,0) def cright(self,rept = 1): """ go forward one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if self.wrap: llen = self.getLength(line) if pos + rept < llen: self.goto(line,pos+rept) return if line < self.numLines()-1: self.goto(line+1,llen-(pos+rept)) return self.goto(line,llen) else: self.goto(line,pos+rept) def scroll_left(self): """ scroll the page left without moving the current cursor position """ self.pushUndo() if self.left: self.left -= 1 self.invalidate_screen() def scroll_right(self): """ scroll the page right without moving the current cursor position """ self.pushUndo() self.left += 1 self.invalidate_screen() def searchagain(self): """ repeat the previous search if any """ self.pushUndo() self.invalidate_mark() if self.isMark(): if not self.last_search_dir: self.goto(self.mark_line_start,self.mark_pos_start) self.mark_span() if self.last_search: return self.search(self.last_search,self.last_search_dir,True) else: return False def search(self, pattern, down = True, next = True): """ search for a regular expression forward or back if next is set then skip one before matching """ self.pushUndo() self.invalidate_mark() self.last_search = pattern self.last_search_dir = down first_line = self.getLine() line = first_line if down: while line < self.numLines(): content = self.getContent(line) if line == first_line: content = content[self.getPos():] offset = self.getPos() else: offset = 0 match = None try: match = re.search(pattern,content) except: pass if match: if self.isMark(): self.mark_span() self.goto(line,match.start()+offset) self.mark_span() self.goto(line,match.end()+offset-1) self.search_mark = True return True line += 1 else: while line >= 0: content = self.getContent(line) if line == first_line: content = content[:self.getPos()] match = None try: match = re.search(pattern,content) except: pass last_match = None offset = 0 while match: last_match = match last_offset = offset offset += match.end() match = re.search(pattern,content[offset:]) if last_match: if self.isMark(): self.mark_span() self.goto(line,last_match.start()+last_offset) self.mark_span() self.goto(line,last_match.end()+last_offset-1) self.search_mark = True return True line -= 1 return False def invalidate_mark(self): """ touch the marked lines so that they'll redraw when we change the shape of the mark or do a copy or paste """ if self.isMark(): self.workfile.touchLine(self.mark_line_start, self.getLine()) if self.search_mark: self.span_mark = False self.search_mark = False def invalidate_all(self): """ touch all the lines in the file so everything will redraw """ self.workfile.touchLine(0,self.workfile.numLines()) def invalidate_screen(self): """ touch all the lines on the screen so everything will redraw """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(line,line+self.max_y) def invalidate_after_cursor(self): """ touch all the lines from the current position to the end of the screen """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(self.getLine(),line+self.max_y) def has_changes(self): """ return true if there are any pending changes """ return self.workfile.hasChanges(self) def mark_span(self): """ mark a span of characters that can start and end in the middle of a line """ self.pushUndo() self.invalidate_mark() if not self.span_mark: self.span_mark = True self.rect_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.span_mark = False def mark_rect(self): """ mark a rectangular or column selection across lines """ # no column cut in wrapped mode, it doesn't make sense if self.wrap: return self.pushUndo() self.invalidate_mark() if not self.rect_mark: self.rect_mark = True self.span_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.rect_mark = False def mark_lines(self): """ mark whole lines """ self.pushUndo() self.invalidate_mark() if not self.line_mark: self.line_mark = True self.span_mark = False self.rect_mark = False self.mark_pos_start = 0 self.mark_line_start = self.getLine() else: self.line_mark = False def get_marked(self, delete=False, nocopy = False): """ returns marked text as tuple ( cliptype, [list of clipped] ) returns () if no mark """ if not self.isMark(): return () self.pushUndo() if delete: self.invalidate_screen() mark_pos_start = self.mark_pos_start mark_line_start = self.mark_line_start mark_pos_end = self.getPos() mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark elif mark_line_start == mark_line_end and mark_pos_start > mark_pos_end: mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark clip = [] clip_type = clipboard.LINE_CLIP line_idx = mark_line_start if self.line_mark: if not nocopy: clip_type = clipboard.LINE_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx)) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.rewrap() elif self.span_mark: if not nocopy: clip_type = clipboard.SPAN_CLIP if line_idx == mark_line_end: clip.append(self.getContent(line_idx)[mark_pos_start:mark_pos_end+1]) else: clip.append(self.getContent(line_idx)[mark_pos_start:]+'\n') line_idx += 1 while line_idx < mark_line_end: clip.append(self.getContent(line_idx)+'\n') line_idx += 1 clip.append(self.getContent(line_idx)[0:mark_pos_end+1]) if delete: line_idx = mark_line_start if line_idx == mark_line_end: orig = self.getContent(line_idx) orig = orig[0:mark_pos_start] + orig[mark_pos_end+1:] self.workfile.replaceLine(line_idx,orig) self.rewrap() else: first_line = self.getContent(mark_line_start) last_line = self.getContent(mark_line_end) while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.workfile.insertLine(mark_line_start,first_line[0:mark_pos_start] + last_line[mark_pos_end+1:]) self.rewrap() elif self.rect_mark: if not nocopy: clip_type = clipboard.RECT_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx,mark_pos_end,True)[mark_pos_start:mark_pos_end+1]) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: orig = self.getContent(line_idx,mark_pos_end,True) self.workfile.replaceLine(line_idx,orig[0:mark_pos_start]+orig[mark_pos_end+1:]) line_idx += 1 # sync the x clipboard self.transfer_clipboard() if self.line_mark: self.line_mark = False if self.rect_mark: self.rect_mark = False if self.span_mark: self.span_mark = False if delete: self.goto(mark_line_start,mark_pos_start) self.invalidate_screen() return (clip_type, clip) def copy_marked(self,delete=False,nocopy = False): """ copy the marked text to the clipboard, delete== True means cut, nocopy == True will just delete """ if not self.isMark(): return self.pushUndo() cp = self.get_marked(delete,nocopy) if cp and not (delete and nocopy): clipboard.clip_type = cp[0] clipboard.clip = cp[1] def paste(self): """ paste the current clip at the cursor position """ if clipboard.clip: # no column cut or paste when in wrap mode if self.wrap and clipboard.clip_type == clipboard.RECT_CLIP: return self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert if clipboard.clip_type == clipboard.LINE_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: self.workfile.insertLine(target,line) target += 1 self.rewrap() self.goto(target,pos) elif clipboard.clip_type == clipboard.SPAN_CLIP: target = self.getLine() pos = self.getPos() idx = 0 for line in clipboard.clip: orig = self.getContent(target,pos,True) if (not line) or line[-1] == '\n': line = line.rstrip() if not idx: self.workfile.replaceLine(target,orig[0:pos]+line) self.workfile.insertLine(target+1,orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) target += 1 else: self.workfile.insertLine(target,line) self.rewrap() self.goto(target, len(line)) target += 1 else: if not idx: self.workfile.replaceLine(target,orig[0:pos]+line+orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) else: self.workfile.replaceLine(target,line+orig) self.rewrap() self.goto(target, len(line)) idx += 1 elif clipboard.clip_type == clipboard.RECT_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: orig = self.getContent(target,self.getPos(),True) self.workfile.replaceLine(target,orig[0:self.getPos()]+line+orig[self.getPos():]) target += 1 self.rewrap() self.goto(target,pos) def cr(self): """ insert a carriage return, split the current line at cursor """ self.pushUndo() orig = self.getContent(self.getLine(),self.getPos(),True) self.workfile.replaceLine(self.getLine(),orig[0:self.getPos()]) self.workfile.insertLine(self.getLine()+1,orig[self.getPos():]) self.rewrap() self.goto(self.getLine()+1,0) def instab(self, line, pos, move_cursor = True ): """ insert a tab at a line and position """ orig = self.getContent(line,pos,True) stop = self.workfile.get_tab_stop(pos) orig = orig[0:pos] + ' '*(stop-(pos)) + orig[pos:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop) def tab(self): """ tab in the correct distance to the next tab stop """ self.pushUndo() if self.isMark() and self.line_mark: oline = self.getLine() opos = self.getPos() mark_line_start = self.mark_line_start mark_line_end = oline if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.instab( mark_line_start, 0, False ) mark_line_start += 1 self.goto(oline,opos) else: self.instab( self.getLine(), self.getPos() ) def deltab(self, line, pos, move_cursor = True ): """ remove a tab from the line at position provided optionally move the cursor """ orig = self.getContent(line,pos+1,True) idx = pos start = 0 stop = 0 while idx: while idx and orig[idx] != ' ': idx -= 1 start = idx stop = self.workfile.get_tab_stop(idx,True) while idx and idx >= stop: if orig[idx] != ' ': break idx -= 1 else: if start > stop: break if start > stop: orig = orig[0:stop]+orig[start+1:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop) def btab(self): """ remove white space to the previous tab stop, or shift the line back to the previous tab stop """ self.pushUndo() if self.isMark() and self.line_mark: mark_line_start = self.mark_line_start mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.deltab( mark_line_start, self.workfile.get_tab_stop(0), False ) mark_line_start += 1 else: self.deltab( self.getLine(), self.getPos() ) def prmt_goto(self): """ prompt for a line to go to and go there """ self.invalidate_screen() goto_line = prompt(self.parent,"Goto","Enter line number 0-%d :"%(self.numLines()-1),10,name="goto") if goto_line: self.goto(int(goto_line),self.getPos()) def saveas(self): """ open the file dialog and enter or point to a file and then save this buffer to that path """ f = file_dialog.FileDialog(self.parent,"Save file as") choices = f.main() if choices and choices["file"]: self.workfile.save(os.path.join(choices["dir"],choices["file"])) self.undo_mgr.flush_undo() self.invalidate_all() gc.collect() def save(self): """ save the current buffer """ if self.workfile.isModifiedOnDisk(): if not confirm(self.parent, "File has changed on disk, overwrite?"): self.invalidate_screen() self.redraw() return self.workfile.save() self.undo_mgr.flush_undo() self.goto(self.getLine(),self.getPos()) self.invalidate_all() self.redraw() gc.collect() def prmt_search(self,down=True): """ prompt for a search string then search for it and either put up a message that it was not found or position the cursor to the occurrance """ self.invalidate_screen() if down: title = "Search Forward" else: title = "Search Backward" pattern = prompt(self.parent,title,"Pattern: ",-1,name="search") if pattern: if not self.search(pattern,down): message(self.parent,"Search","Pattern not found.") def prmt_replace(self): """ prompt for search pattern and replacement string, then confirm replacment or replace all for the occurrences until no more are found """ (pattern,rep) = replace(self.parent) if pattern and rep: found = self.search(pattern) replace_all = False do_replace = False while found: self.redraw() self.scr.refresh() if not replace_all: answer = confirm_replace(self.parent) self.invalidate_screen() if answer == 1: do_replace = True elif answer == 2: do_replace = False elif answer == 3: replace_all = True elif answer == 4: message(self.parent,"Canceled","Replace canceled.") return if do_replace or replace_all: self.insert(rep) found = self.searchagain() else: message(self.parent,"Replace","Pattern not found.") self.invalidate_screen() def prmt_searchagain(self): """ search again and put up a message if no more are found """ self.invalidate_screen() if not self.searchagain(): if self.isMark(): self.mark_span() message(self.parent,"Search","Pattern not found.") def transfer_clipboard(self, from_xclip = False): """ use xclip to transfer out clipboard to x or vice/versa """ if os.path.exists("/dev/clipboard"): if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for line in open("/dev/clipboard","r",buffering=1,encoding="utf-8"): clipboard.clip.append(line) else: cld = open("/dev/clipboard","w",buffering=0,encoding="utf-8") for line in clipboard.clip: cld.write(line) cld.close() elif os.path.exists("/usr/bin/xclip"): cmd = [ "xclip", ] if from_xclip: cmd += ["-out","-selection","clipboard"] else: cmd += ["-in","-selection","clipboard"] try: proc = subprocess.Popen( cmd, encoding="utf-8", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for l in proc.stdout: clipboard.clip.append(l) else: for l in clipboard.clip: print(l.rstrip(), file=proc.stdin) proc.stdout.close() proc.stdin.close() proc.stderr.close() proc.wait() except: pass def toggle_wrap(self): """ toggle wrapping for this editor """ # don't toggle wrapping while we're marking a rectangle if self.rect_mark: return self.pushUndo() oline = self.getLine() opos = self.getPos() self.wrap = not self.wrap self.rewrap(True) self.invalidate_all() self.goto(oline,opos) def handle(self,ch): """ main character handler dispatches keystrokes to execute editor commands returns characters meant to be processed by containing manager or dialog """ self.prev_cmd = self.cmd_id if isinstance(ch,int): self.cmd_id, ret = keymap.mapkey( self.scr, keymap.keymap_editor, ch ) else: self.cmd_id, ret = keymap.mapseq( keymap.keymap_editor, ch ) if extension_manager.is_extension(self.cmd_id): if not extension_manager.invoke_extension( self.cmd_id, self, ch ): return ret if self.cmd_id == cmd_names.CMD_RETURNKEY: if ret in [keytab.KEYTAB_NOKEY,keytab.KEYTAB_REFRESH,keytab.KEYTAB_RESIZE]: self.cmd_id = self.prev_cmd elif self.cmd_id == cmd_names.CMD_INSERT: self.insert(chr(ret)) ret = keytab.KEYTAB_NOKEY elif self.cmd_id == cmd_names.CMD_MARKSPAN: self.mark_span() elif self.cmd_id == cmd_names.CMD_MARKRECT: self.mark_rect() elif self.cmd_id == cmd_names.CMD_COPYMARKED: self.copy_marked() elif self.cmd_id == cmd_names.CMD_PRMTGOTO: self.prmt_goto() elif self.cmd_id == cmd_names.CMD_BACKSPACE: self.backspace() elif self.cmd_id == cmd_names.CMD_FILENAME: if self.getFilename(): message(self.parent,"Filename",self.getFilename()) elif self.cmd_id == cmd_names.CMD_CUTMARKED: self.copy_marked(True) elif self.cmd_id == cmd_names.CMD_PASTE: self.paste() elif self.cmd_id == cmd_names.CMD_MARKLINES: self.mark_lines() elif self.cmd_id == cmd_names.CMD_CR: self.cr() elif self.cmd_id == cmd_names.CMD_TAB: self.tab() elif self.cmd_id == cmd_names.CMD_SAVE: self.save() elif self.cmd_id == cmd_names.CMD_SAVEAS: self.saveas() elif self.cmd_id == cmd_names.CMD_UNDO: self.undo() elif self.cmd_id == cmd_names.CMD_TOGGLEWRAP: self.toggle_wrap() elif self.cmd_id == cmd_names.CMD_MARKCOPYLINE: if not self.isMark(): self.mark_lines() self.copy_marked() elif self.cmd_id == cmd_names.CMD_MARKCUTLINE: if not self.isMark(): self.mark_lines() self.copy_marked(True) elif self.cmd_id == cmd_names.CMD_BTAB: self.btab() elif self.cmd_id == cmd_names.CMD_PREVWORD: self.prev_word() elif self.cmd_id == cmd_names.CMD_NEXTWORD: self.next_word() elif self.cmd_id == cmd_names.CMD_HOME1: self.pushUndo() self.prev_cmd = cmd_names.CMD_HOME self.cmd_id = cmd_names.CMD_HOME self.home_count = 0 self.home() self.home() self.home() elif self.cmd_id == cmd_names.CMD_END1: self.pushUndo() self.prev_cmd = cmd_names.CMD_END self.cmd_id = cmd_names.CMD_END self.end_count = 0 self.end() self.end() self.end() elif self.cmd_id == cmd_names.CMD_UP: self.cup() elif self.cmd_id == cmd_names.CMD_DOWN: self.cdown() elif self.cmd_id == cmd_names.CMD_LEFT: self.cleft() elif self.cmd_id == cmd_names.CMD_RIGHT: self.cright() elif self.cmd_id == cmd_names.CMD_DELC: self.delc() elif self.cmd_id == cmd_names.CMD_HOME: self.home() elif self.cmd_id == cmd_names.CMD_END: self.end() elif self.cmd_id == cmd_names.CMD_PAGEUP: self.pageup() elif self.cmd_id == cmd_names.CMD_PAGEDOWN: self.pagedown() elif self.cmd_id == cmd_names.CMD_PRMTSEARCH: self.prmt_search() elif self.cmd_id == cmd_names.CMD_PRMTREPLACE: self.prmt_replace() elif self.cmd_id == cmd_names.CMD_TRANSFERCLIPIN: self.transfer_clipboard(False) elif self.cmd_id == cmd_names.CMD_TRANSFERCLIPOUT: self.transfer_clipboard(True) elif self.cmd_id == cmd_names.CMD_PRMTSEARCHBACK: self.prmt_search(False) elif self.cmd_id == cmd_names.CMD_SEARCHAGAIN: self.prmt_searchagain() elif self.cmd_id == cmd_names.CMD_TOGGLERECORD: keymap.toggle_recording() elif self.cmd_id == cmd_names.CMD_PLAYBACK: keymap.start_playback() return ret def main(self,blocking = True, start_ch = None): """ main driver loop for editor, if blocking = False exits on each keystroke to allow embedding, start_ch is a character read externally that hould be processed on startup """ curses.curs_set(0) self.rewrap() self.scr.nodelay(1) self.scr.notimeout(0) self.scr.timeout(0) while (1): if not self.scr: return 27 if not self.mode: for m in Editor.modes: if m.detect_mode(self): self.mode = m self.getWorkfile().set_tabs(m.get_tabs(self)) break else: self.mode = None self.redraw() if start_ch: ch = start_ch start_ch = None else: ch = keymap.getch(self.scr) try: self.undo_mgr.new_transaction() if self.mode: ch = self.mode.handle(self,ch) modref = self.workfile.getModref() ret_seq = self.handle(ch) if self.wrap and modref != self.workfile.getModref(): self.rewrap() if ret_seq or not blocking: return ret_seq except ReadOnlyError as e: message(self.parent,"Read Only File Error","Changes not allowed.") if not blocking: return keytab.KEYTAB_REFRESH class StreamThread: """ Thread to read from a stream blocking as needed adding lines to the owning EditFile object """ def __init__(self, ef, stream ): self.ef = ef self.stream = stream self.thread = None self.read_worker_stop = False def __del__( self ): self.stop_stream() def start_stream( self ): self.thread = threading.Thread(target = self.read_worker) self.thread.start() def wait(self): if self.thread: self.thread.join() def stop_stream( self ): self.read_worker_stop = True if self.thread and self.thread.is_alive(): self.thread.join() if self.stream: self.stream.close() self.stream = None self.thread = None self.read_worker_stop = False def read_worker( self ): pos = 0 lidx = 0 while not self.read_worker_stop: line = self.stream.readline() if not line: break try: self.ef.lines_lock.acquire() self.ef.modref += 1 self.ef.working.seek(0,2) self.ef.working.write(line) line = line.rstrip() self.ef.lines.append(FileLine(self.ef,pos,len(self.ef.expand_tabs(line)))) if self.ef.change_mgr: self.ef.change_mgr.changed(lidx,lidx) pos = self.ef.working.tell() lidx += 1 finally: self.ef.lines_lock.release() time.sleep( 0 ) try: self.ef.lines_lock.acquire() while len(self.ef.lines) and not self.ef.lines[-1].getContent().strip(): del self.ef.lines[-1] if not len(self.ef.lines): self.ef.lines.append(MemLine("")) finally: self.ef.lines_lock.release() self.ef.changed = False self.ef.modref = 0 if self.stream: self.stream.close() self.stream = None self.thread = None self.read_worker_stop = False class StreamFile(EditFile): """ Class reads a stream to the end and writes it to a temp file which is opened and loaded read only, used for capturing the output of shell commands to a read-only editor """ def __init__(self,name,stream,wait=False): """ takes name which is a display name for this stream, stream is the input stream to read """ self.stream = stream self.stream_thread = None self.lines_lock = threading.Lock() self.wait = wait # store the filename self.filename = name # the root of the backup directory self.backuproot = EditFile.default_backuproot # set the default tab stops self.tabs = [ 4, 8 ] # set the changed flag to false self.changed = False # read only flag self.readonly = EditFile.default_readonly # undo manager self.undo_mgr = undo.UndoManager() # change manager self.change_mgr = changes.ChangeManager() # modification reference incremented for each change self.modref = 0 # the file object self.working = None # the lines in this file self.lines = [] # load the file self.load() def __del__(self): """ clean up stream thread and stream """ self.stream_thread = None EditFile.__del__(self) def open(self): """ override of the open method, starts a thread that reads the stream into a tempfile which then becomes the file for the editor """ if self.stream and not self.working: self.working = tempfile.NamedTemporaryFile(mode="w+") self.setReadOnly(True) self.stream_thread = StreamThread(self,self.stream) self.stream_thread.start_stream() if self.wait: self.stream_thread.wait() self.stream_thread = None return else: EditFile.open(self) def load(self): if self.stream and not self.working: self.open() return else: EditFile.load(self) def close(self): """ override of close method, make sure the stream gets closed """ if self.stream_thread: self.stream_thread.stop_stream() self.stream = None self.stream_thread = None elif self.stream: self.stream.close() self.stream = None EditFile.close(self) def save( self, filename = None ): """ save the file, if filename is passed it'll be saved to that filename and reopened """ if filename: if filename == self.filename and self.isReadOnly(): raise ReadOnlyError() try: self.lines_lock.acquire() o = open(filename,"w",buffering=1,encoding="utf-8") for l in self.lines: txt = l.getContent()+'\n' o.write(txt) o.close() finally: self.lines_lock.release() self.close() self.filename = filename self.load() else: if self.isReadOnly(): raise ReadOnlyError() def set_tabs(self, tabs): try: self.lines_lock.acquire() EditFile.set_tabs(self, tabs) finally: self.lines_lock.release() def numLines(self): try: self.lines_lock.acquire() return EditFile.numLines(self) finally: self.lines_lock.release() def length(self,line): try: self.lines_lock.acquire() return EditFile.length(self,line) finally: self.lines_lock.release() def getLine(self, line, pad = 0, trim = False ): try: self.lines_lock.acquire() return EditFile.getLine(self,line,pad,trim) finally: self.lines_lock.release() def getLines(self, line_start = 0, line_end = -1 ): try: self.lines_lock.acquire() return EditFile.getLines(self,line_start,line_end) finally: self.lines_lock.release() class StreamEditor(Editor): """ this is a read only editor that wraps a stream it has a select option for use when embedding in a control to select lines from the stream """ def __init__(self, par, scr, name, stream, select = False, line_re = None, follow = False, wait = False, workfile=None ): """ takes parent curses screen, screen to render to, name for stream, stream to read in, and select to indicate if line selection is requested """ self.select = select self.line_re = line_re self.follow = follow self.wait = wait self.o_nlines = 0 if workfile: self.sfile = workfile else: self.sfile = StreamFile(name,stream,self.wait) Editor.__init__(self, par, scr, self.sfile.getFilename(), self.sfile) def __copy__(self): """ override to just copy the editor state and not the underlying file object """ result = StreamEditor(self.parent,self.scr,None,None,self.select, self.line_re, self.follow, self.wait, self.workfile) result.o_nlines = 0 result.line = self.line result.pos = self.pos result.vpos = self.vpos result.left = self.left result.prev_cmd = self.prev_cmd result.cmd_id = self.cmd_id result.home_count = self.home_count result.end_count = self.end_count result.line_mark = self.line_mark result.span_mark = self.span_mark result.rect_mark = self.rect_mark result.search_mark = self.search_mark result.mark_pos_start = self.mark_pos_start result.mark_line_start = self.mark_line_start result.last_search = self.last_search result.last_search_dir = self.last_search_dir result.mode = self.mode result.wrap_lines = copy.copy(self.wrap_lines) result.unwrap_lines = copy.copy(self.unwrap_lines) result.wrap_modref = self.wrap_modref result.wrap_width = self.wrap_width result.show_cursor = self.show_cursor result.focus = self.focus result.prev_pos = copy.copy(self.prev_pos) return result def __del__(self): """ clean up the StreamFile """ self.sfile = None def close(self): """ close and shut down the stream file """ if self.sfile: self.sfile.close() self.sfile = None def handle(self,ch): """ override normal keystroke handling if in select mode and move about doing selection and return on enter """ if self.follow: nlines = self.numLines() if self.o_nlines != nlines: self.endfile() self.o_nlines = nlines if ch in keymap.keydef_map and keymap.keydef_map[ch][-1] == keytab.KEYTAB_CTRLF: self.follow = not self.follow return keytab.KEYTAB_NOKEY elif not self.select: return Editor.handle(self,ch) o_line = self.getLine() if isinstance(ch,int): ch = keymap.get_keyseq( self.scr, ch ) ret_ch = keytab.KEYTAB_NOKEY direction = 0 if ch in [keytab.KEYTAB_F02,keytab.KEYTAB_F04,keytab.KEYTAB_F10,keytab.KEYTAB_F01,keytab.KEYTAB_RESIZE, keytab.KEYTAB_CR, keytab.KEYTAB_BTAB, keytab.KEYTAB_TAB, keytab.KEYTAB_ESC]: ret_ch = ch elif ch == keytab.KEYTAB_UP: self.cup() direction = -1 elif ch == keytab.KEYTAB_DOWN: self.cdown() direction = 1 elif ch == keytab.KEYTAB_LEFT: self.scroll_left() elif ch == keytab.KEYTAB_RIGHT: self.scroll_right() elif ch == keytab.KEYTAB_BACKSPACE: direction = -1 self.cup() elif ch == keytab.KEYTAB_HOME: self.pushUndo() self.left = 0 self.pos = 0 self.line = 0 self.vpos = 0 direction = 1 elif ch == keytab.KEYTAB_END: self.endfile() direction = -1 elif ch == keytab.KEYTAB_PAGEUP: self.pageup() direction = -1 elif ch == keytab.KEYTAB_PAGEDOWN: self.pagedown() direction = 1 elif ch == keytab.KEYTAB_F05: self.prmt_search() direction = 1 elif ch == keytab.KEYTAB_F17: # shift f5: self.prmt_search(False) direction = -1 elif ch == keytab.KEYTAB_F03: self.prmt_searchagain() if self.last_search_dir: direction = 1 else: direction = -1 if self.line_re and direction: if direction > 0: while True: if re.search(self.line_re, self.getCurrentLine()): self.line = self.getLine() self.vpos = 0 break line = self.getLine() self.cdown() if line == self.getLine(): self.undo_mgr.undo_transaction() break elif direction < 0: while True: if re.search(self.line_re, self.getCurrentLine()): self.line = self.getLine() self.vpos = 0 break line = self.getLine() self.cup() if line == self.getLine(): self.undo_mgr.undo_transaction() break if self.getLine() != o_line or not self.isMark(): if self.isMark(): self.mark_lines() self.mark_lines() return ret_ch class ReadonlyEditor(Editor): """ editor subclass implements read only editor for viewing files """ def __init__(self, par, scr, name, showname = True): """ parent curses screen, screen to render to, filename to open """ self.showname = showname sfile = EditFile(name) sfile.setReadOnly() Editor.__init__(self, par, scr, name, sfile, showname) def getFilename(self): """ override getFilename so we can return None to indicate no file stuff should be done """ if self.showname: return Editor.getFilename(self) else: return None def handle(self,ch): """ handle override to only do read only actions to the file """ o_line = self.getLine() if isinstance(ch,int): ch = keymap.get_keyseq( self.scr, ch ) ret_ch = keytab.KEYTAB_NOKEY if ch in [keytab.KEYTAB_F02,keytab.KEYTAB_F04,keytab.KEYTAB_F10,keytab.KEYTAB_F01,keytab.KEYTAB_RESIZE, keytab.KEYTAB_CR, keytab.KEYTAB_BTAB, keytab.KEYTAB_TAB, keytab.KEYTAB_ESC]: ret_ch = ch elif ch == keytab.KEYTAB_CTRLW: # ctrl-w (toggle wrap in readonly editor) self.toggle_wrap() elif ch == keytab.KEYTAB_UP: self.cup() elif ch == keytab.KEYTAB_DOWN: self.cdown() elif ch == keytab.KEYTAB_LEFT: self.scroll_left() elif ch == keytab.KEYTAB_RIGHT: self.scroll_right() elif ch == keytab.KEYTAB_BACKSPACE: self.cup() elif ch == keytab.KEYTAB_HOME: self.home() elif ch == keytab.KEYTAB_END: self.end() elif ch == keytab.KEYTAB_PAGEUP: self.pageup() elif ch == keytab.KEYTAB_PAGEDOWN: self.pagedown() elif ch == keytab.KEYTAB_F05: self.prmt_search() elif ch == keytab.KEYTAB_F17: # shift f5: self.prmt_search(False) elif ch == keytab.KEYTAB_F03: self.prmt_searchagain() if self.getLine() != o_line or not self.isMark(): if self.isMark(): self.mark_lines() self.mark_lines() return ret_ch def main(stdscr): """ test driver for the editor """ open("test.txt","w").write("""This is line one This is line two \tThis is line three \t\tThis is line four This is line five aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb cccccccccccccccc dddddddddddddddd eeeeeeeeeeeeeeee ffffffffffffffff gggggggggggggggg hhhhhhhhhhhhhhhh iiiiiiiiiiiiiiii jjjjjjjjjjjjjjjj kkkkkkkkkkkkkkkk llllllllllllllll mmmmmmmmmmmmmmmm Aa23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789A Ba23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789B Ca23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789C Da23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789D Ea23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789E Fa23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789F Ga23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789G asdkfjlkjaslkfjj asfdkljsa;dfkljas;dflksajdf;laskdfjas;kfdljas;dlkfjas safkjsf;kljsf askdfj;sa asdkfj as;lkfjs fksadfjs;lkfj asdjdfkljsaf al;slkfdj asdlkfj asdlfkj asldkfj asdf;lkj as;lkdfj as;ldkfj adsflk asdlfkj aslfkj aslfj adflkj alkjasdflk aksdfj asdflj asldkfj asdflkj asldkfj aslkfj aslfdkjalksfjd aslfjd asdlfkj ;askfdj alskdfj asldfkj ksaldfj slkdfj kasdfj asdflkja aljkjjlk asdkfljlaksfjd aslkdjf alskdjf alskdfj aslfkj alkjdfslkj aldkfj alskdfj asldkfj asldfj aslkdfj alskdfj alkdfj aslkdfj aslkdfj""") e1 = Editor(stdscr, curses.newwin(0,0),"test.txt",None,True,True) e1.main() if __name__ == '__main__': curses.wrapper(main)
Module variables
var def_encoding
Functions
def main(
stdscr)
test driver for the editor
def main(stdscr): """ test driver for the editor """ open("test.txt","w").write("""This is line one This is line two \tThis is line three \t\tThis is line four This is line five aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb cccccccccccccccc dddddddddddddddd eeeeeeeeeeeeeeee ffffffffffffffff gggggggggggggggg hhhhhhhhhhhhhhhh iiiiiiiiiiiiiiii jjjjjjjjjjjjjjjj kkkkkkkkkkkkkkkk llllllllllllllll mmmmmmmmmmmmmmmm Aa23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789A Ba23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789B Ca23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789C Da23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789D Ea23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789E Fa23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789F Ga23456789b23456789c23456789d23456789d23456789e23456789f23456789g23456789h23456789G asdkfjlkjaslkfjj asfdkljsa;dfkljas;dflksajdf;laskdfjas;kfdljas;dlkfjas safkjsf;kljsf askdfj;sa asdkfj as;lkfjs fksadfjs;lkfj asdjdfkljsaf al;slkfdj asdlkfj asdlfkj asldkfj asdf;lkj as;lkdfj as;ldkfj adsflk asdlfkj aslfkj aslfj adflkj alkjasdflk aksdfj asdflj asldkfj asdflkj asldkfj aslkfj aslfdkjalksfjd aslfjd asdlfkj ;askfdj alskdfj asldfkj ksaldfj slkdfj kasdfj asdflkja aljkjjlk asdkfljlaksfjd aslkdjf alskdjf alskdfj aslfkj alkjdfslkj aldkfj alskdfj asldkfj asldfj aslkdfj alskdfj alkdfj aslkdfj aslkdfj""") e1 = Editor(stdscr, curses.newwin(0,0),"test.txt",None,True,True) e1.main()
Classes
class EditFile
Object that manages one file that is open for editing, lines are either pointers to lines on disk, or in-memory copies for edited lines
class EditFile: """ Object that manages one file that is open for editing, lines are either pointers to lines on disk, or in-memory copies for edited lines """ default_readonly = False default_backuproot = "~" def __init__(self, filename=None ): """ takes an optional filename to either load or create """ # store the filename self.filename = filename # the root of the backup directory self.backuproot = EditFile.default_backuproot # set the default tab stops self.tabs = [ 4, 8 ] # set the changed flag to false self.changed = False # read only flag self.readonly = EditFile.default_readonly # undo manager self.undo_mgr = undo.UndoManager() # change manager self.change_mgr = changes.ChangeManager() # modification reference incremented for each change self.modref = 0 # the file object self.working = None # the lines in this file self.lines = [] # load the file if filename: self.load() def __copy__(self): """ override copy so that copying manages file handles and intelligently copies the lists """ result = EditFile() result.filename = self.filename result.tabs = self.tabs result.changed = self.changed result.readonly = True result.undo_mgr = copy.copy(self.undo_mgr) result.change_mgr = copy.copy(self.change_mgr) result.modref = self.modref result.lines = [] for l in self.lines: if isinstance(l,MemLine): result.lines.append(copy.deepcopy(l)) elif isinstance(l,FileLine): result.lines.append(FileLine(result,l.pos,l.len)) result.working = None if self.working: result.working = open(self.working.name,"r",buffering=1,encoding="utf-8") return result def __del__(self): """ make sure we close file when we are destroyed """ self.undo_mgr = None self.change_mgr = None self.close() def set_tabs(self, tabs ): """ set the tab stops for this file to something new """ if tabs != self.tabs: self.tabs = tabs for l in self.lines: l.flush() def get_tabs(self): """ return the list of tab stops """ return self.tabs def getWorking(self): """ return the file object """ return self.working def getModref(self): """ modref is a serial number that is incremented for each change to a file, used to detect changes externally """ return self.modref def setUndoMgr(self,undo_mgr): """ sets the undo manager object for this EditFile, undo manager is used to record undo records to enable undo in the editor """ self.undo_mgr = undo_mgr def getUndoMgr(self): """ returns our undo_manager """ return self.undo_mgr def isChanged(self): """ true if there are unsaved changes, false otherwise """ return self.changed def isReadOnly(self): """ true if the file is read only, false otherwise """ return self.readonly def setReadOnly(self,flag = True): """ mark this file as read only """ self.readonly = flag def getFilename(self): """ get the filename for this file """ return self.filename def setFilename(self,filename): """ set the filename for this object """ self.filename = filename def numLines(self): """ get the number of lines in this file """ return len(self.lines) def open(self): """ open the file or create it if it doesn't exist """ abs_name = os.path.abspath(self.filename) abs_path = os.path.dirname(abs_name) if os.path.exists(abs_name): self.working = tempfile.NamedTemporaryFile(mode="w+",buffering=1,encoding="utf-8",prefix="ped_",dir=EditFile.get_backup_dir(self.backuproot)) shutil.copyfile(abs_name,self.working.name) if not self.readonly: self.setReadOnly(not os.access(abs_name,os.W_OK)) elif not self.readonly: self.working = tempfile.NamedTemporaryFile(mode="w+",buffering=1,encoding="utf-8",prefix="ped_",dir=EditFile.get_backup_dir(self.backuproot)) else: raise Exception("File %s does not exist!"%(self.filename)) self.filename = abs_name self.working.seek(0,0) def isModifiedOnDisk(self): """ return true if the file we're editing has been modified since we started """ if os.path.exists(self.filename): disk_stat = os.stat(self.filename) temp_stat = os.stat(self.working.name) return disk_stat.st_mtime > temp_stat.st_mtime else: return False def close(self): """ close the file """ if self.working: self.working.close() self.working = None self.lines = None def load(self): """ open the file and load the lines into the array """ self.open() self.lines = [] pos = 0 lidx = 0 while True: line = self.working.readline() if not line: break line = line.rstrip() lidx = lidx + 1 self.lines.append(FileLine(self,pos,len(self.expand_tabs(line)))) pos = self.working.tell() while len(self.lines) and not self.lines[-1].getContent().strip(): del self.lines[-1] if not len(self.lines): self.lines.append(MemLine("")) self.changed = False self.modref = 0 def hasChanges(self,view): """ return true if there are pending screen updates """ return self.change_mgr.has_changes(view) def isLineChanged(self,view,line): """ return true if a particular line is changed """ if self.change_mgr and line < len(self.lines): return self.change_mgr.is_changed(view,line) else: return True def flushChanges(self,view): """ reset the change tracking for full screen redraw events """ if self.change_mgr: self.change_mgr.flush(view) def _deleteLine(self,line,changed = True): """ delete a line """ if self.undo_mgr: self.undo_mgr.get_transaction().push(self._insertLine,(line,self.lines[line],self.changed)) del self.lines[line] self.changed = changed self.modref += 1 if self.change_mgr: self.change_mgr.changed(line,len(self.lines)) def _insertLine(self,line,lineObj,changed = True): """ insert a line """ if self.undo_mgr: self.undo_mgr.get_transaction().push(self._deleteLine,(line,self.changed)) self.lines.insert(line,lineObj) self.changed = changed self.modref += 1 if self.change_mgr: self.change_mgr.changed(line,len(self.lines)) def _replaceLine(self,line,lineObj,changed = True): """ replace a line """ if self.undo_mgr: self.undo_mgr.get_transaction().push(self._replaceLine,(line,self.lines[line],self.changed)) self.lines[line] = lineObj self.changed = changed self.modref += 1 if self.change_mgr: self.change_mgr.changed(line,line) def _appendLine(self,lineObj,changed = True): """ add a line """ if self.undo_mgr: self.undo_mgr.get_transaction().push(self._deleteLine,(len(self.lines),self.changed)) self.lines.append(lineObj) self.changed = changed self.modref += 1 if self.change_mgr: self.change_mgr.changed(len(self.lines)-1,len(self.lines)-1) def touchLine(self, line_start, line_end): """ touch a line so it will redraw""" if self.change_mgr: self.change_mgr.changed(min(line_start,line_end),max(line_start,line_end)) def length(self, line ): """ return the length of the line """ if line < len(self.lines): return self.lines[line].length() else: return 0 def getLine( self, line, pad = 0, trim = False ): """ get a line """ if line < len(self.lines): orig = self.lines[line].getContent() else: orig = "" if trim: orig = orig.rstrip() if pad > len(orig): orig = orig + ' '*(pad-len(orig)) return self.expand_tabs(orig) def getLines( self, line_start = 0, line_end = -1): """ get a list of a range of lines """ if line_end < 0: line_end = len(self.lines) if line_end > len(self.lines): line_end = len(self.lines) lines = [] while line_start < line_end: lines.append(self.expand_tabs(self.lines[line_start].getContent())) line_start += 1 return lines def deleteLine( self, line ): """ delete a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line < len(self.lines): self._deleteLine(line) def insertLine( self, line, content ): """ insert a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line >= len(self.lines): lidx = len(self.lines) while lidx <= line: self._appendLine(MemLine("")) lidx += 1 self._insertLine(line,MemLine(content)) def replaceLine( self, line, content ): """ replace a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line >= len(self.lines): lidx = len(self.lines) while lidx <= line: self._appendLine(MemLine("")) lidx += 1 self._replaceLine(line, MemLine(content)) @staticmethod def get_backup_dir( base = "~" ): """ get the backup directory, create it if it doesn't exist """ base = os.path.expanduser(base) if not os.path.exists(base): base = os.path.expanduser("~") pedbackup = os.path.join(base,".pedbackup") if not os.path.exists(pedbackup): os.mkdir(pedbackup) return pedbackup @staticmethod def make_backup_dir( filename, base = "~" ): """ make a backup directory under ~/.pedbackup for filename and return it's name """ pedbackup = EditFile.get_backup_dir( base ) (filepath,rest) = os.path.split(os.path.abspath(filename)) for part in filepath.split("/"): if part: pedbackup = os.path.join(pedbackup,part) if not os.path.exists(pedbackup): os.mkdir(pedbackup) return os.path.join(pedbackup,rest) def save( self, filename = None ): """ save the file, if filename is passed it'll be saved to that filename and reopened """ if filename: if filename == self.filename and self.isReadOnly(): raise ReadOnlyError() o = open(filename,"w",buffering=1,encoding="utf-8") for l in self.lines: txt = l.getContent()+'\n' o.write(txt) o.close() self.close() self.filename = filename self.load() else: if self.isReadOnly(): raise ReadOnlyError() if not self.changed: return o = open(self.filename+".sav","w",buffering=1,encoding="utf-8") for l in self.lines: txt = l.getContent()+'\n' o.write(txt) o.close() self.working.close() if os.path.exists(self.filename): fstat = os.stat(self.filename) backup_path = EditFile.make_backup_dir(self.filename,self.backuproot) retval = shutil.move(self.filename,backup_path) os.rename(self.filename+".sav",self.filename) os.chmod(self.filename,fstat.st_mode) else: os.rename(self.filename+".sav",self.filename) self.load() def get_tab_stop(self, idx, before=False ): """ return the next tab stop before or after a given offset """ prev = 0 for stop in self.tabs: if stop > idx: if before: return prev else: return stop prev = stop incr = self.tabs[-1]-self.tabs[-2] while stop <= idx: prev = stop stop += incr if before: return prev else: return stop def expand_tabs(self, content ): """ expand tabs in a line """ idx = 0 while idx < len(content): if content[idx] == '\t': stop = self.get_tab_stop(idx) content = content[0:idx] + ' '*(stop-idx) + content[idx+1:] idx += (stop-idx) else: idx += 1 return content
Ancestors (in MRO)
- EditFile
- builtins.object
Class variables
var default_backuproot
var default_readonly
Static methods
def __init__(
self, filename=None)
takes an optional filename to either load or create
def __init__(self, filename=None ): """ takes an optional filename to either load or create """ # store the filename self.filename = filename # the root of the backup directory self.backuproot = EditFile.default_backuproot # set the default tab stops self.tabs = [ 4, 8 ] # set the changed flag to false self.changed = False # read only flag self.readonly = EditFile.default_readonly # undo manager self.undo_mgr = undo.UndoManager() # change manager self.change_mgr = changes.ChangeManager() # modification reference incremented for each change self.modref = 0 # the file object self.working = None # the lines in this file self.lines = [] # load the file if filename: self.load()
def close(
self)
close the file
def close(self): """ close the file """ if self.working: self.working.close() self.working = None self.lines = None
def deleteLine(
self, line)
delete a line, high level interface
def deleteLine( self, line ): """ delete a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line < len(self.lines): self._deleteLine(line)
def expand_tabs(
self, content)
expand tabs in a line
def expand_tabs(self, content ): """ expand tabs in a line """ idx = 0 while idx < len(content): if content[idx] == '\t': stop = self.get_tab_stop(idx) content = content[0:idx] + ' '*(stop-idx) + content[idx+1:] idx += (stop-idx) else: idx += 1 return content
def flushChanges(
self, view)
reset the change tracking for full screen redraw events
def flushChanges(self,view): """ reset the change tracking for full screen redraw events """ if self.change_mgr: self.change_mgr.flush(view)
def getFilename(
self)
get the filename for this file
def getFilename(self): """ get the filename for this file """ return self.filename
def getLine(
self, line, pad=0, trim=False)
get a line
def getLine( self, line, pad = 0, trim = False ): """ get a line """ if line < len(self.lines): orig = self.lines[line].getContent() else: orig = "" if trim: orig = orig.rstrip() if pad > len(orig): orig = orig + ' '*(pad-len(orig)) return self.expand_tabs(orig)
def getLines(
self, line_start=0, line_end=-1)
get a list of a range of lines
def getLines( self, line_start = 0, line_end = -1): """ get a list of a range of lines """ if line_end < 0: line_end = len(self.lines) if line_end > len(self.lines): line_end = len(self.lines) lines = [] while line_start < line_end: lines.append(self.expand_tabs(self.lines[line_start].getContent())) line_start += 1 return lines
def getModref(
self)
modref is a serial number that is incremented for each change to a file, used to detect changes externally
def getModref(self): """ modref is a serial number that is incremented for each change to a file, used to detect changes externally """ return self.modref
def getUndoMgr(
self)
returns our undo_manager
def getUndoMgr(self): """ returns our undo_manager """ return self.undo_mgr
def getWorking(
self)
return the file object
def getWorking(self): """ return the file object """ return self.working
def get_backup_dir(
base='~')
get the backup directory, create it if it doesn't exist
@staticmethod def get_backup_dir( base = "~" ): """ get the backup directory, create it if it doesn't exist """ base = os.path.expanduser(base) if not os.path.exists(base): base = os.path.expanduser("~") pedbackup = os.path.join(base,".pedbackup") if not os.path.exists(pedbackup): os.mkdir(pedbackup) return pedbackup
def get_tab_stop(
self, idx, before=False)
return the next tab stop before or after a given offset
def get_tab_stop(self, idx, before=False ): """ return the next tab stop before or after a given offset """ prev = 0 for stop in self.tabs: if stop > idx: if before: return prev else: return stop prev = stop incr = self.tabs[-1]-self.tabs[-2] while stop <= idx: prev = stop stop += incr if before: return prev else: return stop
def get_tabs(
self)
return the list of tab stops
def get_tabs(self): """ return the list of tab stops """ return self.tabs
def hasChanges(
self, view)
return true if there are pending screen updates
def hasChanges(self,view): """ return true if there are pending screen updates """ return self.change_mgr.has_changes(view)
def insertLine(
self, line, content)
insert a line, high level interface
def insertLine( self, line, content ): """ insert a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line >= len(self.lines): lidx = len(self.lines) while lidx <= line: self._appendLine(MemLine("")) lidx += 1 self._insertLine(line,MemLine(content))
def isChanged(
self)
true if there are unsaved changes, false otherwise
def isChanged(self): """ true if there are unsaved changes, false otherwise """ return self.changed
def isLineChanged(
self, view, line)
return true if a particular line is changed
def isLineChanged(self,view,line): """ return true if a particular line is changed """ if self.change_mgr and line < len(self.lines): return self.change_mgr.is_changed(view,line) else: return True
def isModifiedOnDisk(
self)
return true if the file we're editing has been modified since we started
def isModifiedOnDisk(self): """ return true if the file we're editing has been modified since we started """ if os.path.exists(self.filename): disk_stat = os.stat(self.filename) temp_stat = os.stat(self.working.name) return disk_stat.st_mtime > temp_stat.st_mtime else: return False
def isReadOnly(
self)
true if the file is read only, false otherwise
def isReadOnly(self): """ true if the file is read only, false otherwise """ return self.readonly
def length(
self, line)
return the length of the line
def length(self, line ): """ return the length of the line """ if line < len(self.lines): return self.lines[line].length() else: return 0
def load(
self)
open the file and load the lines into the array
def load(self): """ open the file and load the lines into the array """ self.open() self.lines = [] pos = 0 lidx = 0 while True: line = self.working.readline() if not line: break line = line.rstrip() lidx = lidx + 1 self.lines.append(FileLine(self,pos,len(self.expand_tabs(line)))) pos = self.working.tell() while len(self.lines) and not self.lines[-1].getContent().strip(): del self.lines[-1] if not len(self.lines): self.lines.append(MemLine("")) self.changed = False self.modref = 0
def make_backup_dir(
filename, base='~')
make a backup directory under ~/.pedbackup for filename and return it's name
@staticmethod def make_backup_dir( filename, base = "~" ): """ make a backup directory under ~/.pedbackup for filename and return it's name """ pedbackup = EditFile.get_backup_dir( base ) (filepath,rest) = os.path.split(os.path.abspath(filename)) for part in filepath.split("/"): if part: pedbackup = os.path.join(pedbackup,part) if not os.path.exists(pedbackup): os.mkdir(pedbackup) return os.path.join(pedbackup,rest)
def numLines(
self)
get the number of lines in this file
def numLines(self): """ get the number of lines in this file """ return len(self.lines)
def open(
self)
open the file or create it if it doesn't exist
def open(self): """ open the file or create it if it doesn't exist """ abs_name = os.path.abspath(self.filename) abs_path = os.path.dirname(abs_name) if os.path.exists(abs_name): self.working = tempfile.NamedTemporaryFile(mode="w+",buffering=1,encoding="utf-8",prefix="ped_",dir=EditFile.get_backup_dir(self.backuproot)) shutil.copyfile(abs_name,self.working.name) if not self.readonly: self.setReadOnly(not os.access(abs_name,os.W_OK)) elif not self.readonly: self.working = tempfile.NamedTemporaryFile(mode="w+",buffering=1,encoding="utf-8",prefix="ped_",dir=EditFile.get_backup_dir(self.backuproot)) else: raise Exception("File %s does not exist!"%(self.filename)) self.filename = abs_name self.working.seek(0,0)
def replaceLine(
self, line, content)
replace a line, high level interface
def replaceLine( self, line, content ): """ replace a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line >= len(self.lines): lidx = len(self.lines) while lidx <= line: self._appendLine(MemLine("")) lidx += 1 self._replaceLine(line, MemLine(content))
def save(
self, filename=None)
save the file, if filename is passed it'll be saved to that filename and reopened
def save( self, filename = None ): """ save the file, if filename is passed it'll be saved to that filename and reopened """ if filename: if filename == self.filename and self.isReadOnly(): raise ReadOnlyError() o = open(filename,"w",buffering=1,encoding="utf-8") for l in self.lines: txt = l.getContent()+'\n' o.write(txt) o.close() self.close() self.filename = filename self.load() else: if self.isReadOnly(): raise ReadOnlyError() if not self.changed: return o = open(self.filename+".sav","w",buffering=1,encoding="utf-8") for l in self.lines: txt = l.getContent()+'\n' o.write(txt) o.close() self.working.close() if os.path.exists(self.filename): fstat = os.stat(self.filename) backup_path = EditFile.make_backup_dir(self.filename,self.backuproot) retval = shutil.move(self.filename,backup_path) os.rename(self.filename+".sav",self.filename) os.chmod(self.filename,fstat.st_mode) else: os.rename(self.filename+".sav",self.filename) self.load()
def setFilename(
self, filename)
set the filename for this object
def setFilename(self,filename): """ set the filename for this object """ self.filename = filename
def setReadOnly(
self, flag=True)
mark this file as read only
def setReadOnly(self,flag = True): """ mark this file as read only """ self.readonly = flag
def setUndoMgr(
self, undo_mgr)
sets the undo manager object for this EditFile, undo manager is used to record undo records to enable undo in the editor
def setUndoMgr(self,undo_mgr): """ sets the undo manager object for this EditFile, undo manager is used to record undo records to enable undo in the editor """ self.undo_mgr = undo_mgr
def set_tabs(
self, tabs)
set the tab stops for this file to something new
def set_tabs(self, tabs ): """ set the tab stops for this file to something new """ if tabs != self.tabs: self.tabs = tabs for l in self.lines: l.flush()
def touchLine(
self, line_start, line_end)
touch a line so it will redraw
def touchLine(self, line_start, line_end): """ touch a line so it will redraw""" if self.change_mgr: self.change_mgr.changed(min(line_start,line_end),max(line_start,line_end))
Instance variables
var backuproot
var change_mgr
var changed
var filename
var lines
var modref
var readonly
var tabs
var undo_mgr
var working
class EditLine
Interface for each editable line in a file, a fly-weight object
class EditLine: """ Interface for each editable line in a file, a fly-weight object """ def __init__(self): """ should initialize any content or references to external objects """ pass def length(self): """ return the length of the line """ pass def flush(self): """ flush cached length if you have one """ pass def getContent(self): """ should return line representing this line in the source file """ pass
Ancestors (in MRO)
- EditLine
- builtins.object
Static methods
def __init__(
self)
should initialize any content or references to external objects
def __init__(self): """ should initialize any content or references to external objects """ pass
def flush(
self)
flush cached length if you have one
def flush(self): """ flush cached length if you have one """ pass
def getContent(
self)
should return line representing this line in the source file
def getContent(self): """ should return line representing this line in the source file """ pass
def length(
self)
return the length of the line
def length(self): """ return the length of the line """ pass
class Editor
class that implements the text editor, operates on a file abstraction EditFile
class Editor: """ class that implements the text editor, operates on a file abstraction EditFile """ modes = [python_mode,cpp_mode,java_mode,guess_mode] def __init__(self, parent, scr, filename, workfile = None, showname = True, wrap = False ): """ takes parent curses screen we're popped up over, scr our curses window, filename we should edit, optionally an already open EditFile """ if workfile: self.workfile = workfile else: self.workfile = EditFile(filename) self.workfile.change_mgr.add_view(self) self.undo_mgr = self.workfile.getUndoMgr() self.parent = parent self.scr = scr if scr: self.max_y,self.max_x = self.scr.getmaxyx() else: self.max_y = 0 self.max_x = 0 self.line = 0 self.pos = 0 self.vpos = 0 self.left = 0 self.prev_cmd = cmd_names.CMD_NOP self.cmd_id = cmd_names.CMD_NOP self.home_count = 0 self.end_count = 0 self.line_mark = False self.span_mark = False self.rect_mark = False self.search_mark = False self.mark_pos_start = 0 self.mark_line_start = 0 self.last_search = None self.last_search_dir = True self.mode = None self.showname = showname self.wrap = wrap self.wrap_lines = [] self.unwrap_lines = [] self.wrap_modref = -1 self.wrap_width = -1 self.show_cursor = True self.prev_pos = (0,0) self.focus = True self.invalidate_all() curses.raw() curses.meta(1) def __copy__(self): """ override to just copy the editor state and not the underlying file object """ result = Editor(self.parent,self.scr,None,self.workfile,self.showname,self.wrap) result.line = self.line result.pos = self.pos result.vpos = self.vpos result.left = self.left result.prev_cmd = self.prev_cmd result.cmd_id = self.cmd_id result.home_count = self.home_count result.end_count = self.end_count result.line_mark = self.line_mark result.span_mark = self.span_mark result.rect_mark = self.rect_mark result.search_mark = self.search_mark result.mark_pos_start = self.mark_pos_start result.mark_line_start = self.mark_line_start result.last_search = self.last_search result.last_search_dir = self.last_search_dir result.mode = self.mode result.wrap_lines = copy.copy(self.wrap_lines) result.unwrap_lines = copy.copy(self.unwrap_lines) result.wrap_modref = self.wrap_modref result.wrap_width = self.wrap_width result.show_cursor = self.show_cursor result.focus = self.focus result.prev_pos = copy.copy(self.prev_pos) return result def __del__(self): """ if we're closing then clean some stuff up """ # let the mode clean up if it needs to if self.workfile and self.workfile.change_mgr: self.workfile.change_mgr.remove_view(self) if self.mode: self.mode.finish(self) self.mode = None self.workfile = None self.undo_mgr = None def close(self): """ by default it is a no-op but editors overriding this can hook the close to clean things up """ pass def pushUndo(self): """ push an undo action onto the current transaction """ self.undo_mgr.get_transaction().push(self.applyUndo,(self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap)) def applyUndo(self,*args): """ called by undo to unwind one undo action """ ( self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap ) = args self.invalidate_screen() self.invalidate_mark() def undo(self): """ undo the last transaction, actually undoes the open transaction and the prior closed one """ line = self.line left = self.left self.undo_mgr.undo_transaction() # undo the one we're in... probably empty self.undo_mgr.undo_transaction() # undo the previous one... probably not empty if self.line != line or self.left != left: self.invalidate_screen() def setWin(self,win): """ install a new window to render to """ self.scr = win def getModref(self): """ return the current modref of this editor """ return self.workfile.getModref() def getWorkfile(self): """ return the workfile that this editor is attached to """ return self.workfile def getFilename(self): """ return the filename for this editor """ return self.workfile.getFilename() def getUndoMgr(self): """ get the undo manager that we're using """ return self.undo_mgr def isChanged(self): """ returns true if the file we're working on has unsaved changes """ return self.workfile.isChanged() def isLineChanged(self, line, display=True ): """ return true if line is changed for the current revisions """ if self.workfile: if display: return self.workfile.isLineChanged( self, self.filePos(line,0)[0]) else: return self.workfile.isLineChanged( self, line ) else: return True def flushChanges( self ): """ flush change tracking to show we're done updating """ if self.workfile: self.workfile.flushChanges(self) def isMark(self): """ returns true if there is a mark set """ return (self.line_mark or self.span_mark or self.rect_mark or self.search_mark) def getCurrentLine(self,display=False): """ returns the current line in the file """ return self.getContent(self.getLine(display),display) def getPos(self,display=False): """ get the character position in the current line that we're at """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,self.left+self.pos) return r_pos return self.left+self.pos def getLine(self,display=False): """ get the line that we're on in the current file """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,0) return r_line return self.line+self.vpos def filePos(self, line, pos ): """ translate display line, pos to file line, pos """ if self.wrap: if line < len(self.wrap_lines): return (self.wrap_lines[line][0],self.wrap_lines[line][1]+pos) else: return (self.numLines()+(line-len(self.wrap_lines)),pos) else: return (line,pos) def scrPos(self, line, pos ): """ translate file pos to screen pos """ if self.wrap: nlines = len(self.unwrap_lines) if line >= nlines: r_line,r_pos = self.scrPos(self.numLines()-1,self.getLength(self.numLines()-1)-1) return (r_line+(line-self.numLines())+1,pos) sline = self.unwrap_lines[line] while sline < len(self.wrap_lines) and self.wrap_lines[sline][0] == line: if pos >= self.wrap_lines[sline][1] and pos < self.wrap_lines[sline][2]: return (sline,pos-self.wrap_lines[sline][1]) sline = sline + 1 else: return (sline-1,pos - self.wrap_lines[sline-1][1]) else: return (line,pos) def getContent(self, line, pad = 0, trim= False, display=False ): """ get a line from the file """ if self.wrap: if display: orig = "" if line < len(self.wrap_lines): orig = self.workfile.getLine(self.wrap_lines[line][0])[self.wrap_lines[line][1]:self.wrap_lines[line][2]] if trim: orig = orig.rstrip() if pad > len(orig): orig = orig + ' '*(pad-len(orig)) return orig orig = self.workfile.getLine(line,pad,trim) return orig def getLength(self, line, display=False ): """ get the length of a line """ length = 0 if self.wrap and display: if line < len(self.wrap_lines): length = self.workfile.length(self.wrap_lines[line][0]) else: length = self.workfile.length(line) return length def numLines(self,display=False): """ get the number of lines in the editor """ if self.wrap and display: return len(self.wrap_lines) return self.workfile.numLines() def rewrap(self, force = False): """ compute the wrapped line array """ if self.wrap and (force or self.workfile.getModref() != self.wrap_modref or self.wrap_width != self.max_x): self.wrap_modref = self.workfile.getModref() self.wrap_width = self.max_x self.wrap_lines = [] self.unwrap_lines = [] for l in range(0,self.workfile.numLines()): line_len = self.workfile.length(l) start = 0 self.unwrap_lines.append(len(self.wrap_lines)) if not line_len: self.wrap_lines.append((l,0,0)) else: while start < line_len: self.wrap_lines.append((l,start,min(line_len,start+self.wrap_width))) start += self.wrap_width self.invalidate_after_cursor() def addstr(self,row,col,str,attr = curses.A_NORMAL): """ write properly encoded string to screen location """ try: return self.scr.addstr(row,col,codecs.encode(str,"utf-8"),attr) except: return 0 def window_pos(self,line,pos): sc_line,sc_pos = self.scrPos(line,pos) return((sc_line-self.line)+1,sc_pos-self.left) def showcursor(self,state): """ set flag to turn cursor on or off """ old_cursor_state = self.show_cursor self.show_cursor = state return old_cursor_state def setfocus(self,state): """ set this editor to have focus or not """ old_focus_state = self.focus self.focus = state return old_focus_state def draw_cursor(self): """ worker function to draw the current cursor position """ if self.show_cursor: line = self.getLine() pos = self.getPos() if pos < self.getLength(line): cursor_ch = self.getContent(line)[pos] else: cursor_ch = ' ' sc_line,sc_pos = self.window_pos(line,pos) self.addstr(sc_line,sc_pos,cursor_ch,curses.A_REVERSE) def draw_mark(self): """ worker function to draw the marked section of the file """ if not self.isMark(): return (mark_top,mark_left) = self.scrPos(self.mark_line_start,self.mark_pos_start) mark_line_start = mark_top mark_pos_start = mark_left mark_right = self.getPos(True) mark_bottom = self.getLine(True) if (self.rect_mark or self.line_mark or (self.span_mark and mark_top == mark_bottom)) and mark_left > mark_right: mark = mark_left mark_left = mark_right mark_right = mark if mark_top > mark_bottom: mark = mark_bottom mark_bottom = mark_top mark_top = mark if mark_top < self.line: mark_top = self.line if self.span_mark: mark_left = 0 mark_right = mark_right + 1 s_left = mark_left - self.left s_left = max(0,s_left) s_right = mark_right - self.left s_right = min(self.max_x,s_right) s_top = (mark_top - self.line)+1 s_top = max(1,s_top) s_bottom = (mark_bottom - self.line)+1 s_bottom = min(self.max_y-1,s_bottom) mark_left = max(mark_left,self.left) mark_right = min(mark_right,self.left+self.max_x) if self.line_mark: s_right = self.max_x mark_right = self.left+s_right mark_left = max(mark_left,self.left) if s_top == s_bottom: if s_right > s_left: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) elif self.rect_mark: if mark_top < self.line: mark_top = self.line while s_top <= s_bottom: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) s_top += 1 mark_top += 1 elif self.span_mark: cur_line = mark_top while s_top <= s_bottom: if cur_line == mark_top: offset = s_left width = self.max_x-offset self.addstr(s_top, offset, self.getContent(cur_line, self.left+offset+width, True, True)[self.left+offset:self.left+offset+width], curses.A_REVERSE) elif cur_line == mark_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.getPos(True), True, True)[self.left:self.getPos(True)+1], curses.A_REVERSE) else: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1 elif self.line_mark: cur_line = mark_top while s_top <= s_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1 def resize(self): """ resize the editor to fill the window """ if self.scr: self.max_y,self.max_x = self.scr.getmaxyx() self.rewrap() bottom_y = max(min((self.numLines(True)-1)-self.line,(self.max_y-2)),0) if self.vpos > bottom_y: self.vpos = bottom_y right_x = self.max_x-1 if self.pos > right_x: self.left += self.pos-right_x self.pos = right_x self.invalidate_screen() def move(self): """ update the previous cursor position from the current """ self.prev_pos = (self.getLine(),self.getPos()) def prevPos(self): """ get the previous cursor position """ return self.prev_pos def redraw(self): """ redraw the editor as needed """ try: if not self.scr or keymap.is_playback(): return self.max_y,self.max_x = self.scr.getmaxyx() self.scr.keypad(1) if self.workfile.isChanged(): changed = "*" elif self.workfile.isReadOnly(): changed = "R" else: changed = " " if self.mode: changed = changed + " " + self.mode.name() filename = self.workfile.getFilename() if not self.showname: filename = "" status = "%d : %d : %d : %s : %s : %s"%(self.numLines(),self.getLine(),self.getPos(),changed,filename, "REC" if keymap.is_recording() else "PBK" if keymap.is_playback() else " " ) if len(status) < self.max_x: status += (self.max_x-len(status))*' ' if self.focus: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE|curses.A_BOLD) else: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE) # if the mode is rendering then don't do the default rendering as well mode_redraw = False if self.mode: mode_redraw = self.mode.redraw(self) if not mode_redraw: cursor_line,cursor_pos = self.window_pos(*self.prevPos()) y = 1 lidx = self.line while lidx < self.line+(self.max_y-1): try: line_changed = self.isLineChanged(lidx) is_cursor_line = (y == cursor_line) if line_changed or is_cursor_line: l = self.getContent(lidx,self.left+self.max_x,True,True) if line_changed: self.addstr(y,0,l[self.left:self.left+self.max_x]) else: self.addstr(y,cursor_pos,l[self.left+cursor_pos]) except Exception as e: pass y = y + 1 lidx = lidx + 1 self.draw_mark() self.move() self.draw_cursor() if mode_redraw: self.flushChanges() except: raise def insert(self, c ): """ insert a character or string at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert orig = self.getContent(self.getLine()).rstrip() offset = self.getPos() pad = "" if offset > len(orig): pad = " "*(offset - len(orig)) orig = orig[0:offset] + pad + c + orig[offset:] insert_line = self.getLine() self.workfile.replaceLine(insert_line,orig) self.rewrap() self.goto(insert_line,offset+len(c)) def delc(self): """ deletes one character at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return orig = self.getContent(self.getLine()) offset = self.getPos() if offset > len(orig): return elif offset == len(orig): next_idx = self.getLine()+1 if next_idx > self.numLines(): return next = self.getContent(next_idx) orig = orig[0:offset] + next self.workfile.replaceLine(self.getLine(),orig) self.workfile.deleteLine(next_idx) else: orig = orig[0:offset]+orig[offset+1:] self.workfile.replaceLine(self.getLine(),orig) self.rewrap() def backspace(self): """ delete a character at the cursor and move back one character """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return line = self.getLine() pos = self.getPos() if pos: if pos <= self.getLength(line): self.goto(line,pos-1) self.delc() else: self.goto(line,pos-1) elif line: pos = self.getLength(line-1)-1 self.goto(line-1,pos) self.delc() def goto(self,line, pos ): """ goto a line in the file and position the cursor to pos offset in the line """ self.pushUndo() self.invalidate_mark() if line < 0: line = 0 if pos < 0: pos = 0 (line,pos) = self.scrPos(line,pos) if line >= self.line and line <= self.line+(self.max_y-2): self.vpos = line - self.line elif line < self.line: self.line = line self.vpos = 0 self.invalidate_screen() elif line > self.line+(self.max_y-2): self.line = line - (self.max_y-2) self.vpos = (self.max_y-2) self.invalidate_screen() if pos >= self.left and pos < self.left+(self.max_x-1): self.pos = pos - self.left elif pos >= self.left+(self.max_x-1): self.left = pos-(self.max_x-1) self.pos = self.max_x-1 self.invalidate_screen() else: self.left = pos self.pos = 0 self.invalidate_screen() def endln(self): """ go to the end of a line """ self.pushUndo() self.invalidate_mark() orig = self.getContent(self.getLine()) offset = len(orig) self.goto(self.getLine(),offset) def endpg(self): """ go to the end of a page """ self.pushUndo() self.invalidate_mark() ldisp = (self.numLines(True)-1)-self.line self.vpos = min(self.max_y-2,ldisp) def endfile(self): """ go to the end of the file """ self.pushUndo() ldisp = (self.numLines(True)-1)-self.line if ldisp < self.max_y-2: return self.line = (self.numLines(True)-1) - (self.max_y-2) self.vpos = min(self.max_y-2,ldisp) self.invalidate_screen() def end(self): """ once go to end of line, twice end of page, thrice end of file """ self.pushUndo() if self.cmd_id == cmd_names.CMD_END and self.prev_cmd == cmd_names.CMD_END: self.end_count += 1 self.end_count = self.end_count % 3 else: self.end_count = 0 if self.end_count == 0: self.endln() elif self.end_count == 1: self.endpg() self.endln() elif self.end_count == 2: self.endfile() self.endln() def home(self): """ once to to start of line, twice start of page, thrice start of file """ self.pushUndo() self.invalidate_mark() if self.cmd_id == cmd_names.CMD_HOME and self.prev_cmd == cmd_names.CMD_HOME: self.home_count += 1 self.home_count = self.home_count % 3 else: self.home_count = 0 if self.home_count == 0: self.goto(self.getLine(),0) elif self.home_count == 1: self.vpos = 0 elif self.home_count == 2: self.line = 0 self.invalidate_screen() def pageup(self): """ go back one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line - (self.max_y-2) if offset < 0: offset = 0 self.line = offset self.invalidate_screen() def pagedown(self): """ go forward one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line + (self.max_y-2) if offset > self.numLines(True)-1: return self.line = offset ldisp = (self.numLines(True)-1)-self.line if self.vpos > ldisp: self.vpos = ldisp self.invalidate_screen() def cup(self): """ go back one line in the file """ self.pushUndo() self.invalidate_mark() if self.vpos: self.vpos -= 1 elif self.line: self.line -= 1 self.invalidate_screen() self.goto(self.getLine(),self.getPos()) def cdown(self,rept = 1): """ go forward one or rept lines in the file """ self.pushUndo() self.invalidate_mark() while rept: if self.vpos < min((self.numLines(True)-1)-self.line,(self.max_y-2)): self.vpos += 1 elif self.line <= self.numLines(True)-self.max_y: self.line += 1 self.invalidate_screen() rept = rept - 1 self.goto(self.getLine(),self.getPos()) def prev_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos >= len(orig): pos = len(orig)-1 if pos and pos < len(orig): pos -= 1 while pos and orig[pos] == ' ': pos -= 1 while pos and orig[pos-1] != ' ': pos -= 1 elif pos >= len(orig): pos = len(orig) else: pos = 0 self.goto(self.getLine(),pos) def next_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos < len(orig): if orig[pos] == ' ': while pos < len(orig) and orig[pos] == ' ': pos += 1 else: while pos < len(orig) and orig[pos] != ' ': pos += 1 while pos < len(orig) and orig[pos] == ' ': pos += 1 else: pos = len(orig) self.goto(self.getLine(),pos) def cleft(self,rept = 1): """ go back one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if pos >= rept: self.goto(line,pos-rept) return if self.wrap: if line: offset = self.getLength(line-1)-(rept-pos) self.goto(line-1,offset) else: self.goto(line,0) def cright(self,rept = 1): """ go forward one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if self.wrap: llen = self.getLength(line) if pos + rept < llen: self.goto(line,pos+rept) return if line < self.numLines()-1: self.goto(line+1,llen-(pos+rept)) return self.goto(line,llen) else: self.goto(line,pos+rept) def scroll_left(self): """ scroll the page left without moving the current cursor position """ self.pushUndo() if self.left: self.left -= 1 self.invalidate_screen() def scroll_right(self): """ scroll the page right without moving the current cursor position """ self.pushUndo() self.left += 1 self.invalidate_screen() def searchagain(self): """ repeat the previous search if any """ self.pushUndo() self.invalidate_mark() if self.isMark(): if not self.last_search_dir: self.goto(self.mark_line_start,self.mark_pos_start) self.mark_span() if self.last_search: return self.search(self.last_search,self.last_search_dir,True) else: return False def search(self, pattern, down = True, next = True): """ search for a regular expression forward or back if next is set then skip one before matching """ self.pushUndo() self.invalidate_mark() self.last_search = pattern self.last_search_dir = down first_line = self.getLine() line = first_line if down: while line < self.numLines(): content = self.getContent(line) if line == first_line: content = content[self.getPos():] offset = self.getPos() else: offset = 0 match = None try: match = re.search(pattern,content) except: pass if match: if self.isMark(): self.mark_span() self.goto(line,match.start()+offset) self.mark_span() self.goto(line,match.end()+offset-1) self.search_mark = True return True line += 1 else: while line >= 0: content = self.getContent(line) if line == first_line: content = content[:self.getPos()] match = None try: match = re.search(pattern,content) except: pass last_match = None offset = 0 while match: last_match = match last_offset = offset offset += match.end() match = re.search(pattern,content[offset:]) if last_match: if self.isMark(): self.mark_span() self.goto(line,last_match.start()+last_offset) self.mark_span() self.goto(line,last_match.end()+last_offset-1) self.search_mark = True return True line -= 1 return False def invalidate_mark(self): """ touch the marked lines so that they'll redraw when we change the shape of the mark or do a copy or paste """ if self.isMark(): self.workfile.touchLine(self.mark_line_start, self.getLine()) if self.search_mark: self.span_mark = False self.search_mark = False def invalidate_all(self): """ touch all the lines in the file so everything will redraw """ self.workfile.touchLine(0,self.workfile.numLines()) def invalidate_screen(self): """ touch all the lines on the screen so everything will redraw """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(line,line+self.max_y) def invalidate_after_cursor(self): """ touch all the lines from the current position to the end of the screen """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(self.getLine(),line+self.max_y) def has_changes(self): """ return true if there are any pending changes """ return self.workfile.hasChanges(self) def mark_span(self): """ mark a span of characters that can start and end in the middle of a line """ self.pushUndo() self.invalidate_mark() if not self.span_mark: self.span_mark = True self.rect_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.span_mark = False def mark_rect(self): """ mark a rectangular or column selection across lines """ # no column cut in wrapped mode, it doesn't make sense if self.wrap: return self.pushUndo() self.invalidate_mark() if not self.rect_mark: self.rect_mark = True self.span_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.rect_mark = False def mark_lines(self): """ mark whole lines """ self.pushUndo() self.invalidate_mark() if not self.line_mark: self.line_mark = True self.span_mark = False self.rect_mark = False self.mark_pos_start = 0 self.mark_line_start = self.getLine() else: self.line_mark = False def get_marked(self, delete=False, nocopy = False): """ returns marked text as tuple ( cliptype, [list of clipped] ) returns () if no mark """ if not self.isMark(): return () self.pushUndo() if delete: self.invalidate_screen() mark_pos_start = self.mark_pos_start mark_line_start = self.mark_line_start mark_pos_end = self.getPos() mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark elif mark_line_start == mark_line_end and mark_pos_start > mark_pos_end: mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark clip = [] clip_type = clipboard.LINE_CLIP line_idx = mark_line_start if self.line_mark: if not nocopy: clip_type = clipboard.LINE_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx)) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.rewrap() elif self.span_mark: if not nocopy: clip_type = clipboard.SPAN_CLIP if line_idx == mark_line_end: clip.append(self.getContent(line_idx)[mark_pos_start:mark_pos_end+1]) else: clip.append(self.getContent(line_idx)[mark_pos_start:]+'\n') line_idx += 1 while line_idx < mark_line_end: clip.append(self.getContent(line_idx)+'\n') line_idx += 1 clip.append(self.getContent(line_idx)[0:mark_pos_end+1]) if delete: line_idx = mark_line_start if line_idx == mark_line_end: orig = self.getContent(line_idx) orig = orig[0:mark_pos_start] + orig[mark_pos_end+1:] self.workfile.replaceLine(line_idx,orig) self.rewrap() else: first_line = self.getContent(mark_line_start) last_line = self.getContent(mark_line_end) while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.workfile.insertLine(mark_line_start,first_line[0:mark_pos_start] + last_line[mark_pos_end+1:]) self.rewrap() elif self.rect_mark: if not nocopy: clip_type = clipboard.RECT_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx,mark_pos_end,True)[mark_pos_start:mark_pos_end+1]) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: orig = self.getContent(line_idx,mark_pos_end,True) self.workfile.replaceLine(line_idx,orig[0:mark_pos_start]+orig[mark_pos_end+1:]) line_idx += 1 # sync the x clipboard self.transfer_clipboard() if self.line_mark: self.line_mark = False if self.rect_mark: self.rect_mark = False if self.span_mark: self.span_mark = False if delete: self.goto(mark_line_start,mark_pos_start) self.invalidate_screen() return (clip_type, clip) def copy_marked(self,delete=False,nocopy = False): """ copy the marked text to the clipboard, delete== True means cut, nocopy == True will just delete """ if not self.isMark(): return self.pushUndo() cp = self.get_marked(delete,nocopy) if cp and not (delete and nocopy): clipboard.clip_type = cp[0] clipboard.clip = cp[1] def paste(self): """ paste the current clip at the cursor position """ if clipboard.clip: # no column cut or paste when in wrap mode if self.wrap and clipboard.clip_type == clipboard.RECT_CLIP: return self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert if clipboard.clip_type == clipboard.LINE_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: self.workfile.insertLine(target,line) target += 1 self.rewrap() self.goto(target,pos) elif clipboard.clip_type == clipboard.SPAN_CLIP: target = self.getLine() pos = self.getPos() idx = 0 for line in clipboard.clip: orig = self.getContent(target,pos,True) if (not line) or line[-1] == '\n': line = line.rstrip() if not idx: self.workfile.replaceLine(target,orig[0:pos]+line) self.workfile.insertLine(target+1,orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) target += 1 else: self.workfile.insertLine(target,line) self.rewrap() self.goto(target, len(line)) target += 1 else: if not idx: self.workfile.replaceLine(target,orig[0:pos]+line+orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) else: self.workfile.replaceLine(target,line+orig) self.rewrap() self.goto(target, len(line)) idx += 1 elif clipboard.clip_type == clipboard.RECT_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: orig = self.getContent(target,self.getPos(),True) self.workfile.replaceLine(target,orig[0:self.getPos()]+line+orig[self.getPos():]) target += 1 self.rewrap() self.goto(target,pos) def cr(self): """ insert a carriage return, split the current line at cursor """ self.pushUndo() orig = self.getContent(self.getLine(),self.getPos(),True) self.workfile.replaceLine(self.getLine(),orig[0:self.getPos()]) self.workfile.insertLine(self.getLine()+1,orig[self.getPos():]) self.rewrap() self.goto(self.getLine()+1,0) def instab(self, line, pos, move_cursor = True ): """ insert a tab at a line and position """ orig = self.getContent(line,pos,True) stop = self.workfile.get_tab_stop(pos) orig = orig[0:pos] + ' '*(stop-(pos)) + orig[pos:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop) def tab(self): """ tab in the correct distance to the next tab stop """ self.pushUndo() if self.isMark() and self.line_mark: oline = self.getLine() opos = self.getPos() mark_line_start = self.mark_line_start mark_line_end = oline if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.instab( mark_line_start, 0, False ) mark_line_start += 1 self.goto(oline,opos) else: self.instab( self.getLine(), self.getPos() ) def deltab(self, line, pos, move_cursor = True ): """ remove a tab from the line at position provided optionally move the cursor """ orig = self.getContent(line,pos+1,True) idx = pos start = 0 stop = 0 while idx: while idx and orig[idx] != ' ': idx -= 1 start = idx stop = self.workfile.get_tab_stop(idx,True) while idx and idx >= stop: if orig[idx] != ' ': break idx -= 1 else: if start > stop: break if start > stop: orig = orig[0:stop]+orig[start+1:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop) def btab(self): """ remove white space to the previous tab stop, or shift the line back to the previous tab stop """ self.pushUndo() if self.isMark() and self.line_mark: mark_line_start = self.mark_line_start mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.deltab( mark_line_start, self.workfile.get_tab_stop(0), False ) mark_line_start += 1 else: self.deltab( self.getLine(), self.getPos() ) def prmt_goto(self): """ prompt for a line to go to and go there """ self.invalidate_screen() goto_line = prompt(self.parent,"Goto","Enter line number 0-%d :"%(self.numLines()-1),10,name="goto") if goto_line: self.goto(int(goto_line),self.getPos()) def saveas(self): """ open the file dialog and enter or point to a file and then save this buffer to that path """ f = file_dialog.FileDialog(self.parent,"Save file as") choices = f.main() if choices and choices["file"]: self.workfile.save(os.path.join(choices["dir"],choices["file"])) self.undo_mgr.flush_undo() self.invalidate_all() gc.collect() def save(self): """ save the current buffer """ if self.workfile.isModifiedOnDisk(): if not confirm(self.parent, "File has changed on disk, overwrite?"): self.invalidate_screen() self.redraw() return self.workfile.save() self.undo_mgr.flush_undo() self.goto(self.getLine(),self.getPos()) self.invalidate_all() self.redraw() gc.collect() def prmt_search(self,down=True): """ prompt for a search string then search for it and either put up a message that it was not found or position the cursor to the occurrance """ self.invalidate_screen() if down: title = "Search Forward" else: title = "Search Backward" pattern = prompt(self.parent,title,"Pattern: ",-1,name="search") if pattern: if not self.search(pattern,down): message(self.parent,"Search","Pattern not found.") def prmt_replace(self): """ prompt for search pattern and replacement string, then confirm replacment or replace all for the occurrences until no more are found """ (pattern,rep) = replace(self.parent) if pattern and rep: found = self.search(pattern) replace_all = False do_replace = False while found: self.redraw() self.scr.refresh() if not replace_all: answer = confirm_replace(self.parent) self.invalidate_screen() if answer == 1: do_replace = True elif answer == 2: do_replace = False elif answer == 3: replace_all = True elif answer == 4: message(self.parent,"Canceled","Replace canceled.") return if do_replace or replace_all: self.insert(rep) found = self.searchagain() else: message(self.parent,"Replace","Pattern not found.") self.invalidate_screen() def prmt_searchagain(self): """ search again and put up a message if no more are found """ self.invalidate_screen() if not self.searchagain(): if self.isMark(): self.mark_span() message(self.parent,"Search","Pattern not found.") def transfer_clipboard(self, from_xclip = False): """ use xclip to transfer out clipboard to x or vice/versa """ if os.path.exists("/dev/clipboard"): if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for line in open("/dev/clipboard","r",buffering=1,encoding="utf-8"): clipboard.clip.append(line) else: cld = open("/dev/clipboard","w",buffering=0,encoding="utf-8") for line in clipboard.clip: cld.write(line) cld.close() elif os.path.exists("/usr/bin/xclip"): cmd = [ "xclip", ] if from_xclip: cmd += ["-out","-selection","clipboard"] else: cmd += ["-in","-selection","clipboard"] try: proc = subprocess.Popen( cmd, encoding="utf-8", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for l in proc.stdout: clipboard.clip.append(l) else: for l in clipboard.clip: print(l.rstrip(), file=proc.stdin) proc.stdout.close() proc.stdin.close() proc.stderr.close() proc.wait() except: pass def toggle_wrap(self): """ toggle wrapping for this editor """ # don't toggle wrapping while we're marking a rectangle if self.rect_mark: return self.pushUndo() oline = self.getLine() opos = self.getPos() self.wrap = not self.wrap self.rewrap(True) self.invalidate_all() self.goto(oline,opos) def handle(self,ch): """ main character handler dispatches keystrokes to execute editor commands returns characters meant to be processed by containing manager or dialog """ self.prev_cmd = self.cmd_id if isinstance(ch,int): self.cmd_id, ret = keymap.mapkey( self.scr, keymap.keymap_editor, ch ) else: self.cmd_id, ret = keymap.mapseq( keymap.keymap_editor, ch ) if extension_manager.is_extension(self.cmd_id): if not extension_manager.invoke_extension( self.cmd_id, self, ch ): return ret if self.cmd_id == cmd_names.CMD_RETURNKEY: if ret in [keytab.KEYTAB_NOKEY,keytab.KEYTAB_REFRESH,keytab.KEYTAB_RESIZE]: self.cmd_id = self.prev_cmd elif self.cmd_id == cmd_names.CMD_INSERT: self.insert(chr(ret)) ret = keytab.KEYTAB_NOKEY elif self.cmd_id == cmd_names.CMD_MARKSPAN: self.mark_span() elif self.cmd_id == cmd_names.CMD_MARKRECT: self.mark_rect() elif self.cmd_id == cmd_names.CMD_COPYMARKED: self.copy_marked() elif self.cmd_id == cmd_names.CMD_PRMTGOTO: self.prmt_goto() elif self.cmd_id == cmd_names.CMD_BACKSPACE: self.backspace() elif self.cmd_id == cmd_names.CMD_FILENAME: if self.getFilename(): message(self.parent,"Filename",self.getFilename()) elif self.cmd_id == cmd_names.CMD_CUTMARKED: self.copy_marked(True) elif self.cmd_id == cmd_names.CMD_PASTE: self.paste() elif self.cmd_id == cmd_names.CMD_MARKLINES: self.mark_lines() elif self.cmd_id == cmd_names.CMD_CR: self.cr() elif self.cmd_id == cmd_names.CMD_TAB: self.tab() elif self.cmd_id == cmd_names.CMD_SAVE: self.save() elif self.cmd_id == cmd_names.CMD_SAVEAS: self.saveas() elif self.cmd_id == cmd_names.CMD_UNDO: self.undo() elif self.cmd_id == cmd_names.CMD_TOGGLEWRAP: self.toggle_wrap() elif self.cmd_id == cmd_names.CMD_MARKCOPYLINE: if not self.isMark(): self.mark_lines() self.copy_marked() elif self.cmd_id == cmd_names.CMD_MARKCUTLINE: if not self.isMark(): self.mark_lines() self.copy_marked(True) elif self.cmd_id == cmd_names.CMD_BTAB: self.btab() elif self.cmd_id == cmd_names.CMD_PREVWORD: self.prev_word() elif self.cmd_id == cmd_names.CMD_NEXTWORD: self.next_word() elif self.cmd_id == cmd_names.CMD_HOME1: self.pushUndo() self.prev_cmd = cmd_names.CMD_HOME self.cmd_id = cmd_names.CMD_HOME self.home_count = 0 self.home() self.home() self.home() elif self.cmd_id == cmd_names.CMD_END1: self.pushUndo() self.prev_cmd = cmd_names.CMD_END self.cmd_id = cmd_names.CMD_END self.end_count = 0 self.end() self.end() self.end() elif self.cmd_id == cmd_names.CMD_UP: self.cup() elif self.cmd_id == cmd_names.CMD_DOWN: self.cdown() elif self.cmd_id == cmd_names.CMD_LEFT: self.cleft() elif self.cmd_id == cmd_names.CMD_RIGHT: self.cright() elif self.cmd_id == cmd_names.CMD_DELC: self.delc() elif self.cmd_id == cmd_names.CMD_HOME: self.home() elif self.cmd_id == cmd_names.CMD_END: self.end() elif self.cmd_id == cmd_names.CMD_PAGEUP: self.pageup() elif self.cmd_id == cmd_names.CMD_PAGEDOWN: self.pagedown() elif self.cmd_id == cmd_names.CMD_PRMTSEARCH: self.prmt_search() elif self.cmd_id == cmd_names.CMD_PRMTREPLACE: self.prmt_replace() elif self.cmd_id == cmd_names.CMD_TRANSFERCLIPIN: self.transfer_clipboard(False) elif self.cmd_id == cmd_names.CMD_TRANSFERCLIPOUT: self.transfer_clipboard(True) elif self.cmd_id == cmd_names.CMD_PRMTSEARCHBACK: self.prmt_search(False) elif self.cmd_id == cmd_names.CMD_SEARCHAGAIN: self.prmt_searchagain() elif self.cmd_id == cmd_names.CMD_TOGGLERECORD: keymap.toggle_recording() elif self.cmd_id == cmd_names.CMD_PLAYBACK: keymap.start_playback() return ret def main(self,blocking = True, start_ch = None): """ main driver loop for editor, if blocking = False exits on each keystroke to allow embedding, start_ch is a character read externally that hould be processed on startup """ curses.curs_set(0) self.rewrap() self.scr.nodelay(1) self.scr.notimeout(0) self.scr.timeout(0) while (1): if not self.scr: return 27 if not self.mode: for m in Editor.modes: if m.detect_mode(self): self.mode = m self.getWorkfile().set_tabs(m.get_tabs(self)) break else: self.mode = None self.redraw() if start_ch: ch = start_ch start_ch = None else: ch = keymap.getch(self.scr) try: self.undo_mgr.new_transaction() if self.mode: ch = self.mode.handle(self,ch) modref = self.workfile.getModref() ret_seq = self.handle(ch) if self.wrap and modref != self.workfile.getModref(): self.rewrap() if ret_seq or not blocking: return ret_seq except ReadOnlyError as e: message(self.parent,"Read Only File Error","Changes not allowed.") if not blocking: return keytab.KEYTAB_REFRESH
Ancestors (in MRO)
- Editor
- builtins.object
Class variables
var modes
Static methods
def __init__(
self, parent, scr, filename, workfile=None, showname=True, wrap=False)
takes parent curses screen we're popped up over, scr our curses window, filename we should edit, optionally an already open EditFile
def __init__(self, parent, scr, filename, workfile = None, showname = True, wrap = False ): """ takes parent curses screen we're popped up over, scr our curses window, filename we should edit, optionally an already open EditFile """ if workfile: self.workfile = workfile else: self.workfile = EditFile(filename) self.workfile.change_mgr.add_view(self) self.undo_mgr = self.workfile.getUndoMgr() self.parent = parent self.scr = scr if scr: self.max_y,self.max_x = self.scr.getmaxyx() else: self.max_y = 0 self.max_x = 0 self.line = 0 self.pos = 0 self.vpos = 0 self.left = 0 self.prev_cmd = cmd_names.CMD_NOP self.cmd_id = cmd_names.CMD_NOP self.home_count = 0 self.end_count = 0 self.line_mark = False self.span_mark = False self.rect_mark = False self.search_mark = False self.mark_pos_start = 0 self.mark_line_start = 0 self.last_search = None self.last_search_dir = True self.mode = None self.showname = showname self.wrap = wrap self.wrap_lines = [] self.unwrap_lines = [] self.wrap_modref = -1 self.wrap_width = -1 self.show_cursor = True self.prev_pos = (0,0) self.focus = True self.invalidate_all() curses.raw() curses.meta(1)
def addstr(
self, row, col, str, attr=0)
write properly encoded string to screen location
def addstr(self,row,col,str,attr = curses.A_NORMAL): """ write properly encoded string to screen location """ try: return self.scr.addstr(row,col,codecs.encode(str,"utf-8"),attr) except: return 0
def applyUndo(
self, *args)
called by undo to unwind one undo action
def applyUndo(self,*args): """ called by undo to unwind one undo action """ ( self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap ) = args self.invalidate_screen() self.invalidate_mark()
def backspace(
self)
delete a character at the cursor and move back one character
def backspace(self): """ delete a character at the cursor and move back one character """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return line = self.getLine() pos = self.getPos() if pos: if pos <= self.getLength(line): self.goto(line,pos-1) self.delc() else: self.goto(line,pos-1) elif line: pos = self.getLength(line-1)-1 self.goto(line-1,pos) self.delc()
def btab(
self)
remove white space to the previous tab stop, or shift the line back to the previous tab stop
def btab(self): """ remove white space to the previous tab stop, or shift the line back to the previous tab stop """ self.pushUndo() if self.isMark() and self.line_mark: mark_line_start = self.mark_line_start mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.deltab( mark_line_start, self.workfile.get_tab_stop(0), False ) mark_line_start += 1 else: self.deltab( self.getLine(), self.getPos() )
def cdown(
self, rept=1)
go forward one or rept lines in the file
def cdown(self,rept = 1): """ go forward one or rept lines in the file """ self.pushUndo() self.invalidate_mark() while rept: if self.vpos < min((self.numLines(True)-1)-self.line,(self.max_y-2)): self.vpos += 1 elif self.line <= self.numLines(True)-self.max_y: self.line += 1 self.invalidate_screen() rept = rept - 1 self.goto(self.getLine(),self.getPos())
def cleft(
self, rept=1)
go back one or rept characters in the current line
def cleft(self,rept = 1): """ go back one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if pos >= rept: self.goto(line,pos-rept) return if self.wrap: if line: offset = self.getLength(line-1)-(rept-pos) self.goto(line-1,offset) else: self.goto(line,0)
def close(
self)
by default it is a no-op but editors overriding this can hook the close to clean things up
def close(self): """ by default it is a no-op but editors overriding this can hook the close to clean things up """ pass
def copy_marked(
self, delete=False, nocopy=False)
copy the marked text to the clipboard, delete== True means cut, nocopy == True will just delete
def copy_marked(self,delete=False,nocopy = False): """ copy the marked text to the clipboard, delete== True means cut, nocopy == True will just delete """ if not self.isMark(): return self.pushUndo() cp = self.get_marked(delete,nocopy) if cp and not (delete and nocopy): clipboard.clip_type = cp[0] clipboard.clip = cp[1]
def cr(
self)
insert a carriage return, split the current line at cursor
def cr(self): """ insert a carriage return, split the current line at cursor """ self.pushUndo() orig = self.getContent(self.getLine(),self.getPos(),True) self.workfile.replaceLine(self.getLine(),orig[0:self.getPos()]) self.workfile.insertLine(self.getLine()+1,orig[self.getPos():]) self.rewrap() self.goto(self.getLine()+1,0)
def cright(
self, rept=1)
go forward one or rept characters in the current line
def cright(self,rept = 1): """ go forward one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if self.wrap: llen = self.getLength(line) if pos + rept < llen: self.goto(line,pos+rept) return if line < self.numLines()-1: self.goto(line+1,llen-(pos+rept)) return self.goto(line,llen) else: self.goto(line,pos+rept)
def cup(
self)
go back one line in the file
def cup(self): """ go back one line in the file """ self.pushUndo() self.invalidate_mark() if self.vpos: self.vpos -= 1 elif self.line: self.line -= 1 self.invalidate_screen() self.goto(self.getLine(),self.getPos())
def delc(
self)
deletes one character at the cursor position
def delc(self): """ deletes one character at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return orig = self.getContent(self.getLine()) offset = self.getPos() if offset > len(orig): return elif offset == len(orig): next_idx = self.getLine()+1 if next_idx > self.numLines(): return next = self.getContent(next_idx) orig = orig[0:offset] + next self.workfile.replaceLine(self.getLine(),orig) self.workfile.deleteLine(next_idx) else: orig = orig[0:offset]+orig[offset+1:] self.workfile.replaceLine(self.getLine(),orig) self.rewrap()
def deltab(
self, line, pos, move_cursor=True)
remove a tab from the line at position provided optionally move the cursor
def deltab(self, line, pos, move_cursor = True ): """ remove a tab from the line at position provided optionally move the cursor """ orig = self.getContent(line,pos+1,True) idx = pos start = 0 stop = 0 while idx: while idx and orig[idx] != ' ': idx -= 1 start = idx stop = self.workfile.get_tab_stop(idx,True) while idx and idx >= stop: if orig[idx] != ' ': break idx -= 1 else: if start > stop: break if start > stop: orig = orig[0:stop]+orig[start+1:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop)
def draw_cursor(
self)
worker function to draw the current cursor position
def draw_cursor(self): """ worker function to draw the current cursor position """ if self.show_cursor: line = self.getLine() pos = self.getPos() if pos < self.getLength(line): cursor_ch = self.getContent(line)[pos] else: cursor_ch = ' ' sc_line,sc_pos = self.window_pos(line,pos) self.addstr(sc_line,sc_pos,cursor_ch,curses.A_REVERSE)
def draw_mark(
self)
worker function to draw the marked section of the file
def draw_mark(self): """ worker function to draw the marked section of the file """ if not self.isMark(): return (mark_top,mark_left) = self.scrPos(self.mark_line_start,self.mark_pos_start) mark_line_start = mark_top mark_pos_start = mark_left mark_right = self.getPos(True) mark_bottom = self.getLine(True) if (self.rect_mark or self.line_mark or (self.span_mark and mark_top == mark_bottom)) and mark_left > mark_right: mark = mark_left mark_left = mark_right mark_right = mark if mark_top > mark_bottom: mark = mark_bottom mark_bottom = mark_top mark_top = mark if mark_top < self.line: mark_top = self.line if self.span_mark: mark_left = 0 mark_right = mark_right + 1 s_left = mark_left - self.left s_left = max(0,s_left) s_right = mark_right - self.left s_right = min(self.max_x,s_right) s_top = (mark_top - self.line)+1 s_top = max(1,s_top) s_bottom = (mark_bottom - self.line)+1 s_bottom = min(self.max_y-1,s_bottom) mark_left = max(mark_left,self.left) mark_right = min(mark_right,self.left+self.max_x) if self.line_mark: s_right = self.max_x mark_right = self.left+s_right mark_left = max(mark_left,self.left) if s_top == s_bottom: if s_right > s_left: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) elif self.rect_mark: if mark_top < self.line: mark_top = self.line while s_top <= s_bottom: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) s_top += 1 mark_top += 1 elif self.span_mark: cur_line = mark_top while s_top <= s_bottom: if cur_line == mark_top: offset = s_left width = self.max_x-offset self.addstr(s_top, offset, self.getContent(cur_line, self.left+offset+width, True, True)[self.left+offset:self.left+offset+width], curses.A_REVERSE) elif cur_line == mark_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.getPos(True), True, True)[self.left:self.getPos(True)+1], curses.A_REVERSE) else: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1 elif self.line_mark: cur_line = mark_top while s_top <= s_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1
def end(
self)
once go to end of line, twice end of page, thrice end of file
def end(self): """ once go to end of line, twice end of page, thrice end of file """ self.pushUndo() if self.cmd_id == cmd_names.CMD_END and self.prev_cmd == cmd_names.CMD_END: self.end_count += 1 self.end_count = self.end_count % 3 else: self.end_count = 0 if self.end_count == 0: self.endln() elif self.end_count == 1: self.endpg() self.endln() elif self.end_count == 2: self.endfile() self.endln()
def endfile(
self)
go to the end of the file
def endfile(self): """ go to the end of the file """ self.pushUndo() ldisp = (self.numLines(True)-1)-self.line if ldisp < self.max_y-2: return self.line = (self.numLines(True)-1) - (self.max_y-2) self.vpos = min(self.max_y-2,ldisp) self.invalidate_screen()
def endln(
self)
go to the end of a line
def endln(self): """ go to the end of a line """ self.pushUndo() self.invalidate_mark() orig = self.getContent(self.getLine()) offset = len(orig) self.goto(self.getLine(),offset)
def endpg(
self)
go to the end of a page
def endpg(self): """ go to the end of a page """ self.pushUndo() self.invalidate_mark() ldisp = (self.numLines(True)-1)-self.line self.vpos = min(self.max_y-2,ldisp)
def filePos(
self, line, pos)
translate display line, pos to file line, pos
def filePos(self, line, pos ): """ translate display line, pos to file line, pos """ if self.wrap: if line < len(self.wrap_lines): return (self.wrap_lines[line][0],self.wrap_lines[line][1]+pos) else: return (self.numLines()+(line-len(self.wrap_lines)),pos) else: return (line,pos)
def flushChanges(
self)
flush change tracking to show we're done updating
def flushChanges( self ): """ flush change tracking to show we're done updating """ if self.workfile: self.workfile.flushChanges(self)
def getContent(
self, line, pad=0, trim=False, display=False)
get a line from the file
def getContent(self, line, pad = 0, trim= False, display=False ): """ get a line from the file """ if self.wrap: if display: orig = "" if line < len(self.wrap_lines): orig = self.workfile.getLine(self.wrap_lines[line][0])[self.wrap_lines[line][1]:self.wrap_lines[line][2]] if trim: orig = orig.rstrip() if pad > len(orig): orig = orig + ' '*(pad-len(orig)) return orig orig = self.workfile.getLine(line,pad,trim) return orig
def getCurrentLine(
self, display=False)
returns the current line in the file
def getCurrentLine(self,display=False): """ returns the current line in the file """ return self.getContent(self.getLine(display),display)
def getFilename(
self)
return the filename for this editor
def getFilename(self): """ return the filename for this editor """ return self.workfile.getFilename()
def getLength(
self, line, display=False)
get the length of a line
def getLength(self, line, display=False ): """ get the length of a line """ length = 0 if self.wrap and display: if line < len(self.wrap_lines): length = self.workfile.length(self.wrap_lines[line][0]) else: length = self.workfile.length(line) return length
def getLine(
self, display=False)
get the line that we're on in the current file
def getLine(self,display=False): """ get the line that we're on in the current file """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,0) return r_line return self.line+self.vpos
def getModref(
self)
return the current modref of this editor
def getModref(self): """ return the current modref of this editor """ return self.workfile.getModref()
def getPos(
self, display=False)
get the character position in the current line that we're at
def getPos(self,display=False): """ get the character position in the current line that we're at """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,self.left+self.pos) return r_pos return self.left+self.pos
def getUndoMgr(
self)
get the undo manager that we're using
def getUndoMgr(self): """ get the undo manager that we're using """ return self.undo_mgr
def getWorkfile(
self)
return the workfile that this editor is attached to
def getWorkfile(self): """ return the workfile that this editor is attached to """ return self.workfile
def get_marked(
self, delete=False, nocopy=False)
returns marked text as tuple ( cliptype, [list of clipped] ) returns () if no mark
def get_marked(self, delete=False, nocopy = False): """ returns marked text as tuple ( cliptype, [list of clipped] ) returns () if no mark """ if not self.isMark(): return () self.pushUndo() if delete: self.invalidate_screen() mark_pos_start = self.mark_pos_start mark_line_start = self.mark_line_start mark_pos_end = self.getPos() mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark elif mark_line_start == mark_line_end and mark_pos_start > mark_pos_end: mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark clip = [] clip_type = clipboard.LINE_CLIP line_idx = mark_line_start if self.line_mark: if not nocopy: clip_type = clipboard.LINE_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx)) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.rewrap() elif self.span_mark: if not nocopy: clip_type = clipboard.SPAN_CLIP if line_idx == mark_line_end: clip.append(self.getContent(line_idx)[mark_pos_start:mark_pos_end+1]) else: clip.append(self.getContent(line_idx)[mark_pos_start:]+'\n') line_idx += 1 while line_idx < mark_line_end: clip.append(self.getContent(line_idx)+'\n') line_idx += 1 clip.append(self.getContent(line_idx)[0:mark_pos_end+1]) if delete: line_idx = mark_line_start if line_idx == mark_line_end: orig = self.getContent(line_idx) orig = orig[0:mark_pos_start] + orig[mark_pos_end+1:] self.workfile.replaceLine(line_idx,orig) self.rewrap() else: first_line = self.getContent(mark_line_start) last_line = self.getContent(mark_line_end) while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.workfile.insertLine(mark_line_start,first_line[0:mark_pos_start] + last_line[mark_pos_end+1:]) self.rewrap() elif self.rect_mark: if not nocopy: clip_type = clipboard.RECT_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx,mark_pos_end,True)[mark_pos_start:mark_pos_end+1]) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: orig = self.getContent(line_idx,mark_pos_end,True) self.workfile.replaceLine(line_idx,orig[0:mark_pos_start]+orig[mark_pos_end+1:]) line_idx += 1 # sync the x clipboard self.transfer_clipboard() if self.line_mark: self.line_mark = False if self.rect_mark: self.rect_mark = False if self.span_mark: self.span_mark = False if delete: self.goto(mark_line_start,mark_pos_start) self.invalidate_screen() return (clip_type, clip)
def goto(
self, line, pos)
goto a line in the file and position the cursor to pos offset in the line
def goto(self,line, pos ): """ goto a line in the file and position the cursor to pos offset in the line """ self.pushUndo() self.invalidate_mark() if line < 0: line = 0 if pos < 0: pos = 0 (line,pos) = self.scrPos(line,pos) if line >= self.line and line <= self.line+(self.max_y-2): self.vpos = line - self.line elif line < self.line: self.line = line self.vpos = 0 self.invalidate_screen() elif line > self.line+(self.max_y-2): self.line = line - (self.max_y-2) self.vpos = (self.max_y-2) self.invalidate_screen() if pos >= self.left and pos < self.left+(self.max_x-1): self.pos = pos - self.left elif pos >= self.left+(self.max_x-1): self.left = pos-(self.max_x-1) self.pos = self.max_x-1 self.invalidate_screen() else: self.left = pos self.pos = 0 self.invalidate_screen()
def handle(
self, ch)
main character handler dispatches keystrokes to execute editor commands returns characters meant to be processed by containing manager or dialog
def handle(self,ch): """ main character handler dispatches keystrokes to execute editor commands returns characters meant to be processed by containing manager or dialog """ self.prev_cmd = self.cmd_id if isinstance(ch,int): self.cmd_id, ret = keymap.mapkey( self.scr, keymap.keymap_editor, ch ) else: self.cmd_id, ret = keymap.mapseq( keymap.keymap_editor, ch ) if extension_manager.is_extension(self.cmd_id): if not extension_manager.invoke_extension( self.cmd_id, self, ch ): return ret if self.cmd_id == cmd_names.CMD_RETURNKEY: if ret in [keytab.KEYTAB_NOKEY,keytab.KEYTAB_REFRESH,keytab.KEYTAB_RESIZE]: self.cmd_id = self.prev_cmd elif self.cmd_id == cmd_names.CMD_INSERT: self.insert(chr(ret)) ret = keytab.KEYTAB_NOKEY elif self.cmd_id == cmd_names.CMD_MARKSPAN: self.mark_span() elif self.cmd_id == cmd_names.CMD_MARKRECT: self.mark_rect() elif self.cmd_id == cmd_names.CMD_COPYMARKED: self.copy_marked() elif self.cmd_id == cmd_names.CMD_PRMTGOTO: self.prmt_goto() elif self.cmd_id == cmd_names.CMD_BACKSPACE: self.backspace() elif self.cmd_id == cmd_names.CMD_FILENAME: if self.getFilename(): message(self.parent,"Filename",self.getFilename()) elif self.cmd_id == cmd_names.CMD_CUTMARKED: self.copy_marked(True) elif self.cmd_id == cmd_names.CMD_PASTE: self.paste() elif self.cmd_id == cmd_names.CMD_MARKLINES: self.mark_lines() elif self.cmd_id == cmd_names.CMD_CR: self.cr() elif self.cmd_id == cmd_names.CMD_TAB: self.tab() elif self.cmd_id == cmd_names.CMD_SAVE: self.save() elif self.cmd_id == cmd_names.CMD_SAVEAS: self.saveas() elif self.cmd_id == cmd_names.CMD_UNDO: self.undo() elif self.cmd_id == cmd_names.CMD_TOGGLEWRAP: self.toggle_wrap() elif self.cmd_id == cmd_names.CMD_MARKCOPYLINE: if not self.isMark(): self.mark_lines() self.copy_marked() elif self.cmd_id == cmd_names.CMD_MARKCUTLINE: if not self.isMark(): self.mark_lines() self.copy_marked(True) elif self.cmd_id == cmd_names.CMD_BTAB: self.btab() elif self.cmd_id == cmd_names.CMD_PREVWORD: self.prev_word() elif self.cmd_id == cmd_names.CMD_NEXTWORD: self.next_word() elif self.cmd_id == cmd_names.CMD_HOME1: self.pushUndo() self.prev_cmd = cmd_names.CMD_HOME self.cmd_id = cmd_names.CMD_HOME self.home_count = 0 self.home() self.home() self.home() elif self.cmd_id == cmd_names.CMD_END1: self.pushUndo() self.prev_cmd = cmd_names.CMD_END self.cmd_id = cmd_names.CMD_END self.end_count = 0 self.end() self.end() self.end() elif self.cmd_id == cmd_names.CMD_UP: self.cup() elif self.cmd_id == cmd_names.CMD_DOWN: self.cdown() elif self.cmd_id == cmd_names.CMD_LEFT: self.cleft() elif self.cmd_id == cmd_names.CMD_RIGHT: self.cright() elif self.cmd_id == cmd_names.CMD_DELC: self.delc() elif self.cmd_id == cmd_names.CMD_HOME: self.home() elif self.cmd_id == cmd_names.CMD_END: self.end() elif self.cmd_id == cmd_names.CMD_PAGEUP: self.pageup() elif self.cmd_id == cmd_names.CMD_PAGEDOWN: self.pagedown() elif self.cmd_id == cmd_names.CMD_PRMTSEARCH: self.prmt_search() elif self.cmd_id == cmd_names.CMD_PRMTREPLACE: self.prmt_replace() elif self.cmd_id == cmd_names.CMD_TRANSFERCLIPIN: self.transfer_clipboard(False) elif self.cmd_id == cmd_names.CMD_TRANSFERCLIPOUT: self.transfer_clipboard(True) elif self.cmd_id == cmd_names.CMD_PRMTSEARCHBACK: self.prmt_search(False) elif self.cmd_id == cmd_names.CMD_SEARCHAGAIN: self.prmt_searchagain() elif self.cmd_id == cmd_names.CMD_TOGGLERECORD: keymap.toggle_recording() elif self.cmd_id == cmd_names.CMD_PLAYBACK: keymap.start_playback() return ret
def has_changes(
self)
return true if there are any pending changes
def has_changes(self): """ return true if there are any pending changes """ return self.workfile.hasChanges(self)
def home(
self)
once to to start of line, twice start of page, thrice start of file
def home(self): """ once to to start of line, twice start of page, thrice start of file """ self.pushUndo() self.invalidate_mark() if self.cmd_id == cmd_names.CMD_HOME and self.prev_cmd == cmd_names.CMD_HOME: self.home_count += 1 self.home_count = self.home_count % 3 else: self.home_count = 0 if self.home_count == 0: self.goto(self.getLine(),0) elif self.home_count == 1: self.vpos = 0 elif self.home_count == 2: self.line = 0 self.invalidate_screen()
def insert(
self, c)
insert a character or string at the cursor position
def insert(self, c ): """ insert a character or string at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert orig = self.getContent(self.getLine()).rstrip() offset = self.getPos() pad = "" if offset > len(orig): pad = " "*(offset - len(orig)) orig = orig[0:offset] + pad + c + orig[offset:] insert_line = self.getLine() self.workfile.replaceLine(insert_line,orig) self.rewrap() self.goto(insert_line,offset+len(c))
def instab(
self, line, pos, move_cursor=True)
insert a tab at a line and position
def instab(self, line, pos, move_cursor = True ): """ insert a tab at a line and position """ orig = self.getContent(line,pos,True) stop = self.workfile.get_tab_stop(pos) orig = orig[0:pos] + ' '*(stop-(pos)) + orig[pos:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop)
def invalidate_after_cursor(
self)
touch all the lines from the current position to the end of the screen
def invalidate_after_cursor(self): """ touch all the lines from the current position to the end of the screen """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(self.getLine(),line+self.max_y)
def invalidate_all(
self)
touch all the lines in the file so everything will redraw
def invalidate_all(self): """ touch all the lines in the file so everything will redraw """ self.workfile.touchLine(0,self.workfile.numLines())
def invalidate_mark(
self)
touch the marked lines so that they'll redraw when we change the shape of the mark or do a copy or paste
def invalidate_mark(self): """ touch the marked lines so that they'll redraw when we change the shape of the mark or do a copy or paste """ if self.isMark(): self.workfile.touchLine(self.mark_line_start, self.getLine()) if self.search_mark: self.span_mark = False self.search_mark = False
def invalidate_screen(
self)
touch all the lines on the screen so everything will redraw
def invalidate_screen(self): """ touch all the lines on the screen so everything will redraw """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(line,line+self.max_y)
def isChanged(
self)
returns true if the file we're working on has unsaved changes
def isChanged(self): """ returns true if the file we're working on has unsaved changes """ return self.workfile.isChanged()
def isLineChanged(
self, line, display=True)
return true if line is changed for the current revisions
def isLineChanged(self, line, display=True ): """ return true if line is changed for the current revisions """ if self.workfile: if display: return self.workfile.isLineChanged( self, self.filePos(line,0)[0]) else: return self.workfile.isLineChanged( self, line ) else: return True
def isMark(
self)
returns true if there is a mark set
def isMark(self): """ returns true if there is a mark set """ return (self.line_mark or self.span_mark or self.rect_mark or self.search_mark)
def main(
self, blocking=True, start_ch=None)
main driver loop for editor, if blocking = False exits on each keystroke to allow embedding, start_ch is a character read externally that hould be processed on startup
def main(self,blocking = True, start_ch = None): """ main driver loop for editor, if blocking = False exits on each keystroke to allow embedding, start_ch is a character read externally that hould be processed on startup """ curses.curs_set(0) self.rewrap() self.scr.nodelay(1) self.scr.notimeout(0) self.scr.timeout(0) while (1): if not self.scr: return 27 if not self.mode: for m in Editor.modes: if m.detect_mode(self): self.mode = m self.getWorkfile().set_tabs(m.get_tabs(self)) break else: self.mode = None self.redraw() if start_ch: ch = start_ch start_ch = None else: ch = keymap.getch(self.scr) try: self.undo_mgr.new_transaction() if self.mode: ch = self.mode.handle(self,ch) modref = self.workfile.getModref() ret_seq = self.handle(ch) if self.wrap and modref != self.workfile.getModref(): self.rewrap() if ret_seq or not blocking: return ret_seq except ReadOnlyError as e: message(self.parent,"Read Only File Error","Changes not allowed.") if not blocking: return keytab.KEYTAB_REFRESH
def mark_lines(
self)
mark whole lines
def mark_lines(self): """ mark whole lines """ self.pushUndo() self.invalidate_mark() if not self.line_mark: self.line_mark = True self.span_mark = False self.rect_mark = False self.mark_pos_start = 0 self.mark_line_start = self.getLine() else: self.line_mark = False
def mark_rect(
self)
mark a rectangular or column selection across lines
def mark_rect(self): """ mark a rectangular or column selection across lines """ # no column cut in wrapped mode, it doesn't make sense if self.wrap: return self.pushUndo() self.invalidate_mark() if not self.rect_mark: self.rect_mark = True self.span_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.rect_mark = False
def mark_span(
self)
mark a span of characters that can start and end in the middle of a line
def mark_span(self): """ mark a span of characters that can start and end in the middle of a line """ self.pushUndo() self.invalidate_mark() if not self.span_mark: self.span_mark = True self.rect_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.span_mark = False
def move(
self)
update the previous cursor position from the current
def move(self): """ update the previous cursor position from the current """ self.prev_pos = (self.getLine(),self.getPos())
def next_word(
self)
scan left until you get to the previous word
def next_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos < len(orig): if orig[pos] == ' ': while pos < len(orig) and orig[pos] == ' ': pos += 1 else: while pos < len(orig) and orig[pos] != ' ': pos += 1 while pos < len(orig) and orig[pos] == ' ': pos += 1 else: pos = len(orig) self.goto(self.getLine(),pos)
def numLines(
self, display=False)
get the number of lines in the editor
def numLines(self,display=False): """ get the number of lines in the editor """ if self.wrap and display: return len(self.wrap_lines) return self.workfile.numLines()
def pagedown(
self)
go forward one page in the file
def pagedown(self): """ go forward one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line + (self.max_y-2) if offset > self.numLines(True)-1: return self.line = offset ldisp = (self.numLines(True)-1)-self.line if self.vpos > ldisp: self.vpos = ldisp self.invalidate_screen()
def pageup(
self)
go back one page in the file
def pageup(self): """ go back one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line - (self.max_y-2) if offset < 0: offset = 0 self.line = offset self.invalidate_screen()
def paste(
self)
paste the current clip at the cursor position
def paste(self): """ paste the current clip at the cursor position """ if clipboard.clip: # no column cut or paste when in wrap mode if self.wrap and clipboard.clip_type == clipboard.RECT_CLIP: return self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert if clipboard.clip_type == clipboard.LINE_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: self.workfile.insertLine(target,line) target += 1 self.rewrap() self.goto(target,pos) elif clipboard.clip_type == clipboard.SPAN_CLIP: target = self.getLine() pos = self.getPos() idx = 0 for line in clipboard.clip: orig = self.getContent(target,pos,True) if (not line) or line[-1] == '\n': line = line.rstrip() if not idx: self.workfile.replaceLine(target,orig[0:pos]+line) self.workfile.insertLine(target+1,orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) target += 1 else: self.workfile.insertLine(target,line) self.rewrap() self.goto(target, len(line)) target += 1 else: if not idx: self.workfile.replaceLine(target,orig[0:pos]+line+orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) else: self.workfile.replaceLine(target,line+orig) self.rewrap() self.goto(target, len(line)) idx += 1 elif clipboard.clip_type == clipboard.RECT_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: orig = self.getContent(target,self.getPos(),True) self.workfile.replaceLine(target,orig[0:self.getPos()]+line+orig[self.getPos():]) target += 1 self.rewrap() self.goto(target,pos)
def prevPos(
self)
get the previous cursor position
def prevPos(self): """ get the previous cursor position """ return self.prev_pos
def prev_word(
self)
scan left until you get to the previous word
def prev_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos >= len(orig): pos = len(orig)-1 if pos and pos < len(orig): pos -= 1 while pos and orig[pos] == ' ': pos -= 1 while pos and orig[pos-1] != ' ': pos -= 1 elif pos >= len(orig): pos = len(orig) else: pos = 0 self.goto(self.getLine(),pos)
def prmt_goto(
self)
prompt for a line to go to and go there
def prmt_goto(self): """ prompt for a line to go to and go there """ self.invalidate_screen() goto_line = prompt(self.parent,"Goto","Enter line number 0-%d :"%(self.numLines()-1),10,name="goto") if goto_line: self.goto(int(goto_line),self.getPos())
def prmt_replace(
self)
prompt for search pattern and replacement string, then confirm replacment or replace all for the occurrences until no more are found
def prmt_replace(self): """ prompt for search pattern and replacement string, then confirm replacment or replace all for the occurrences until no more are found """ (pattern,rep) = replace(self.parent) if pattern and rep: found = self.search(pattern) replace_all = False do_replace = False while found: self.redraw() self.scr.refresh() if not replace_all: answer = confirm_replace(self.parent) self.invalidate_screen() if answer == 1: do_replace = True elif answer == 2: do_replace = False elif answer == 3: replace_all = True elif answer == 4: message(self.parent,"Canceled","Replace canceled.") return if do_replace or replace_all: self.insert(rep) found = self.searchagain() else: message(self.parent,"Replace","Pattern not found.") self.invalidate_screen()
def prmt_search(
self, down=True)
prompt for a search string then search for it and either put up a message that it was not found or position the cursor to the occurrance
def prmt_search(self,down=True): """ prompt for a search string then search for it and either put up a message that it was not found or position the cursor to the occurrance """ self.invalidate_screen() if down: title = "Search Forward" else: title = "Search Backward" pattern = prompt(self.parent,title,"Pattern: ",-1,name="search") if pattern: if not self.search(pattern,down): message(self.parent,"Search","Pattern not found.")
def prmt_searchagain(
self)
search again and put up a message if no more are found
def prmt_searchagain(self): """ search again and put up a message if no more are found """ self.invalidate_screen() if not self.searchagain(): if self.isMark(): self.mark_span() message(self.parent,"Search","Pattern not found.")
def pushUndo(
self)
push an undo action onto the current transaction
def pushUndo(self): """ push an undo action onto the current transaction """ self.undo_mgr.get_transaction().push(self.applyUndo,(self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap))
def redraw(
self)
redraw the editor as needed
def redraw(self): """ redraw the editor as needed """ try: if not self.scr or keymap.is_playback(): return self.max_y,self.max_x = self.scr.getmaxyx() self.scr.keypad(1) if self.workfile.isChanged(): changed = "*" elif self.workfile.isReadOnly(): changed = "R" else: changed = " " if self.mode: changed = changed + " " + self.mode.name() filename = self.workfile.getFilename() if not self.showname: filename = "" status = "%d : %d : %d : %s : %s : %s"%(self.numLines(),self.getLine(),self.getPos(),changed,filename, "REC" if keymap.is_recording() else "PBK" if keymap.is_playback() else " " ) if len(status) < self.max_x: status += (self.max_x-len(status))*' ' if self.focus: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE|curses.A_BOLD) else: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE) # if the mode is rendering then don't do the default rendering as well mode_redraw = False if self.mode: mode_redraw = self.mode.redraw(self) if not mode_redraw: cursor_line,cursor_pos = self.window_pos(*self.prevPos()) y = 1 lidx = self.line while lidx < self.line+(self.max_y-1): try: line_changed = self.isLineChanged(lidx) is_cursor_line = (y == cursor_line) if line_changed or is_cursor_line: l = self.getContent(lidx,self.left+self.max_x,True,True) if line_changed: self.addstr(y,0,l[self.left:self.left+self.max_x]) else: self.addstr(y,cursor_pos,l[self.left+cursor_pos]) except Exception as e: pass y = y + 1 lidx = lidx + 1 self.draw_mark() self.move() self.draw_cursor() if mode_redraw: self.flushChanges() except: raise
def resize(
self)
resize the editor to fill the window
def resize(self): """ resize the editor to fill the window """ if self.scr: self.max_y,self.max_x = self.scr.getmaxyx() self.rewrap() bottom_y = max(min((self.numLines(True)-1)-self.line,(self.max_y-2)),0) if self.vpos > bottom_y: self.vpos = bottom_y right_x = self.max_x-1 if self.pos > right_x: self.left += self.pos-right_x self.pos = right_x self.invalidate_screen()
def rewrap(
self, force=False)
compute the wrapped line array
def rewrap(self, force = False): """ compute the wrapped line array """ if self.wrap and (force or self.workfile.getModref() != self.wrap_modref or self.wrap_width != self.max_x): self.wrap_modref = self.workfile.getModref() self.wrap_width = self.max_x self.wrap_lines = [] self.unwrap_lines = [] for l in range(0,self.workfile.numLines()): line_len = self.workfile.length(l) start = 0 self.unwrap_lines.append(len(self.wrap_lines)) if not line_len: self.wrap_lines.append((l,0,0)) else: while start < line_len: self.wrap_lines.append((l,start,min(line_len,start+self.wrap_width))) start += self.wrap_width self.invalidate_after_cursor()
def save(
self)
save the current buffer
def save(self): """ save the current buffer """ if self.workfile.isModifiedOnDisk(): if not confirm(self.parent, "File has changed on disk, overwrite?"): self.invalidate_screen() self.redraw() return self.workfile.save() self.undo_mgr.flush_undo() self.goto(self.getLine(),self.getPos()) self.invalidate_all() self.redraw() gc.collect()
def saveas(
self)
open the file dialog and enter or point to a file and then save this buffer to that path
def saveas(self): """ open the file dialog and enter or point to a file and then save this buffer to that path """ f = file_dialog.FileDialog(self.parent,"Save file as") choices = f.main() if choices and choices["file"]: self.workfile.save(os.path.join(choices["dir"],choices["file"])) self.undo_mgr.flush_undo() self.invalidate_all() gc.collect()
def scrPos(
self, line, pos)
translate file pos to screen pos
def scrPos(self, line, pos ): """ translate file pos to screen pos """ if self.wrap: nlines = len(self.unwrap_lines) if line >= nlines: r_line,r_pos = self.scrPos(self.numLines()-1,self.getLength(self.numLines()-1)-1) return (r_line+(line-self.numLines())+1,pos) sline = self.unwrap_lines[line] while sline < len(self.wrap_lines) and self.wrap_lines[sline][0] == line: if pos >= self.wrap_lines[sline][1] and pos < self.wrap_lines[sline][2]: return (sline,pos-self.wrap_lines[sline][1]) sline = sline + 1 else: return (sline-1,pos - self.wrap_lines[sline-1][1]) else: return (line,pos)
def scroll_left(
self)
scroll the page left without moving the current cursor position
def scroll_left(self): """ scroll the page left without moving the current cursor position """ self.pushUndo() if self.left: self.left -= 1 self.invalidate_screen()
def scroll_right(
self)
scroll the page right without moving the current cursor position
def scroll_right(self): """ scroll the page right without moving the current cursor position """ self.pushUndo() self.left += 1 self.invalidate_screen()
def search(
self, pattern, down=True, next=True)
search for a regular expression forward or back if next is set then skip one before matching
def search(self, pattern, down = True, next = True): """ search for a regular expression forward or back if next is set then skip one before matching """ self.pushUndo() self.invalidate_mark() self.last_search = pattern self.last_search_dir = down first_line = self.getLine() line = first_line if down: while line < self.numLines(): content = self.getContent(line) if line == first_line: content = content[self.getPos():] offset = self.getPos() else: offset = 0 match = None try: match = re.search(pattern,content) except: pass if match: if self.isMark(): self.mark_span() self.goto(line,match.start()+offset) self.mark_span() self.goto(line,match.end()+offset-1) self.search_mark = True return True line += 1 else: while line >= 0: content = self.getContent(line) if line == first_line: content = content[:self.getPos()] match = None try: match = re.search(pattern,content) except: pass last_match = None offset = 0 while match: last_match = match last_offset = offset offset += match.end() match = re.search(pattern,content[offset:]) if last_match: if self.isMark(): self.mark_span() self.goto(line,last_match.start()+last_offset) self.mark_span() self.goto(line,last_match.end()+last_offset-1) self.search_mark = True return True line -= 1 return False
def searchagain(
self)
repeat the previous search if any
def searchagain(self): """ repeat the previous search if any """ self.pushUndo() self.invalidate_mark() if self.isMark(): if not self.last_search_dir: self.goto(self.mark_line_start,self.mark_pos_start) self.mark_span() if self.last_search: return self.search(self.last_search,self.last_search_dir,True) else: return False
def setWin(
self, win)
install a new window to render to
def setWin(self,win): """ install a new window to render to """ self.scr = win
def setfocus(
self, state)
set this editor to have focus or not
def setfocus(self,state): """ set this editor to have focus or not """ old_focus_state = self.focus self.focus = state return old_focus_state
def showcursor(
self, state)
set flag to turn cursor on or off
def showcursor(self,state): """ set flag to turn cursor on or off """ old_cursor_state = self.show_cursor self.show_cursor = state return old_cursor_state
def tab(
self)
tab in the correct distance to the next tab stop
def tab(self): """ tab in the correct distance to the next tab stop """ self.pushUndo() if self.isMark() and self.line_mark: oline = self.getLine() opos = self.getPos() mark_line_start = self.mark_line_start mark_line_end = oline if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.instab( mark_line_start, 0, False ) mark_line_start += 1 self.goto(oline,opos) else: self.instab( self.getLine(), self.getPos() )
def toggle_wrap(
self)
toggle wrapping for this editor
def toggle_wrap(self): """ toggle wrapping for this editor """ # don't toggle wrapping while we're marking a rectangle if self.rect_mark: return self.pushUndo() oline = self.getLine() opos = self.getPos() self.wrap = not self.wrap self.rewrap(True) self.invalidate_all() self.goto(oline,opos)
def transfer_clipboard(
self, from_xclip=False)
use xclip to transfer out clipboard to x or vice/versa
def transfer_clipboard(self, from_xclip = False): """ use xclip to transfer out clipboard to x or vice/versa """ if os.path.exists("/dev/clipboard"): if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for line in open("/dev/clipboard","r",buffering=1,encoding="utf-8"): clipboard.clip.append(line) else: cld = open("/dev/clipboard","w",buffering=0,encoding="utf-8") for line in clipboard.clip: cld.write(line) cld.close() elif os.path.exists("/usr/bin/xclip"): cmd = [ "xclip", ] if from_xclip: cmd += ["-out","-selection","clipboard"] else: cmd += ["-in","-selection","clipboard"] try: proc = subprocess.Popen( cmd, encoding="utf-8", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for l in proc.stdout: clipboard.clip.append(l) else: for l in clipboard.clip: print(l.rstrip(), file=proc.stdin) proc.stdout.close() proc.stdin.close() proc.stderr.close() proc.wait() except: pass
def undo(
self)
undo the last transaction, actually undoes the open transaction and the prior closed one
def undo(self): """ undo the last transaction, actually undoes the open transaction and the prior closed one """ line = self.line left = self.left self.undo_mgr.undo_transaction() # undo the one we're in... probably empty self.undo_mgr.undo_transaction() # undo the previous one... probably not empty if self.line != line or self.left != left: self.invalidate_screen()
def window_pos(
self, line, pos)
def window_pos(self,line,pos): sc_line,sc_pos = self.scrPos(line,pos) return((sc_line-self.line)+1,sc_pos-self.left)
Instance variables
var cmd_id
var end_count
var focus
var home_count
var last_search
var last_search_dir
var left
var line
var line_mark
var mark_line_start
var mark_pos_start
var mode
var parent
var pos
var prev_cmd
var prev_pos
var rect_mark
var scr
var search_mark
var show_cursor
var showname
var span_mark
var undo_mgr
var unwrap_lines
var vpos
var wrap
var wrap_lines
var wrap_modref
var wrap_width
class FileLine
Instance of a line in a file that hasn't been changed, stored on disk
class FileLine(EditLine): """ Instance of a line in a file that hasn't been changed, stored on disk """ def __init__(self, parent, pos, len = -1 ): """ FileLine(s) are pointers to a line on disk the EditFile reference and offset are stored """ EditLine.__init__(self) self.parent = parent self.pos = pos self.len = len def length(self): """ return length of line """ if self.len < 0: self.len = len(self.parent.expand_tabs(self.getContent())) return self.len def flush(self): """ flush cached length """ self.len = -1 def getContent(self): """ gets the file from its parent, seeks to position, reads line and returns it """ working = self.parent.getWorking() working.seek(self.pos,0) txt = working.readline().rstrip() return txt def __del__(self): self.parent = None
Ancestors (in MRO)
Static methods
def __init__(
self, parent, pos, len=-1)
FileLine(s) are pointers to a line on disk the EditFile reference and offset are stored
def __init__(self, parent, pos, len = -1 ): """ FileLine(s) are pointers to a line on disk the EditFile reference and offset are stored """ EditLine.__init__(self) self.parent = parent self.pos = pos self.len = len
def flush(
self)
flush cached length
def flush(self): """ flush cached length """ self.len = -1
def getContent(
self)
gets the file from its parent, seeks to position, reads line and returns it
def getContent(self): """ gets the file from its parent, seeks to position, reads line and returns it """ working = self.parent.getWorking() working.seek(self.pos,0) txt = working.readline().rstrip() return txt
def length(
self)
return length of line
def length(self): """ return length of line """ if self.len < 0: self.len = len(self.parent.expand_tabs(self.getContent())) return self.len
Instance variables
var len
var parent
var pos
class MemLine
Instance of a line in memory that has been edited
class MemLine(EditLine): """ Instance of a line in memory that has been edited """ def __init__(self, content ): """ MemLine(s) are in memory strings that represent a line that has been edited, it is initialized from the original file content""" EditLine.__init__(self) self.content = content def length(self): """ return the length of the content """ return len(self.content) def flush(self): """ flush cached length """ pass def getContent(self): """ just return the string reference """ return self.content
Ancestors (in MRO)
Static methods
def __init__(
self, content)
MemLine(s) are in memory strings that represent a line that has been edited, it is initialized from the original file content
def __init__(self, content ): """ MemLine(s) are in memory strings that represent a line that has been edited, it is initialized from the original file content""" EditLine.__init__(self) self.content = content
def getContent(
self)
just return the string reference
def getContent(self): """ just return the string reference """ return self.content
def length(
self)
return the length of the content
def length(self): """ return the length of the content """ return len(self.content)
Instance variables
var content
class ReadOnlyError
Exception when modification to readonly file attempted
class ReadOnlyError(Exception): """ Exception when modification to readonly file attempted """ pass
Ancestors (in MRO)
- ReadOnlyError
- builtins.Exception
- builtins.BaseException
- builtins.object
Class variables
var args
class ReadonlyEditor
editor subclass implements read only editor for viewing files
class ReadonlyEditor(Editor): """ editor subclass implements read only editor for viewing files """ def __init__(self, par, scr, name, showname = True): """ parent curses screen, screen to render to, filename to open """ self.showname = showname sfile = EditFile(name) sfile.setReadOnly() Editor.__init__(self, par, scr, name, sfile, showname) def getFilename(self): """ override getFilename so we can return None to indicate no file stuff should be done """ if self.showname: return Editor.getFilename(self) else: return None def handle(self,ch): """ handle override to only do read only actions to the file """ o_line = self.getLine() if isinstance(ch,int): ch = keymap.get_keyseq( self.scr, ch ) ret_ch = keytab.KEYTAB_NOKEY if ch in [keytab.KEYTAB_F02,keytab.KEYTAB_F04,keytab.KEYTAB_F10,keytab.KEYTAB_F01,keytab.KEYTAB_RESIZE, keytab.KEYTAB_CR, keytab.KEYTAB_BTAB, keytab.KEYTAB_TAB, keytab.KEYTAB_ESC]: ret_ch = ch elif ch == keytab.KEYTAB_CTRLW: # ctrl-w (toggle wrap in readonly editor) self.toggle_wrap() elif ch == keytab.KEYTAB_UP: self.cup() elif ch == keytab.KEYTAB_DOWN: self.cdown() elif ch == keytab.KEYTAB_LEFT: self.scroll_left() elif ch == keytab.KEYTAB_RIGHT: self.scroll_right() elif ch == keytab.KEYTAB_BACKSPACE: self.cup() elif ch == keytab.KEYTAB_HOME: self.home() elif ch == keytab.KEYTAB_END: self.end() elif ch == keytab.KEYTAB_PAGEUP: self.pageup() elif ch == keytab.KEYTAB_PAGEDOWN: self.pagedown() elif ch == keytab.KEYTAB_F05: self.prmt_search() elif ch == keytab.KEYTAB_F17: # shift f5: self.prmt_search(False) elif ch == keytab.KEYTAB_F03: self.prmt_searchagain() if self.getLine() != o_line or not self.isMark(): if self.isMark(): self.mark_lines() self.mark_lines() return ret_ch
Ancestors (in MRO)
- ReadonlyEditor
- Editor
- builtins.object
Class variables
var modes
Static methods
def __init__(
self, par, scr, name, showname=True)
parent curses screen, screen to render to, filename to open
def __init__(self, par, scr, name, showname = True): """ parent curses screen, screen to render to, filename to open """ self.showname = showname sfile = EditFile(name) sfile.setReadOnly() Editor.__init__(self, par, scr, name, sfile, showname)
def addstr(
self, row, col, str, attr=0)
write properly encoded string to screen location
def addstr(self,row,col,str,attr = curses.A_NORMAL): """ write properly encoded string to screen location """ try: return self.scr.addstr(row,col,codecs.encode(str,"utf-8"),attr) except: return 0
def applyUndo(
self, *args)
called by undo to unwind one undo action
def applyUndo(self,*args): """ called by undo to unwind one undo action """ ( self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap ) = args self.invalidate_screen() self.invalidate_mark()
def backspace(
self)
delete a character at the cursor and move back one character
def backspace(self): """ delete a character at the cursor and move back one character """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return line = self.getLine() pos = self.getPos() if pos: if pos <= self.getLength(line): self.goto(line,pos-1) self.delc() else: self.goto(line,pos-1) elif line: pos = self.getLength(line-1)-1 self.goto(line-1,pos) self.delc()
def btab(
self)
remove white space to the previous tab stop, or shift the line back to the previous tab stop
def btab(self): """ remove white space to the previous tab stop, or shift the line back to the previous tab stop """ self.pushUndo() if self.isMark() and self.line_mark: mark_line_start = self.mark_line_start mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.deltab( mark_line_start, self.workfile.get_tab_stop(0), False ) mark_line_start += 1 else: self.deltab( self.getLine(), self.getPos() )
def cdown(
self, rept=1)
go forward one or rept lines in the file
def cdown(self,rept = 1): """ go forward one or rept lines in the file """ self.pushUndo() self.invalidate_mark() while rept: if self.vpos < min((self.numLines(True)-1)-self.line,(self.max_y-2)): self.vpos += 1 elif self.line <= self.numLines(True)-self.max_y: self.line += 1 self.invalidate_screen() rept = rept - 1 self.goto(self.getLine(),self.getPos())
def cleft(
self, rept=1)
go back one or rept characters in the current line
def cleft(self,rept = 1): """ go back one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if pos >= rept: self.goto(line,pos-rept) return if self.wrap: if line: offset = self.getLength(line-1)-(rept-pos) self.goto(line-1,offset) else: self.goto(line,0)
def close(
self)
by default it is a no-op but editors overriding this can hook the close to clean things up
def close(self): """ by default it is a no-op but editors overriding this can hook the close to clean things up """ pass
def copy_marked(
self, delete=False, nocopy=False)
copy the marked text to the clipboard, delete== True means cut, nocopy == True will just delete
def copy_marked(self,delete=False,nocopy = False): """ copy the marked text to the clipboard, delete== True means cut, nocopy == True will just delete """ if not self.isMark(): return self.pushUndo() cp = self.get_marked(delete,nocopy) if cp and not (delete and nocopy): clipboard.clip_type = cp[0] clipboard.clip = cp[1]
def cr(
self)
insert a carriage return, split the current line at cursor
def cr(self): """ insert a carriage return, split the current line at cursor """ self.pushUndo() orig = self.getContent(self.getLine(),self.getPos(),True) self.workfile.replaceLine(self.getLine(),orig[0:self.getPos()]) self.workfile.insertLine(self.getLine()+1,orig[self.getPos():]) self.rewrap() self.goto(self.getLine()+1,0)
def cright(
self, rept=1)
go forward one or rept characters in the current line
def cright(self,rept = 1): """ go forward one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if self.wrap: llen = self.getLength(line) if pos + rept < llen: self.goto(line,pos+rept) return if line < self.numLines()-1: self.goto(line+1,llen-(pos+rept)) return self.goto(line,llen) else: self.goto(line,pos+rept)
def cup(
self)
go back one line in the file
def cup(self): """ go back one line in the file """ self.pushUndo() self.invalidate_mark() if self.vpos: self.vpos -= 1 elif self.line: self.line -= 1 self.invalidate_screen() self.goto(self.getLine(),self.getPos())
def delc(
self)
deletes one character at the cursor position
def delc(self): """ deletes one character at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return orig = self.getContent(self.getLine()) offset = self.getPos() if offset > len(orig): return elif offset == len(orig): next_idx = self.getLine()+1 if next_idx > self.numLines(): return next = self.getContent(next_idx) orig = orig[0:offset] + next self.workfile.replaceLine(self.getLine(),orig) self.workfile.deleteLine(next_idx) else: orig = orig[0:offset]+orig[offset+1:] self.workfile.replaceLine(self.getLine(),orig) self.rewrap()
def deltab(
self, line, pos, move_cursor=True)
remove a tab from the line at position provided optionally move the cursor
def deltab(self, line, pos, move_cursor = True ): """ remove a tab from the line at position provided optionally move the cursor """ orig = self.getContent(line,pos+1,True) idx = pos start = 0 stop = 0 while idx: while idx and orig[idx] != ' ': idx -= 1 start = idx stop = self.workfile.get_tab_stop(idx,True) while idx and idx >= stop: if orig[idx] != ' ': break idx -= 1 else: if start > stop: break if start > stop: orig = orig[0:stop]+orig[start+1:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop)
def draw_cursor(
self)
worker function to draw the current cursor position
def draw_cursor(self): """ worker function to draw the current cursor position """ if self.show_cursor: line = self.getLine() pos = self.getPos() if pos < self.getLength(line): cursor_ch = self.getContent(line)[pos] else: cursor_ch = ' ' sc_line,sc_pos = self.window_pos(line,pos) self.addstr(sc_line,sc_pos,cursor_ch,curses.A_REVERSE)
def draw_mark(
self)
worker function to draw the marked section of the file
def draw_mark(self): """ worker function to draw the marked section of the file """ if not self.isMark(): return (mark_top,mark_left) = self.scrPos(self.mark_line_start,self.mark_pos_start) mark_line_start = mark_top mark_pos_start = mark_left mark_right = self.getPos(True) mark_bottom = self.getLine(True) if (self.rect_mark or self.line_mark or (self.span_mark and mark_top == mark_bottom)) and mark_left > mark_right: mark = mark_left mark_left = mark_right mark_right = mark if mark_top > mark_bottom: mark = mark_bottom mark_bottom = mark_top mark_top = mark if mark_top < self.line: mark_top = self.line if self.span_mark: mark_left = 0 mark_right = mark_right + 1 s_left = mark_left - self.left s_left = max(0,s_left) s_right = mark_right - self.left s_right = min(self.max_x,s_right) s_top = (mark_top - self.line)+1 s_top = max(1,s_top) s_bottom = (mark_bottom - self.line)+1 s_bottom = min(self.max_y-1,s_bottom) mark_left = max(mark_left,self.left) mark_right = min(mark_right,self.left+self.max_x) if self.line_mark: s_right = self.max_x mark_right = self.left+s_right mark_left = max(mark_left,self.left) if s_top == s_bottom: if s_right > s_left: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) elif self.rect_mark: if mark_top < self.line: mark_top = self.line while s_top <= s_bottom: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) s_top += 1 mark_top += 1 elif self.span_mark: cur_line = mark_top while s_top <= s_bottom: if cur_line == mark_top: offset = s_left width = self.max_x-offset self.addstr(s_top, offset, self.getContent(cur_line, self.left+offset+width, True, True)[self.left+offset:self.left+offset+width], curses.A_REVERSE) elif cur_line == mark_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.getPos(True), True, True)[self.left:self.getPos(True)+1], curses.A_REVERSE) else: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1 elif self.line_mark: cur_line = mark_top while s_top <= s_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1
def end(
self)
once go to end of line, twice end of page, thrice end of file
def end(self): """ once go to end of line, twice end of page, thrice end of file """ self.pushUndo() if self.cmd_id == cmd_names.CMD_END and self.prev_cmd == cmd_names.CMD_END: self.end_count += 1 self.end_count = self.end_count % 3 else: self.end_count = 0 if self.end_count == 0: self.endln() elif self.end_count == 1: self.endpg() self.endln() elif self.end_count == 2: self.endfile() self.endln()
def endfile(
self)
go to the end of the file
def endfile(self): """ go to the end of the file """ self.pushUndo() ldisp = (self.numLines(True)-1)-self.line if ldisp < self.max_y-2: return self.line = (self.numLines(True)-1) - (self.max_y-2) self.vpos = min(self.max_y-2,ldisp) self.invalidate_screen()
def endln(
self)
go to the end of a line
def endln(self): """ go to the end of a line """ self.pushUndo() self.invalidate_mark() orig = self.getContent(self.getLine()) offset = len(orig) self.goto(self.getLine(),offset)
def endpg(
self)
go to the end of a page
def endpg(self): """ go to the end of a page """ self.pushUndo() self.invalidate_mark() ldisp = (self.numLines(True)-1)-self.line self.vpos = min(self.max_y-2,ldisp)
def filePos(
self, line, pos)
translate display line, pos to file line, pos
def filePos(self, line, pos ): """ translate display line, pos to file line, pos """ if self.wrap: if line < len(self.wrap_lines): return (self.wrap_lines[line][0],self.wrap_lines[line][1]+pos) else: return (self.numLines()+(line-len(self.wrap_lines)),pos) else: return (line,pos)
def flushChanges(
self)
flush change tracking to show we're done updating
def flushChanges( self ): """ flush change tracking to show we're done updating """ if self.workfile: self.workfile.flushChanges(self)
def getContent(
self, line, pad=0, trim=False, display=False)
get a line from the file
def getContent(self, line, pad = 0, trim= False, display=False ): """ get a line from the file """ if self.wrap: if display: orig = "" if line < len(self.wrap_lines): orig = self.workfile.getLine(self.wrap_lines[line][0])[self.wrap_lines[line][1]:self.wrap_lines[line][2]] if trim: orig = orig.rstrip() if pad > len(orig): orig = orig + ' '*(pad-len(orig)) return orig orig = self.workfile.getLine(line,pad,trim) return orig
def getCurrentLine(
self, display=False)
returns the current line in the file
def getCurrentLine(self,display=False): """ returns the current line in the file """ return self.getContent(self.getLine(display),display)
def getFilename(
self)
override getFilename so we can return None to indicate no file stuff should be done
def getFilename(self): """ override getFilename so we can return None to indicate no file stuff should be done """ if self.showname: return Editor.getFilename(self) else: return None
def getLength(
self, line, display=False)
get the length of a line
def getLength(self, line, display=False ): """ get the length of a line """ length = 0 if self.wrap and display: if line < len(self.wrap_lines): length = self.workfile.length(self.wrap_lines[line][0]) else: length = self.workfile.length(line) return length
def getLine(
self, display=False)
get the line that we're on in the current file
def getLine(self,display=False): """ get the line that we're on in the current file """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,0) return r_line return self.line+self.vpos
def getModref(
self)
return the current modref of this editor
def getModref(self): """ return the current modref of this editor """ return self.workfile.getModref()
def getPos(
self, display=False)
get the character position in the current line that we're at
def getPos(self,display=False): """ get the character position in the current line that we're at """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,self.left+self.pos) return r_pos return self.left+self.pos
def getUndoMgr(
self)
get the undo manager that we're using
def getUndoMgr(self): """ get the undo manager that we're using """ return self.undo_mgr
def getWorkfile(
self)
return the workfile that this editor is attached to
def getWorkfile(self): """ return the workfile that this editor is attached to """ return self.workfile
def get_marked(
self, delete=False, nocopy=False)
returns marked text as tuple ( cliptype, [list of clipped] ) returns () if no mark
def get_marked(self, delete=False, nocopy = False): """ returns marked text as tuple ( cliptype, [list of clipped] ) returns () if no mark """ if not self.isMark(): return () self.pushUndo() if delete: self.invalidate_screen() mark_pos_start = self.mark_pos_start mark_line_start = self.mark_line_start mark_pos_end = self.getPos() mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark elif mark_line_start == mark_line_end and mark_pos_start > mark_pos_end: mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark clip = [] clip_type = clipboard.LINE_CLIP line_idx = mark_line_start if self.line_mark: if not nocopy: clip_type = clipboard.LINE_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx)) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.rewrap() elif self.span_mark: if not nocopy: clip_type = clipboard.SPAN_CLIP if line_idx == mark_line_end: clip.append(self.getContent(line_idx)[mark_pos_start:mark_pos_end+1]) else: clip.append(self.getContent(line_idx)[mark_pos_start:]+'\n') line_idx += 1 while line_idx < mark_line_end: clip.append(self.getContent(line_idx)+'\n') line_idx += 1 clip.append(self.getContent(line_idx)[0:mark_pos_end+1]) if delete: line_idx = mark_line_start if line_idx == mark_line_end: orig = self.getContent(line_idx) orig = orig[0:mark_pos_start] + orig[mark_pos_end+1:] self.workfile.replaceLine(line_idx,orig) self.rewrap() else: first_line = self.getContent(mark_line_start) last_line = self.getContent(mark_line_end) while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.workfile.insertLine(mark_line_start,first_line[0:mark_pos_start] + last_line[mark_pos_end+1:]) self.rewrap() elif self.rect_mark: if not nocopy: clip_type = clipboard.RECT_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx,mark_pos_end,True)[mark_pos_start:mark_pos_end+1]) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: orig = self.getContent(line_idx,mark_pos_end,True) self.workfile.replaceLine(line_idx,orig[0:mark_pos_start]+orig[mark_pos_end+1:]) line_idx += 1 # sync the x clipboard self.transfer_clipboard() if self.line_mark: self.line_mark = False if self.rect_mark: self.rect_mark = False if self.span_mark: self.span_mark = False if delete: self.goto(mark_line_start,mark_pos_start) self.invalidate_screen() return (clip_type, clip)
def goto(
self, line, pos)
goto a line in the file and position the cursor to pos offset in the line
def goto(self,line, pos ): """ goto a line in the file and position the cursor to pos offset in the line """ self.pushUndo() self.invalidate_mark() if line < 0: line = 0 if pos < 0: pos = 0 (line,pos) = self.scrPos(line,pos) if line >= self.line and line <= self.line+(self.max_y-2): self.vpos = line - self.line elif line < self.line: self.line = line self.vpos = 0 self.invalidate_screen() elif line > self.line+(self.max_y-2): self.line = line - (self.max_y-2) self.vpos = (self.max_y-2) self.invalidate_screen() if pos >= self.left and pos < self.left+(self.max_x-1): self.pos = pos - self.left elif pos >= self.left+(self.max_x-1): self.left = pos-(self.max_x-1) self.pos = self.max_x-1 self.invalidate_screen() else: self.left = pos self.pos = 0 self.invalidate_screen()
def handle(
self, ch)
handle override to only do read only actions to the file
def handle(self,ch): """ handle override to only do read only actions to the file """ o_line = self.getLine() if isinstance(ch,int): ch = keymap.get_keyseq( self.scr, ch ) ret_ch = keytab.KEYTAB_NOKEY if ch in [keytab.KEYTAB_F02,keytab.KEYTAB_F04,keytab.KEYTAB_F10,keytab.KEYTAB_F01,keytab.KEYTAB_RESIZE, keytab.KEYTAB_CR, keytab.KEYTAB_BTAB, keytab.KEYTAB_TAB, keytab.KEYTAB_ESC]: ret_ch = ch elif ch == keytab.KEYTAB_CTRLW: # ctrl-w (toggle wrap in readonly editor) self.toggle_wrap() elif ch == keytab.KEYTAB_UP: self.cup() elif ch == keytab.KEYTAB_DOWN: self.cdown() elif ch == keytab.KEYTAB_LEFT: self.scroll_left() elif ch == keytab.KEYTAB_RIGHT: self.scroll_right() elif ch == keytab.KEYTAB_BACKSPACE: self.cup() elif ch == keytab.KEYTAB_HOME: self.home() elif ch == keytab.KEYTAB_END: self.end() elif ch == keytab.KEYTAB_PAGEUP: self.pageup() elif ch == keytab.KEYTAB_PAGEDOWN: self.pagedown() elif ch == keytab.KEYTAB_F05: self.prmt_search() elif ch == keytab.KEYTAB_F17: # shift f5: self.prmt_search(False) elif ch == keytab.KEYTAB_F03: self.prmt_searchagain() if self.getLine() != o_line or not self.isMark(): if self.isMark(): self.mark_lines() self.mark_lines() return ret_ch
def has_changes(
self)
return true if there are any pending changes
def has_changes(self): """ return true if there are any pending changes """ return self.workfile.hasChanges(self)
def home(
self)
once to to start of line, twice start of page, thrice start of file
def home(self): """ once to to start of line, twice start of page, thrice start of file """ self.pushUndo() self.invalidate_mark() if self.cmd_id == cmd_names.CMD_HOME and self.prev_cmd == cmd_names.CMD_HOME: self.home_count += 1 self.home_count = self.home_count % 3 else: self.home_count = 0 if self.home_count == 0: self.goto(self.getLine(),0) elif self.home_count == 1: self.vpos = 0 elif self.home_count == 2: self.line = 0 self.invalidate_screen()
def insert(
self, c)
insert a character or string at the cursor position
def insert(self, c ): """ insert a character or string at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert orig = self.getContent(self.getLine()).rstrip() offset = self.getPos() pad = "" if offset > len(orig): pad = " "*(offset - len(orig)) orig = orig[0:offset] + pad + c + orig[offset:] insert_line = self.getLine() self.workfile.replaceLine(insert_line,orig) self.rewrap() self.goto(insert_line,offset+len(c))
def instab(
self, line, pos, move_cursor=True)
insert a tab at a line and position
def instab(self, line, pos, move_cursor = True ): """ insert a tab at a line and position """ orig = self.getContent(line,pos,True) stop = self.workfile.get_tab_stop(pos) orig = orig[0:pos] + ' '*(stop-(pos)) + orig[pos:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop)
def invalidate_after_cursor(
self)
touch all the lines from the current position to the end of the screen
def invalidate_after_cursor(self): """ touch all the lines from the current position to the end of the screen """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(self.getLine(),line+self.max_y)
def invalidate_all(
self)
touch all the lines in the file so everything will redraw
def invalidate_all(self): """ touch all the lines in the file so everything will redraw """ self.workfile.touchLine(0,self.workfile.numLines())
def invalidate_mark(
self)
touch the marked lines so that they'll redraw when we change the shape of the mark or do a copy or paste
def invalidate_mark(self): """ touch the marked lines so that they'll redraw when we change the shape of the mark or do a copy or paste """ if self.isMark(): self.workfile.touchLine(self.mark_line_start, self.getLine()) if self.search_mark: self.span_mark = False self.search_mark = False
def invalidate_screen(
self)
touch all the lines on the screen so everything will redraw
def invalidate_screen(self): """ touch all the lines on the screen so everything will redraw """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(line,line+self.max_y)
def isChanged(
self)
returns true if the file we're working on has unsaved changes
def isChanged(self): """ returns true if the file we're working on has unsaved changes """ return self.workfile.isChanged()
def isLineChanged(
self, line, display=True)
return true if line is changed for the current revisions
def isLineChanged(self, line, display=True ): """ return true if line is changed for the current revisions """ if self.workfile: if display: return self.workfile.isLineChanged( self, self.filePos(line,0)[0]) else: return self.workfile.isLineChanged( self, line ) else: return True
def isMark(
self)
returns true if there is a mark set
def isMark(self): """ returns true if there is a mark set """ return (self.line_mark or self.span_mark or self.rect_mark or self.search_mark)
def main(
self, blocking=True, start_ch=None)
main driver loop for editor, if blocking = False exits on each keystroke to allow embedding, start_ch is a character read externally that hould be processed on startup
def main(self,blocking = True, start_ch = None): """ main driver loop for editor, if blocking = False exits on each keystroke to allow embedding, start_ch is a character read externally that hould be processed on startup """ curses.curs_set(0) self.rewrap() self.scr.nodelay(1) self.scr.notimeout(0) self.scr.timeout(0) while (1): if not self.scr: return 27 if not self.mode: for m in Editor.modes: if m.detect_mode(self): self.mode = m self.getWorkfile().set_tabs(m.get_tabs(self)) break else: self.mode = None self.redraw() if start_ch: ch = start_ch start_ch = None else: ch = keymap.getch(self.scr) try: self.undo_mgr.new_transaction() if self.mode: ch = self.mode.handle(self,ch) modref = self.workfile.getModref() ret_seq = self.handle(ch) if self.wrap and modref != self.workfile.getModref(): self.rewrap() if ret_seq or not blocking: return ret_seq except ReadOnlyError as e: message(self.parent,"Read Only File Error","Changes not allowed.") if not blocking: return keytab.KEYTAB_REFRESH
def mark_lines(
self)
mark whole lines
def mark_lines(self): """ mark whole lines """ self.pushUndo() self.invalidate_mark() if not self.line_mark: self.line_mark = True self.span_mark = False self.rect_mark = False self.mark_pos_start = 0 self.mark_line_start = self.getLine() else: self.line_mark = False
def mark_rect(
self)
mark a rectangular or column selection across lines
def mark_rect(self): """ mark a rectangular or column selection across lines """ # no column cut in wrapped mode, it doesn't make sense if self.wrap: return self.pushUndo() self.invalidate_mark() if not self.rect_mark: self.rect_mark = True self.span_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.rect_mark = False
def mark_span(
self)
mark a span of characters that can start and end in the middle of a line
def mark_span(self): """ mark a span of characters that can start and end in the middle of a line """ self.pushUndo() self.invalidate_mark() if not self.span_mark: self.span_mark = True self.rect_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.span_mark = False
def move(
self)
update the previous cursor position from the current
def move(self): """ update the previous cursor position from the current """ self.prev_pos = (self.getLine(),self.getPos())
def next_word(
self)
scan left until you get to the previous word
def next_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos < len(orig): if orig[pos] == ' ': while pos < len(orig) and orig[pos] == ' ': pos += 1 else: while pos < len(orig) and orig[pos] != ' ': pos += 1 while pos < len(orig) and orig[pos] == ' ': pos += 1 else: pos = len(orig) self.goto(self.getLine(),pos)
def numLines(
self, display=False)
get the number of lines in the editor
def numLines(self,display=False): """ get the number of lines in the editor """ if self.wrap and display: return len(self.wrap_lines) return self.workfile.numLines()
def pagedown(
self)
go forward one page in the file
def pagedown(self): """ go forward one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line + (self.max_y-2) if offset > self.numLines(True)-1: return self.line = offset ldisp = (self.numLines(True)-1)-self.line if self.vpos > ldisp: self.vpos = ldisp self.invalidate_screen()
def pageup(
self)
go back one page in the file
def pageup(self): """ go back one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line - (self.max_y-2) if offset < 0: offset = 0 self.line = offset self.invalidate_screen()
def paste(
self)
paste the current clip at the cursor position
def paste(self): """ paste the current clip at the cursor position """ if clipboard.clip: # no column cut or paste when in wrap mode if self.wrap and clipboard.clip_type == clipboard.RECT_CLIP: return self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert if clipboard.clip_type == clipboard.LINE_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: self.workfile.insertLine(target,line) target += 1 self.rewrap() self.goto(target,pos) elif clipboard.clip_type == clipboard.SPAN_CLIP: target = self.getLine() pos = self.getPos() idx = 0 for line in clipboard.clip: orig = self.getContent(target,pos,True) if (not line) or line[-1] == '\n': line = line.rstrip() if not idx: self.workfile.replaceLine(target,orig[0:pos]+line) self.workfile.insertLine(target+1,orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) target += 1 else: self.workfile.insertLine(target,line) self.rewrap() self.goto(target, len(line)) target += 1 else: if not idx: self.workfile.replaceLine(target,orig[0:pos]+line+orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) else: self.workfile.replaceLine(target,line+orig) self.rewrap() self.goto(target, len(line)) idx += 1 elif clipboard.clip_type == clipboard.RECT_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: orig = self.getContent(target,self.getPos(),True) self.workfile.replaceLine(target,orig[0:self.getPos()]+line+orig[self.getPos():]) target += 1 self.rewrap() self.goto(target,pos)
def prevPos(
self)
get the previous cursor position
def prevPos(self): """ get the previous cursor position """ return self.prev_pos
def prev_word(
self)
scan left until you get to the previous word
def prev_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos >= len(orig): pos = len(orig)-1 if pos and pos < len(orig): pos -= 1 while pos and orig[pos] == ' ': pos -= 1 while pos and orig[pos-1] != ' ': pos -= 1 elif pos >= len(orig): pos = len(orig) else: pos = 0 self.goto(self.getLine(),pos)
def prmt_goto(
self)
prompt for a line to go to and go there
def prmt_goto(self): """ prompt for a line to go to and go there """ self.invalidate_screen() goto_line = prompt(self.parent,"Goto","Enter line number 0-%d :"%(self.numLines()-1),10,name="goto") if goto_line: self.goto(int(goto_line),self.getPos())
def prmt_replace(
self)
prompt for search pattern and replacement string, then confirm replacment or replace all for the occurrences until no more are found
def prmt_replace(self): """ prompt for search pattern and replacement string, then confirm replacment or replace all for the occurrences until no more are found """ (pattern,rep) = replace(self.parent) if pattern and rep: found = self.search(pattern) replace_all = False do_replace = False while found: self.redraw() self.scr.refresh() if not replace_all: answer = confirm_replace(self.parent) self.invalidate_screen() if answer == 1: do_replace = True elif answer == 2: do_replace = False elif answer == 3: replace_all = True elif answer == 4: message(self.parent,"Canceled","Replace canceled.") return if do_replace or replace_all: self.insert(rep) found = self.searchagain() else: message(self.parent,"Replace","Pattern not found.") self.invalidate_screen()
def prmt_search(
self, down=True)
prompt for a search string then search for it and either put up a message that it was not found or position the cursor to the occurrance
def prmt_search(self,down=True): """ prompt for a search string then search for it and either put up a message that it was not found or position the cursor to the occurrance """ self.invalidate_screen() if down: title = "Search Forward" else: title = "Search Backward" pattern = prompt(self.parent,title,"Pattern: ",-1,name="search") if pattern: if not self.search(pattern,down): message(self.parent,"Search","Pattern not found.")
def prmt_searchagain(
self)
search again and put up a message if no more are found
def prmt_searchagain(self): """ search again and put up a message if no more are found """ self.invalidate_screen() if not self.searchagain(): if self.isMark(): self.mark_span() message(self.parent,"Search","Pattern not found.")
def pushUndo(
self)
push an undo action onto the current transaction
def pushUndo(self): """ push an undo action onto the current transaction """ self.undo_mgr.get_transaction().push(self.applyUndo,(self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap))
def redraw(
self)
redraw the editor as needed
def redraw(self): """ redraw the editor as needed """ try: if not self.scr or keymap.is_playback(): return self.max_y,self.max_x = self.scr.getmaxyx() self.scr.keypad(1) if self.workfile.isChanged(): changed = "*" elif self.workfile.isReadOnly(): changed = "R" else: changed = " " if self.mode: changed = changed + " " + self.mode.name() filename = self.workfile.getFilename() if not self.showname: filename = "" status = "%d : %d : %d : %s : %s : %s"%(self.numLines(),self.getLine(),self.getPos(),changed,filename, "REC" if keymap.is_recording() else "PBK" if keymap.is_playback() else " " ) if len(status) < self.max_x: status += (self.max_x-len(status))*' ' if self.focus: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE|curses.A_BOLD) else: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE) # if the mode is rendering then don't do the default rendering as well mode_redraw = False if self.mode: mode_redraw = self.mode.redraw(self) if not mode_redraw: cursor_line,cursor_pos = self.window_pos(*self.prevPos()) y = 1 lidx = self.line while lidx < self.line+(self.max_y-1): try: line_changed = self.isLineChanged(lidx) is_cursor_line = (y == cursor_line) if line_changed or is_cursor_line: l = self.getContent(lidx,self.left+self.max_x,True,True) if line_changed: self.addstr(y,0,l[self.left:self.left+self.max_x]) else: self.addstr(y,cursor_pos,l[self.left+cursor_pos]) except Exception as e: pass y = y + 1 lidx = lidx + 1 self.draw_mark() self.move() self.draw_cursor() if mode_redraw: self.flushChanges() except: raise
def resize(
self)
resize the editor to fill the window
def resize(self): """ resize the editor to fill the window """ if self.scr: self.max_y,self.max_x = self.scr.getmaxyx() self.rewrap() bottom_y = max(min((self.numLines(True)-1)-self.line,(self.max_y-2)),0) if self.vpos > bottom_y: self.vpos = bottom_y right_x = self.max_x-1 if self.pos > right_x: self.left += self.pos-right_x self.pos = right_x self.invalidate_screen()
def rewrap(
self, force=False)
compute the wrapped line array
def rewrap(self, force = False): """ compute the wrapped line array """ if self.wrap and (force or self.workfile.getModref() != self.wrap_modref or self.wrap_width != self.max_x): self.wrap_modref = self.workfile.getModref() self.wrap_width = self.max_x self.wrap_lines = [] self.unwrap_lines = [] for l in range(0,self.workfile.numLines()): line_len = self.workfile.length(l) start = 0 self.unwrap_lines.append(len(self.wrap_lines)) if not line_len: self.wrap_lines.append((l,0,0)) else: while start < line_len: self.wrap_lines.append((l,start,min(line_len,start+self.wrap_width))) start += self.wrap_width self.invalidate_after_cursor()
def save(
self)
save the current buffer
def save(self): """ save the current buffer """ if self.workfile.isModifiedOnDisk(): if not confirm(self.parent, "File has changed on disk, overwrite?"): self.invalidate_screen() self.redraw() return self.workfile.save() self.undo_mgr.flush_undo() self.goto(self.getLine(),self.getPos()) self.invalidate_all() self.redraw() gc.collect()
def saveas(
self)
open the file dialog and enter or point to a file and then save this buffer to that path
def saveas(self): """ open the file dialog and enter or point to a file and then save this buffer to that path """ f = file_dialog.FileDialog(self.parent,"Save file as") choices = f.main() if choices and choices["file"]: self.workfile.save(os.path.join(choices["dir"],choices["file"])) self.undo_mgr.flush_undo() self.invalidate_all() gc.collect()
def scrPos(
self, line, pos)
translate file pos to screen pos
def scrPos(self, line, pos ): """ translate file pos to screen pos """ if self.wrap: nlines = len(self.unwrap_lines) if line >= nlines: r_line,r_pos = self.scrPos(self.numLines()-1,self.getLength(self.numLines()-1)-1) return (r_line+(line-self.numLines())+1,pos) sline = self.unwrap_lines[line] while sline < len(self.wrap_lines) and self.wrap_lines[sline][0] == line: if pos >= self.wrap_lines[sline][1] and pos < self.wrap_lines[sline][2]: return (sline,pos-self.wrap_lines[sline][1]) sline = sline + 1 else: return (sline-1,pos - self.wrap_lines[sline-1][1]) else: return (line,pos)
def scroll_left(
self)
scroll the page left without moving the current cursor position
def scroll_left(self): """ scroll the page left without moving the current cursor position """ self.pushUndo() if self.left: self.left -= 1 self.invalidate_screen()
def scroll_right(
self)
scroll the page right without moving the current cursor position
def scroll_right(self): """ scroll the page right without moving the current cursor position """ self.pushUndo() self.left += 1 self.invalidate_screen()
def search(
self, pattern, down=True, next=True)
search for a regular expression forward or back if next is set then skip one before matching
def search(self, pattern, down = True, next = True): """ search for a regular expression forward or back if next is set then skip one before matching """ self.pushUndo() self.invalidate_mark() self.last_search = pattern self.last_search_dir = down first_line = self.getLine() line = first_line if down: while line < self.numLines(): content = self.getContent(line) if line == first_line: content = content[self.getPos():] offset = self.getPos() else: offset = 0 match = None try: match = re.search(pattern,content) except: pass if match: if self.isMark(): self.mark_span() self.goto(line,match.start()+offset) self.mark_span() self.goto(line,match.end()+offset-1) self.search_mark = True return True line += 1 else: while line >= 0: content = self.getContent(line) if line == first_line: content = content[:self.getPos()] match = None try: match = re.search(pattern,content) except: pass last_match = None offset = 0 while match: last_match = match last_offset = offset offset += match.end() match = re.search(pattern,content[offset:]) if last_match: if self.isMark(): self.mark_span() self.goto(line,last_match.start()+last_offset) self.mark_span() self.goto(line,last_match.end()+last_offset-1) self.search_mark = True return True line -= 1 return False
def searchagain(
self)
repeat the previous search if any
def searchagain(self): """ repeat the previous search if any """ self.pushUndo() self.invalidate_mark() if self.isMark(): if not self.last_search_dir: self.goto(self.mark_line_start,self.mark_pos_start) self.mark_span() if self.last_search: return self.search(self.last_search,self.last_search_dir,True) else: return False
def setWin(
self, win)
install a new window to render to
def setWin(self,win): """ install a new window to render to """ self.scr = win
def setfocus(
self, state)
set this editor to have focus or not
def setfocus(self,state): """ set this editor to have focus or not """ old_focus_state = self.focus self.focus = state return old_focus_state
def showcursor(
self, state)
set flag to turn cursor on or off
def showcursor(self,state): """ set flag to turn cursor on or off """ old_cursor_state = self.show_cursor self.show_cursor = state return old_cursor_state
def tab(
self)
tab in the correct distance to the next tab stop
def tab(self): """ tab in the correct distance to the next tab stop """ self.pushUndo() if self.isMark() and self.line_mark: oline = self.getLine() opos = self.getPos() mark_line_start = self.mark_line_start mark_line_end = oline if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.instab( mark_line_start, 0, False ) mark_line_start += 1 self.goto(oline,opos) else: self.instab( self.getLine(), self.getPos() )
def toggle_wrap(
self)
toggle wrapping for this editor
def toggle_wrap(self): """ toggle wrapping for this editor """ # don't toggle wrapping while we're marking a rectangle if self.rect_mark: return self.pushUndo() oline = self.getLine() opos = self.getPos() self.wrap = not self.wrap self.rewrap(True) self.invalidate_all() self.goto(oline,opos)
def transfer_clipboard(
self, from_xclip=False)
use xclip to transfer out clipboard to x or vice/versa
def transfer_clipboard(self, from_xclip = False): """ use xclip to transfer out clipboard to x or vice/versa """ if os.path.exists("/dev/clipboard"): if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for line in open("/dev/clipboard","r",buffering=1,encoding="utf-8"): clipboard.clip.append(line) else: cld = open("/dev/clipboard","w",buffering=0,encoding="utf-8") for line in clipboard.clip: cld.write(line) cld.close() elif os.path.exists("/usr/bin/xclip"): cmd = [ "xclip", ] if from_xclip: cmd += ["-out","-selection","clipboard"] else: cmd += ["-in","-selection","clipboard"] try: proc = subprocess.Popen( cmd, encoding="utf-8", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for l in proc.stdout: clipboard.clip.append(l) else: for l in clipboard.clip: print(l.rstrip(), file=proc.stdin) proc.stdout.close() proc.stdin.close() proc.stderr.close() proc.wait() except: pass
def undo(
self)
undo the last transaction, actually undoes the open transaction and the prior closed one
def undo(self): """ undo the last transaction, actually undoes the open transaction and the prior closed one """ line = self.line left = self.left self.undo_mgr.undo_transaction() # undo the one we're in... probably empty self.undo_mgr.undo_transaction() # undo the previous one... probably not empty if self.line != line or self.left != left: self.invalidate_screen()
def window_pos(
self, line, pos)
def window_pos(self,line,pos): sc_line,sc_pos = self.scrPos(line,pos) return((sc_line-self.line)+1,sc_pos-self.left)
Instance variables
class StreamEditor
this is a read only editor that wraps a stream it has a select option for use when embedding in a control to select lines from the stream
class StreamEditor(Editor): """ this is a read only editor that wraps a stream it has a select option for use when embedding in a control to select lines from the stream """ def __init__(self, par, scr, name, stream, select = False, line_re = None, follow = False, wait = False, workfile=None ): """ takes parent curses screen, screen to render to, name for stream, stream to read in, and select to indicate if line selection is requested """ self.select = select self.line_re = line_re self.follow = follow self.wait = wait self.o_nlines = 0 if workfile: self.sfile = workfile else: self.sfile = StreamFile(name,stream,self.wait) Editor.__init__(self, par, scr, self.sfile.getFilename(), self.sfile) def __copy__(self): """ override to just copy the editor state and not the underlying file object """ result = StreamEditor(self.parent,self.scr,None,None,self.select, self.line_re, self.follow, self.wait, self.workfile) result.o_nlines = 0 result.line = self.line result.pos = self.pos result.vpos = self.vpos result.left = self.left result.prev_cmd = self.prev_cmd result.cmd_id = self.cmd_id result.home_count = self.home_count result.end_count = self.end_count result.line_mark = self.line_mark result.span_mark = self.span_mark result.rect_mark = self.rect_mark result.search_mark = self.search_mark result.mark_pos_start = self.mark_pos_start result.mark_line_start = self.mark_line_start result.last_search = self.last_search result.last_search_dir = self.last_search_dir result.mode = self.mode result.wrap_lines = copy.copy(self.wrap_lines) result.unwrap_lines = copy.copy(self.unwrap_lines) result.wrap_modref = self.wrap_modref result.wrap_width = self.wrap_width result.show_cursor = self.show_cursor result.focus = self.focus result.prev_pos = copy.copy(self.prev_pos) return result def __del__(self): """ clean up the StreamFile """ self.sfile = None def close(self): """ close and shut down the stream file """ if self.sfile: self.sfile.close() self.sfile = None def handle(self,ch): """ override normal keystroke handling if in select mode and move about doing selection and return on enter """ if self.follow: nlines = self.numLines() if self.o_nlines != nlines: self.endfile() self.o_nlines = nlines if ch in keymap.keydef_map and keymap.keydef_map[ch][-1] == keytab.KEYTAB_CTRLF: self.follow = not self.follow return keytab.KEYTAB_NOKEY elif not self.select: return Editor.handle(self,ch) o_line = self.getLine() if isinstance(ch,int): ch = keymap.get_keyseq( self.scr, ch ) ret_ch = keytab.KEYTAB_NOKEY direction = 0 if ch in [keytab.KEYTAB_F02,keytab.KEYTAB_F04,keytab.KEYTAB_F10,keytab.KEYTAB_F01,keytab.KEYTAB_RESIZE, keytab.KEYTAB_CR, keytab.KEYTAB_BTAB, keytab.KEYTAB_TAB, keytab.KEYTAB_ESC]: ret_ch = ch elif ch == keytab.KEYTAB_UP: self.cup() direction = -1 elif ch == keytab.KEYTAB_DOWN: self.cdown() direction = 1 elif ch == keytab.KEYTAB_LEFT: self.scroll_left() elif ch == keytab.KEYTAB_RIGHT: self.scroll_right() elif ch == keytab.KEYTAB_BACKSPACE: direction = -1 self.cup() elif ch == keytab.KEYTAB_HOME: self.pushUndo() self.left = 0 self.pos = 0 self.line = 0 self.vpos = 0 direction = 1 elif ch == keytab.KEYTAB_END: self.endfile() direction = -1 elif ch == keytab.KEYTAB_PAGEUP: self.pageup() direction = -1 elif ch == keytab.KEYTAB_PAGEDOWN: self.pagedown() direction = 1 elif ch == keytab.KEYTAB_F05: self.prmt_search() direction = 1 elif ch == keytab.KEYTAB_F17: # shift f5: self.prmt_search(False) direction = -1 elif ch == keytab.KEYTAB_F03: self.prmt_searchagain() if self.last_search_dir: direction = 1 else: direction = -1 if self.line_re and direction: if direction > 0: while True: if re.search(self.line_re, self.getCurrentLine()): self.line = self.getLine() self.vpos = 0 break line = self.getLine() self.cdown() if line == self.getLine(): self.undo_mgr.undo_transaction() break elif direction < 0: while True: if re.search(self.line_re, self.getCurrentLine()): self.line = self.getLine() self.vpos = 0 break line = self.getLine() self.cup() if line == self.getLine(): self.undo_mgr.undo_transaction() break if self.getLine() != o_line or not self.isMark(): if self.isMark(): self.mark_lines() self.mark_lines() return ret_ch
Ancestors (in MRO)
- StreamEditor
- Editor
- builtins.object
Class variables
var modes
Static methods
def __init__(
self, par, scr, name, stream, select=False, line_re=None, follow=False, wait=False, workfile=None)
takes parent curses screen, screen to render to, name for stream, stream to read in, and select to indicate if line selection is requested
def __init__(self, par, scr, name, stream, select = False, line_re = None, follow = False, wait = False, workfile=None ): """ takes parent curses screen, screen to render to, name for stream, stream to read in, and select to indicate if line selection is requested """ self.select = select self.line_re = line_re self.follow = follow self.wait = wait self.o_nlines = 0 if workfile: self.sfile = workfile else: self.sfile = StreamFile(name,stream,self.wait) Editor.__init__(self, par, scr, self.sfile.getFilename(), self.sfile)
def addstr(
self, row, col, str, attr=0)
write properly encoded string to screen location
def addstr(self,row,col,str,attr = curses.A_NORMAL): """ write properly encoded string to screen location """ try: return self.scr.addstr(row,col,codecs.encode(str,"utf-8"),attr) except: return 0
def applyUndo(
self, *args)
called by undo to unwind one undo action
def applyUndo(self,*args): """ called by undo to unwind one undo action """ ( self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap ) = args self.invalidate_screen() self.invalidate_mark()
def backspace(
self)
delete a character at the cursor and move back one character
def backspace(self): """ delete a character at the cursor and move back one character """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return line = self.getLine() pos = self.getPos() if pos: if pos <= self.getLength(line): self.goto(line,pos-1) self.delc() else: self.goto(line,pos-1) elif line: pos = self.getLength(line-1)-1 self.goto(line-1,pos) self.delc()
def btab(
self)
remove white space to the previous tab stop, or shift the line back to the previous tab stop
def btab(self): """ remove white space to the previous tab stop, or shift the line back to the previous tab stop """ self.pushUndo() if self.isMark() and self.line_mark: mark_line_start = self.mark_line_start mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.deltab( mark_line_start, self.workfile.get_tab_stop(0), False ) mark_line_start += 1 else: self.deltab( self.getLine(), self.getPos() )
def cdown(
self, rept=1)
go forward one or rept lines in the file
def cdown(self,rept = 1): """ go forward one or rept lines in the file """ self.pushUndo() self.invalidate_mark() while rept: if self.vpos < min((self.numLines(True)-1)-self.line,(self.max_y-2)): self.vpos += 1 elif self.line <= self.numLines(True)-self.max_y: self.line += 1 self.invalidate_screen() rept = rept - 1 self.goto(self.getLine(),self.getPos())
def cleft(
self, rept=1)
go back one or rept characters in the current line
def cleft(self,rept = 1): """ go back one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if pos >= rept: self.goto(line,pos-rept) return if self.wrap: if line: offset = self.getLength(line-1)-(rept-pos) self.goto(line-1,offset) else: self.goto(line,0)
def close(
self)
close and shut down the stream file
def close(self): """ close and shut down the stream file """ if self.sfile: self.sfile.close() self.sfile = None
def copy_marked(
self, delete=False, nocopy=False)
copy the marked text to the clipboard, delete== True means cut, nocopy == True will just delete
def copy_marked(self,delete=False,nocopy = False): """ copy the marked text to the clipboard, delete== True means cut, nocopy == True will just delete """ if not self.isMark(): return self.pushUndo() cp = self.get_marked(delete,nocopy) if cp and not (delete and nocopy): clipboard.clip_type = cp[0] clipboard.clip = cp[1]
def cr(
self)
insert a carriage return, split the current line at cursor
def cr(self): """ insert a carriage return, split the current line at cursor """ self.pushUndo() orig = self.getContent(self.getLine(),self.getPos(),True) self.workfile.replaceLine(self.getLine(),orig[0:self.getPos()]) self.workfile.insertLine(self.getLine()+1,orig[self.getPos():]) self.rewrap() self.goto(self.getLine()+1,0)
def cright(
self, rept=1)
go forward one or rept characters in the current line
def cright(self,rept = 1): """ go forward one or rept characters in the current line """ self.pushUndo() pos = self.getPos() line = self.getLine() if self.wrap: llen = self.getLength(line) if pos + rept < llen: self.goto(line,pos+rept) return if line < self.numLines()-1: self.goto(line+1,llen-(pos+rept)) return self.goto(line,llen) else: self.goto(line,pos+rept)
def cup(
self)
go back one line in the file
def cup(self): """ go back one line in the file """ self.pushUndo() self.invalidate_mark() if self.vpos: self.vpos -= 1 elif self.line: self.line -= 1 self.invalidate_screen() self.goto(self.getLine(),self.getPos())
def delc(
self)
deletes one character at the cursor position
def delc(self): """ deletes one character at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block instead and return return orig = self.getContent(self.getLine()) offset = self.getPos() if offset > len(orig): return elif offset == len(orig): next_idx = self.getLine()+1 if next_idx > self.numLines(): return next = self.getContent(next_idx) orig = orig[0:offset] + next self.workfile.replaceLine(self.getLine(),orig) self.workfile.deleteLine(next_idx) else: orig = orig[0:offset]+orig[offset+1:] self.workfile.replaceLine(self.getLine(),orig) self.rewrap()
def deltab(
self, line, pos, move_cursor=True)
remove a tab from the line at position provided optionally move the cursor
def deltab(self, line, pos, move_cursor = True ): """ remove a tab from the line at position provided optionally move the cursor """ orig = self.getContent(line,pos+1,True) idx = pos start = 0 stop = 0 while idx: while idx and orig[idx] != ' ': idx -= 1 start = idx stop = self.workfile.get_tab_stop(idx,True) while idx and idx >= stop: if orig[idx] != ' ': break idx -= 1 else: if start > stop: break if start > stop: orig = orig[0:stop]+orig[start+1:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop)
def draw_cursor(
self)
worker function to draw the current cursor position
def draw_cursor(self): """ worker function to draw the current cursor position """ if self.show_cursor: line = self.getLine() pos = self.getPos() if pos < self.getLength(line): cursor_ch = self.getContent(line)[pos] else: cursor_ch = ' ' sc_line,sc_pos = self.window_pos(line,pos) self.addstr(sc_line,sc_pos,cursor_ch,curses.A_REVERSE)
def draw_mark(
self)
worker function to draw the marked section of the file
def draw_mark(self): """ worker function to draw the marked section of the file """ if not self.isMark(): return (mark_top,mark_left) = self.scrPos(self.mark_line_start,self.mark_pos_start) mark_line_start = mark_top mark_pos_start = mark_left mark_right = self.getPos(True) mark_bottom = self.getLine(True) if (self.rect_mark or self.line_mark or (self.span_mark and mark_top == mark_bottom)) and mark_left > mark_right: mark = mark_left mark_left = mark_right mark_right = mark if mark_top > mark_bottom: mark = mark_bottom mark_bottom = mark_top mark_top = mark if mark_top < self.line: mark_top = self.line if self.span_mark: mark_left = 0 mark_right = mark_right + 1 s_left = mark_left - self.left s_left = max(0,s_left) s_right = mark_right - self.left s_right = min(self.max_x,s_right) s_top = (mark_top - self.line)+1 s_top = max(1,s_top) s_bottom = (mark_bottom - self.line)+1 s_bottom = min(self.max_y-1,s_bottom) mark_left = max(mark_left,self.left) mark_right = min(mark_right,self.left+self.max_x) if self.line_mark: s_right = self.max_x mark_right = self.left+s_right mark_left = max(mark_left,self.left) if s_top == s_bottom: if s_right > s_left: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) elif self.rect_mark: if mark_top < self.line: mark_top = self.line while s_top <= s_bottom: self.addstr(s_top, s_left, self.getContent(mark_top, mark_right, True, True)[mark_left:mark_right], curses.A_REVERSE) s_top += 1 mark_top += 1 elif self.span_mark: cur_line = mark_top while s_top <= s_bottom: if cur_line == mark_top: offset = s_left width = self.max_x-offset self.addstr(s_top, offset, self.getContent(cur_line, self.left+offset+width, True, True)[self.left+offset:self.left+offset+width], curses.A_REVERSE) elif cur_line == mark_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.getPos(True), True, True)[self.left:self.getPos(True)+1], curses.A_REVERSE) else: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1 elif self.line_mark: cur_line = mark_top while s_top <= s_bottom: self.addstr(s_top, 0, self.getContent(cur_line, self.left+self.max_x, True, True)[self.left:self.left+self.max_x], curses.A_REVERSE) s_top += 1 cur_line += 1
def end(
self)
once go to end of line, twice end of page, thrice end of file
def end(self): """ once go to end of line, twice end of page, thrice end of file """ self.pushUndo() if self.cmd_id == cmd_names.CMD_END and self.prev_cmd == cmd_names.CMD_END: self.end_count += 1 self.end_count = self.end_count % 3 else: self.end_count = 0 if self.end_count == 0: self.endln() elif self.end_count == 1: self.endpg() self.endln() elif self.end_count == 2: self.endfile() self.endln()
def endfile(
self)
go to the end of the file
def endfile(self): """ go to the end of the file """ self.pushUndo() ldisp = (self.numLines(True)-1)-self.line if ldisp < self.max_y-2: return self.line = (self.numLines(True)-1) - (self.max_y-2) self.vpos = min(self.max_y-2,ldisp) self.invalidate_screen()
def endln(
self)
go to the end of a line
def endln(self): """ go to the end of a line """ self.pushUndo() self.invalidate_mark() orig = self.getContent(self.getLine()) offset = len(orig) self.goto(self.getLine(),offset)
def endpg(
self)
go to the end of a page
def endpg(self): """ go to the end of a page """ self.pushUndo() self.invalidate_mark() ldisp = (self.numLines(True)-1)-self.line self.vpos = min(self.max_y-2,ldisp)
def filePos(
self, line, pos)
translate display line, pos to file line, pos
def filePos(self, line, pos ): """ translate display line, pos to file line, pos """ if self.wrap: if line < len(self.wrap_lines): return (self.wrap_lines[line][0],self.wrap_lines[line][1]+pos) else: return (self.numLines()+(line-len(self.wrap_lines)),pos) else: return (line,pos)
def flushChanges(
self)
flush change tracking to show we're done updating
def flushChanges( self ): """ flush change tracking to show we're done updating """ if self.workfile: self.workfile.flushChanges(self)
def getContent(
self, line, pad=0, trim=False, display=False)
get a line from the file
def getContent(self, line, pad = 0, trim= False, display=False ): """ get a line from the file """ if self.wrap: if display: orig = "" if line < len(self.wrap_lines): orig = self.workfile.getLine(self.wrap_lines[line][0])[self.wrap_lines[line][1]:self.wrap_lines[line][2]] if trim: orig = orig.rstrip() if pad > len(orig): orig = orig + ' '*(pad-len(orig)) return orig orig = self.workfile.getLine(line,pad,trim) return orig
def getCurrentLine(
self, display=False)
returns the current line in the file
def getCurrentLine(self,display=False): """ returns the current line in the file """ return self.getContent(self.getLine(display),display)
def getFilename(
self)
return the filename for this editor
def getFilename(self): """ return the filename for this editor """ return self.workfile.getFilename()
def getLength(
self, line, display=False)
get the length of a line
def getLength(self, line, display=False ): """ get the length of a line """ length = 0 if self.wrap and display: if line < len(self.wrap_lines): length = self.workfile.length(self.wrap_lines[line][0]) else: length = self.workfile.length(line) return length
def getLine(
self, display=False)
get the line that we're on in the current file
def getLine(self,display=False): """ get the line that we're on in the current file """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,0) return r_line return self.line+self.vpos
def getModref(
self)
return the current modref of this editor
def getModref(self): """ return the current modref of this editor """ return self.workfile.getModref()
def getPos(
self, display=False)
get the character position in the current line that we're at
def getPos(self,display=False): """ get the character position in the current line that we're at """ if self.wrap: if not display: r_line,r_pos = self.filePos(self.line+self.vpos,self.left+self.pos) return r_pos return self.left+self.pos
def getUndoMgr(
self)
get the undo manager that we're using
def getUndoMgr(self): """ get the undo manager that we're using """ return self.undo_mgr
def getWorkfile(
self)
return the workfile that this editor is attached to
def getWorkfile(self): """ return the workfile that this editor is attached to """ return self.workfile
def get_marked(
self, delete=False, nocopy=False)
returns marked text as tuple ( cliptype, [list of clipped] ) returns () if no mark
def get_marked(self, delete=False, nocopy = False): """ returns marked text as tuple ( cliptype, [list of clipped] ) returns () if no mark """ if not self.isMark(): return () self.pushUndo() if delete: self.invalidate_screen() mark_pos_start = self.mark_pos_start mark_line_start = self.mark_line_start mark_pos_end = self.getPos() mark_line_end = self.getLine() if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark elif mark_line_start == mark_line_end and mark_pos_start > mark_pos_end: mark = mark_pos_start mark_pos_start = mark_pos_end mark_pos_end = mark clip = [] clip_type = clipboard.LINE_CLIP line_idx = mark_line_start if self.line_mark: if not nocopy: clip_type = clipboard.LINE_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx)) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.rewrap() elif self.span_mark: if not nocopy: clip_type = clipboard.SPAN_CLIP if line_idx == mark_line_end: clip.append(self.getContent(line_idx)[mark_pos_start:mark_pos_end+1]) else: clip.append(self.getContent(line_idx)[mark_pos_start:]+'\n') line_idx += 1 while line_idx < mark_line_end: clip.append(self.getContent(line_idx)+'\n') line_idx += 1 clip.append(self.getContent(line_idx)[0:mark_pos_end+1]) if delete: line_idx = mark_line_start if line_idx == mark_line_end: orig = self.getContent(line_idx) orig = orig[0:mark_pos_start] + orig[mark_pos_end+1:] self.workfile.replaceLine(line_idx,orig) self.rewrap() else: first_line = self.getContent(mark_line_start) last_line = self.getContent(mark_line_end) while line_idx <= mark_line_end: self.workfile.deleteLine(mark_line_start) line_idx += 1 self.workfile.insertLine(mark_line_start,first_line[0:mark_pos_start] + last_line[mark_pos_end+1:]) self.rewrap() elif self.rect_mark: if not nocopy: clip_type = clipboard.RECT_CLIP while line_idx <= mark_line_end: clip.append(self.getContent(line_idx,mark_pos_end,True)[mark_pos_start:mark_pos_end+1]) line_idx += 1 if delete: line_idx = mark_line_start while line_idx <= mark_line_end: orig = self.getContent(line_idx,mark_pos_end,True) self.workfile.replaceLine(line_idx,orig[0:mark_pos_start]+orig[mark_pos_end+1:]) line_idx += 1 # sync the x clipboard self.transfer_clipboard() if self.line_mark: self.line_mark = False if self.rect_mark: self.rect_mark = False if self.span_mark: self.span_mark = False if delete: self.goto(mark_line_start,mark_pos_start) self.invalidate_screen() return (clip_type, clip)
def goto(
self, line, pos)
goto a line in the file and position the cursor to pos offset in the line
def goto(self,line, pos ): """ goto a line in the file and position the cursor to pos offset in the line """ self.pushUndo() self.invalidate_mark() if line < 0: line = 0 if pos < 0: pos = 0 (line,pos) = self.scrPos(line,pos) if line >= self.line and line <= self.line+(self.max_y-2): self.vpos = line - self.line elif line < self.line: self.line = line self.vpos = 0 self.invalidate_screen() elif line > self.line+(self.max_y-2): self.line = line - (self.max_y-2) self.vpos = (self.max_y-2) self.invalidate_screen() if pos >= self.left and pos < self.left+(self.max_x-1): self.pos = pos - self.left elif pos >= self.left+(self.max_x-1): self.left = pos-(self.max_x-1) self.pos = self.max_x-1 self.invalidate_screen() else: self.left = pos self.pos = 0 self.invalidate_screen()
def handle(
self, ch)
override normal keystroke handling if in select mode and move about doing selection and return on enter
def handle(self,ch): """ override normal keystroke handling if in select mode and move about doing selection and return on enter """ if self.follow: nlines = self.numLines() if self.o_nlines != nlines: self.endfile() self.o_nlines = nlines if ch in keymap.keydef_map and keymap.keydef_map[ch][-1] == keytab.KEYTAB_CTRLF: self.follow = not self.follow return keytab.KEYTAB_NOKEY elif not self.select: return Editor.handle(self,ch) o_line = self.getLine() if isinstance(ch,int): ch = keymap.get_keyseq( self.scr, ch ) ret_ch = keytab.KEYTAB_NOKEY direction = 0 if ch in [keytab.KEYTAB_F02,keytab.KEYTAB_F04,keytab.KEYTAB_F10,keytab.KEYTAB_F01,keytab.KEYTAB_RESIZE, keytab.KEYTAB_CR, keytab.KEYTAB_BTAB, keytab.KEYTAB_TAB, keytab.KEYTAB_ESC]: ret_ch = ch elif ch == keytab.KEYTAB_UP: self.cup() direction = -1 elif ch == keytab.KEYTAB_DOWN: self.cdown() direction = 1 elif ch == keytab.KEYTAB_LEFT: self.scroll_left() elif ch == keytab.KEYTAB_RIGHT: self.scroll_right() elif ch == keytab.KEYTAB_BACKSPACE: direction = -1 self.cup() elif ch == keytab.KEYTAB_HOME: self.pushUndo() self.left = 0 self.pos = 0 self.line = 0 self.vpos = 0 direction = 1 elif ch == keytab.KEYTAB_END: self.endfile() direction = -1 elif ch == keytab.KEYTAB_PAGEUP: self.pageup() direction = -1 elif ch == keytab.KEYTAB_PAGEDOWN: self.pagedown() direction = 1 elif ch == keytab.KEYTAB_F05: self.prmt_search() direction = 1 elif ch == keytab.KEYTAB_F17: # shift f5: self.prmt_search(False) direction = -1 elif ch == keytab.KEYTAB_F03: self.prmt_searchagain() if self.last_search_dir: direction = 1 else: direction = -1 if self.line_re and direction: if direction > 0: while True: if re.search(self.line_re, self.getCurrentLine()): self.line = self.getLine() self.vpos = 0 break line = self.getLine() self.cdown() if line == self.getLine(): self.undo_mgr.undo_transaction() break elif direction < 0: while True: if re.search(self.line_re, self.getCurrentLine()): self.line = self.getLine() self.vpos = 0 break line = self.getLine() self.cup() if line == self.getLine(): self.undo_mgr.undo_transaction() break if self.getLine() != o_line or not self.isMark(): if self.isMark(): self.mark_lines() self.mark_lines() return ret_ch
def has_changes(
self)
return true if there are any pending changes
def has_changes(self): """ return true if there are any pending changes """ return self.workfile.hasChanges(self)
def home(
self)
once to to start of line, twice start of page, thrice start of file
def home(self): """ once to to start of line, twice start of page, thrice start of file """ self.pushUndo() self.invalidate_mark() if self.cmd_id == cmd_names.CMD_HOME and self.prev_cmd == cmd_names.CMD_HOME: self.home_count += 1 self.home_count = self.home_count % 3 else: self.home_count = 0 if self.home_count == 0: self.goto(self.getLine(),0) elif self.home_count == 1: self.vpos = 0 elif self.home_count == 2: self.line = 0 self.invalidate_screen()
def insert(
self, c)
insert a character or string at the cursor position
def insert(self, c ): """ insert a character or string at the cursor position """ self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert orig = self.getContent(self.getLine()).rstrip() offset = self.getPos() pad = "" if offset > len(orig): pad = " "*(offset - len(orig)) orig = orig[0:offset] + pad + c + orig[offset:] insert_line = self.getLine() self.workfile.replaceLine(insert_line,orig) self.rewrap() self.goto(insert_line,offset+len(c))
def instab(
self, line, pos, move_cursor=True)
insert a tab at a line and position
def instab(self, line, pos, move_cursor = True ): """ insert a tab at a line and position """ orig = self.getContent(line,pos,True) stop = self.workfile.get_tab_stop(pos) orig = orig[0:pos] + ' '*(stop-(pos)) + orig[pos:] self.workfile.replaceLine(line,orig) self.rewrap() if move_cursor: self.goto(line,stop)
def invalidate_after_cursor(
self)
touch all the lines from the current position to the end of the screen
def invalidate_after_cursor(self): """ touch all the lines from the current position to the end of the screen """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(self.getLine(),line+self.max_y)
def invalidate_all(
self)
touch all the lines in the file so everything will redraw
def invalidate_all(self): """ touch all the lines in the file so everything will redraw """ self.workfile.touchLine(0,self.workfile.numLines())
def invalidate_mark(
self)
touch the marked lines so that they'll redraw when we change the shape of the mark or do a copy or paste
def invalidate_mark(self): """ touch the marked lines so that they'll redraw when we change the shape of the mark or do a copy or paste """ if self.isMark(): self.workfile.touchLine(self.mark_line_start, self.getLine()) if self.search_mark: self.span_mark = False self.search_mark = False
def invalidate_screen(
self)
touch all the lines on the screen so everything will redraw
def invalidate_screen(self): """ touch all the lines on the screen so everything will redraw """ line,pos = self.filePos(self.line,self.left) self.workfile.touchLine(line,line+self.max_y)
def isChanged(
self)
returns true if the file we're working on has unsaved changes
def isChanged(self): """ returns true if the file we're working on has unsaved changes """ return self.workfile.isChanged()
def isLineChanged(
self, line, display=True)
return true if line is changed for the current revisions
def isLineChanged(self, line, display=True ): """ return true if line is changed for the current revisions """ if self.workfile: if display: return self.workfile.isLineChanged( self, self.filePos(line,0)[0]) else: return self.workfile.isLineChanged( self, line ) else: return True
def isMark(
self)
returns true if there is a mark set
def isMark(self): """ returns true if there is a mark set """ return (self.line_mark or self.span_mark or self.rect_mark or self.search_mark)
def main(
self, blocking=True, start_ch=None)
main driver loop for editor, if blocking = False exits on each keystroke to allow embedding, start_ch is a character read externally that hould be processed on startup
def main(self,blocking = True, start_ch = None): """ main driver loop for editor, if blocking = False exits on each keystroke to allow embedding, start_ch is a character read externally that hould be processed on startup """ curses.curs_set(0) self.rewrap() self.scr.nodelay(1) self.scr.notimeout(0) self.scr.timeout(0) while (1): if not self.scr: return 27 if not self.mode: for m in Editor.modes: if m.detect_mode(self): self.mode = m self.getWorkfile().set_tabs(m.get_tabs(self)) break else: self.mode = None self.redraw() if start_ch: ch = start_ch start_ch = None else: ch = keymap.getch(self.scr) try: self.undo_mgr.new_transaction() if self.mode: ch = self.mode.handle(self,ch) modref = self.workfile.getModref() ret_seq = self.handle(ch) if self.wrap and modref != self.workfile.getModref(): self.rewrap() if ret_seq or not blocking: return ret_seq except ReadOnlyError as e: message(self.parent,"Read Only File Error","Changes not allowed.") if not blocking: return keytab.KEYTAB_REFRESH
def mark_lines(
self)
mark whole lines
def mark_lines(self): """ mark whole lines """ self.pushUndo() self.invalidate_mark() if not self.line_mark: self.line_mark = True self.span_mark = False self.rect_mark = False self.mark_pos_start = 0 self.mark_line_start = self.getLine() else: self.line_mark = False
def mark_rect(
self)
mark a rectangular or column selection across lines
def mark_rect(self): """ mark a rectangular or column selection across lines """ # no column cut in wrapped mode, it doesn't make sense if self.wrap: return self.pushUndo() self.invalidate_mark() if not self.rect_mark: self.rect_mark = True self.span_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.rect_mark = False
def mark_span(
self)
mark a span of characters that can start and end in the middle of a line
def mark_span(self): """ mark a span of characters that can start and end in the middle of a line """ self.pushUndo() self.invalidate_mark() if not self.span_mark: self.span_mark = True self.rect_mark = False self.line_mark = False self.mark_pos_start = self.getPos() self.mark_line_start = self.getLine() else: self.span_mark = False
def move(
self)
update the previous cursor position from the current
def move(self): """ update the previous cursor position from the current """ self.prev_pos = (self.getLine(),self.getPos())
def next_word(
self)
scan left until you get to the previous word
def next_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos < len(orig): if orig[pos] == ' ': while pos < len(orig) and orig[pos] == ' ': pos += 1 else: while pos < len(orig) and orig[pos] != ' ': pos += 1 while pos < len(orig) and orig[pos] == ' ': pos += 1 else: pos = len(orig) self.goto(self.getLine(),pos)
def numLines(
self, display=False)
get the number of lines in the editor
def numLines(self,display=False): """ get the number of lines in the editor """ if self.wrap and display: return len(self.wrap_lines) return self.workfile.numLines()
def pagedown(
self)
go forward one page in the file
def pagedown(self): """ go forward one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line + (self.max_y-2) if offset > self.numLines(True)-1: return self.line = offset ldisp = (self.numLines(True)-1)-self.line if self.vpos > ldisp: self.vpos = ldisp self.invalidate_screen()
def pageup(
self)
go back one page in the file
def pageup(self): """ go back one page in the file """ self.pushUndo() self.invalidate_mark() offset = self.line - (self.max_y-2) if offset < 0: offset = 0 self.line = offset self.invalidate_screen()
def paste(
self)
paste the current clip at the cursor position
def paste(self): """ paste the current clip at the cursor position """ if clipboard.clip: # no column cut or paste when in wrap mode if self.wrap and clipboard.clip_type == clipboard.RECT_CLIP: return self.pushUndo() if self.isMark(): self.copy_marked(True,True) # delete the marked block first then insert if clipboard.clip_type == clipboard.LINE_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: self.workfile.insertLine(target,line) target += 1 self.rewrap() self.goto(target,pos) elif clipboard.clip_type == clipboard.SPAN_CLIP: target = self.getLine() pos = self.getPos() idx = 0 for line in clipboard.clip: orig = self.getContent(target,pos,True) if (not line) or line[-1] == '\n': line = line.rstrip() if not idx: self.workfile.replaceLine(target,orig[0:pos]+line) self.workfile.insertLine(target+1,orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) target += 1 else: self.workfile.insertLine(target,line) self.rewrap() self.goto(target, len(line)) target += 1 else: if not idx: self.workfile.replaceLine(target,orig[0:pos]+line+orig[pos:]) self.rewrap() self.goto(target, pos+len(line)) else: self.workfile.replaceLine(target,line+orig) self.rewrap() self.goto(target, len(line)) idx += 1 elif clipboard.clip_type == clipboard.RECT_CLIP: target = self.getLine() pos = self.getPos() for line in clipboard.clip: orig = self.getContent(target,self.getPos(),True) self.workfile.replaceLine(target,orig[0:self.getPos()]+line+orig[self.getPos():]) target += 1 self.rewrap() self.goto(target,pos)
def prevPos(
self)
get the previous cursor position
def prevPos(self): """ get the previous cursor position """ return self.prev_pos
def prev_word(
self)
scan left until you get to the previous word
def prev_word( self ): """ scan left until you get to the previous word """ self.pushUndo() orig = self.getContent(self.getLine()).rstrip() pos = self.getPos() if pos >= len(orig): pos = len(orig)-1 if pos and pos < len(orig): pos -= 1 while pos and orig[pos] == ' ': pos -= 1 while pos and orig[pos-1] != ' ': pos -= 1 elif pos >= len(orig): pos = len(orig) else: pos = 0 self.goto(self.getLine(),pos)
def prmt_goto(
self)
prompt for a line to go to and go there
def prmt_goto(self): """ prompt for a line to go to and go there """ self.invalidate_screen() goto_line = prompt(self.parent,"Goto","Enter line number 0-%d :"%(self.numLines()-1),10,name="goto") if goto_line: self.goto(int(goto_line),self.getPos())
def prmt_replace(
self)
prompt for search pattern and replacement string, then confirm replacment or replace all for the occurrences until no more are found
def prmt_replace(self): """ prompt for search pattern and replacement string, then confirm replacment or replace all for the occurrences until no more are found """ (pattern,rep) = replace(self.parent) if pattern and rep: found = self.search(pattern) replace_all = False do_replace = False while found: self.redraw() self.scr.refresh() if not replace_all: answer = confirm_replace(self.parent) self.invalidate_screen() if answer == 1: do_replace = True elif answer == 2: do_replace = False elif answer == 3: replace_all = True elif answer == 4: message(self.parent,"Canceled","Replace canceled.") return if do_replace or replace_all: self.insert(rep) found = self.searchagain() else: message(self.parent,"Replace","Pattern not found.") self.invalidate_screen()
def prmt_search(
self, down=True)
prompt for a search string then search for it and either put up a message that it was not found or position the cursor to the occurrance
def prmt_search(self,down=True): """ prompt for a search string then search for it and either put up a message that it was not found or position the cursor to the occurrance """ self.invalidate_screen() if down: title = "Search Forward" else: title = "Search Backward" pattern = prompt(self.parent,title,"Pattern: ",-1,name="search") if pattern: if not self.search(pattern,down): message(self.parent,"Search","Pattern not found.")
def prmt_searchagain(
self)
search again and put up a message if no more are found
def prmt_searchagain(self): """ search again and put up a message if no more are found """ self.invalidate_screen() if not self.searchagain(): if self.isMark(): self.mark_span() message(self.parent,"Search","Pattern not found.")
def pushUndo(
self)
push an undo action onto the current transaction
def pushUndo(self): """ push an undo action onto the current transaction """ self.undo_mgr.get_transaction().push(self.applyUndo,(self.line, self.pos, self.vpos, self.left, self.prev_cmd, self.cmd_id, self.home_count, self.end_count, self.line_mark, self.span_mark, self.rect_mark, self.search_mark, self.mark_pos_start, self.mark_line_start, self.last_search, self.last_search_dir, clipboard.clip, clipboard.clip_type, self.show_cursor, self.focus, self.wrap))
def redraw(
self)
redraw the editor as needed
def redraw(self): """ redraw the editor as needed """ try: if not self.scr or keymap.is_playback(): return self.max_y,self.max_x = self.scr.getmaxyx() self.scr.keypad(1) if self.workfile.isChanged(): changed = "*" elif self.workfile.isReadOnly(): changed = "R" else: changed = " " if self.mode: changed = changed + " " + self.mode.name() filename = self.workfile.getFilename() if not self.showname: filename = "" status = "%d : %d : %d : %s : %s : %s"%(self.numLines(),self.getLine(),self.getPos(),changed,filename, "REC" if keymap.is_recording() else "PBK" if keymap.is_playback() else " " ) if len(status) < self.max_x: status += (self.max_x-len(status))*' ' if self.focus: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE|curses.A_BOLD) else: self.addstr(0,0,status[0:self.max_x],curses.A_REVERSE) # if the mode is rendering then don't do the default rendering as well mode_redraw = False if self.mode: mode_redraw = self.mode.redraw(self) if not mode_redraw: cursor_line,cursor_pos = self.window_pos(*self.prevPos()) y = 1 lidx = self.line while lidx < self.line+(self.max_y-1): try: line_changed = self.isLineChanged(lidx) is_cursor_line = (y == cursor_line) if line_changed or is_cursor_line: l = self.getContent(lidx,self.left+self.max_x,True,True) if line_changed: self.addstr(y,0,l[self.left:self.left+self.max_x]) else: self.addstr(y,cursor_pos,l[self.left+cursor_pos]) except Exception as e: pass y = y + 1 lidx = lidx + 1 self.draw_mark() self.move() self.draw_cursor() if mode_redraw: self.flushChanges() except: raise
def resize(
self)
resize the editor to fill the window
def resize(self): """ resize the editor to fill the window """ if self.scr: self.max_y,self.max_x = self.scr.getmaxyx() self.rewrap() bottom_y = max(min((self.numLines(True)-1)-self.line,(self.max_y-2)),0) if self.vpos > bottom_y: self.vpos = bottom_y right_x = self.max_x-1 if self.pos > right_x: self.left += self.pos-right_x self.pos = right_x self.invalidate_screen()
def rewrap(
self, force=False)
compute the wrapped line array
def rewrap(self, force = False): """ compute the wrapped line array """ if self.wrap and (force or self.workfile.getModref() != self.wrap_modref or self.wrap_width != self.max_x): self.wrap_modref = self.workfile.getModref() self.wrap_width = self.max_x self.wrap_lines = [] self.unwrap_lines = [] for l in range(0,self.workfile.numLines()): line_len = self.workfile.length(l) start = 0 self.unwrap_lines.append(len(self.wrap_lines)) if not line_len: self.wrap_lines.append((l,0,0)) else: while start < line_len: self.wrap_lines.append((l,start,min(line_len,start+self.wrap_width))) start += self.wrap_width self.invalidate_after_cursor()
def save(
self)
save the current buffer
def save(self): """ save the current buffer """ if self.workfile.isModifiedOnDisk(): if not confirm(self.parent, "File has changed on disk, overwrite?"): self.invalidate_screen() self.redraw() return self.workfile.save() self.undo_mgr.flush_undo() self.goto(self.getLine(),self.getPos()) self.invalidate_all() self.redraw() gc.collect()
def saveas(
self)
open the file dialog and enter or point to a file and then save this buffer to that path
def saveas(self): """ open the file dialog and enter or point to a file and then save this buffer to that path """ f = file_dialog.FileDialog(self.parent,"Save file as") choices = f.main() if choices and choices["file"]: self.workfile.save(os.path.join(choices["dir"],choices["file"])) self.undo_mgr.flush_undo() self.invalidate_all() gc.collect()
def scrPos(
self, line, pos)
translate file pos to screen pos
def scrPos(self, line, pos ): """ translate file pos to screen pos """ if self.wrap: nlines = len(self.unwrap_lines) if line >= nlines: r_line,r_pos = self.scrPos(self.numLines()-1,self.getLength(self.numLines()-1)-1) return (r_line+(line-self.numLines())+1,pos) sline = self.unwrap_lines[line] while sline < len(self.wrap_lines) and self.wrap_lines[sline][0] == line: if pos >= self.wrap_lines[sline][1] and pos < self.wrap_lines[sline][2]: return (sline,pos-self.wrap_lines[sline][1]) sline = sline + 1 else: return (sline-1,pos - self.wrap_lines[sline-1][1]) else: return (line,pos)
def scroll_left(
self)
scroll the page left without moving the current cursor position
def scroll_left(self): """ scroll the page left without moving the current cursor position """ self.pushUndo() if self.left: self.left -= 1 self.invalidate_screen()
def scroll_right(
self)
scroll the page right without moving the current cursor position
def scroll_right(self): """ scroll the page right without moving the current cursor position """ self.pushUndo() self.left += 1 self.invalidate_screen()
def search(
self, pattern, down=True, next=True)
search for a regular expression forward or back if next is set then skip one before matching
def search(self, pattern, down = True, next = True): """ search for a regular expression forward or back if next is set then skip one before matching """ self.pushUndo() self.invalidate_mark() self.last_search = pattern self.last_search_dir = down first_line = self.getLine() line = first_line if down: while line < self.numLines(): content = self.getContent(line) if line == first_line: content = content[self.getPos():] offset = self.getPos() else: offset = 0 match = None try: match = re.search(pattern,content) except: pass if match: if self.isMark(): self.mark_span() self.goto(line,match.start()+offset) self.mark_span() self.goto(line,match.end()+offset-1) self.search_mark = True return True line += 1 else: while line >= 0: content = self.getContent(line) if line == first_line: content = content[:self.getPos()] match = None try: match = re.search(pattern,content) except: pass last_match = None offset = 0 while match: last_match = match last_offset = offset offset += match.end() match = re.search(pattern,content[offset:]) if last_match: if self.isMark(): self.mark_span() self.goto(line,last_match.start()+last_offset) self.mark_span() self.goto(line,last_match.end()+last_offset-1) self.search_mark = True return True line -= 1 return False
def searchagain(
self)
repeat the previous search if any
def searchagain(self): """ repeat the previous search if any """ self.pushUndo() self.invalidate_mark() if self.isMark(): if not self.last_search_dir: self.goto(self.mark_line_start,self.mark_pos_start) self.mark_span() if self.last_search: return self.search(self.last_search,self.last_search_dir,True) else: return False
def setWin(
self, win)
install a new window to render to
def setWin(self,win): """ install a new window to render to """ self.scr = win
def setfocus(
self, state)
set this editor to have focus or not
def setfocus(self,state): """ set this editor to have focus or not """ old_focus_state = self.focus self.focus = state return old_focus_state
def showcursor(
self, state)
set flag to turn cursor on or off
def showcursor(self,state): """ set flag to turn cursor on or off """ old_cursor_state = self.show_cursor self.show_cursor = state return old_cursor_state
def tab(
self)
tab in the correct distance to the next tab stop
def tab(self): """ tab in the correct distance to the next tab stop """ self.pushUndo() if self.isMark() and self.line_mark: oline = self.getLine() opos = self.getPos() mark_line_start = self.mark_line_start mark_line_end = oline if mark_line_start > mark_line_end: mark = mark_line_start mark_line_start = mark_line_end mark_line_end = mark while mark_line_start <= mark_line_end: self.instab( mark_line_start, 0, False ) mark_line_start += 1 self.goto(oline,opos) else: self.instab( self.getLine(), self.getPos() )
def toggle_wrap(
self)
toggle wrapping for this editor
def toggle_wrap(self): """ toggle wrapping for this editor """ # don't toggle wrapping while we're marking a rectangle if self.rect_mark: return self.pushUndo() oline = self.getLine() opos = self.getPos() self.wrap = not self.wrap self.rewrap(True) self.invalidate_all() self.goto(oline,opos)
def transfer_clipboard(
self, from_xclip=False)
use xclip to transfer out clipboard to x or vice/versa
def transfer_clipboard(self, from_xclip = False): """ use xclip to transfer out clipboard to x or vice/versa """ if os.path.exists("/dev/clipboard"): if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for line in open("/dev/clipboard","r",buffering=1,encoding="utf-8"): clipboard.clip.append(line) else: cld = open("/dev/clipboard","w",buffering=0,encoding="utf-8") for line in clipboard.clip: cld.write(line) cld.close() elif os.path.exists("/usr/bin/xclip"): cmd = [ "xclip", ] if from_xclip: cmd += ["-out","-selection","clipboard"] else: cmd += ["-in","-selection","clipboard"] try: proc = subprocess.Popen( cmd, encoding="utf-8", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if from_xclip: clipboard.clip = [] clipboard.clip_type = clipboard.SPAN_CLIP for l in proc.stdout: clipboard.clip.append(l) else: for l in clipboard.clip: print(l.rstrip(), file=proc.stdin) proc.stdout.close() proc.stdin.close() proc.stderr.close() proc.wait() except: pass
def undo(
self)
undo the last transaction, actually undoes the open transaction and the prior closed one
def undo(self): """ undo the last transaction, actually undoes the open transaction and the prior closed one """ line = self.line left = self.left self.undo_mgr.undo_transaction() # undo the one we're in... probably empty self.undo_mgr.undo_transaction() # undo the previous one... probably not empty if self.line != line or self.left != left: self.invalidate_screen()
def window_pos(
self, line, pos)
def window_pos(self,line,pos): sc_line,sc_pos = self.scrPos(line,pos) return((sc_line-self.line)+1,sc_pos-self.left)
Instance variables
var follow
var line_re
var o_nlines
var select
var wait
class StreamFile
Class reads a stream to the end and writes it to a temp file which is opened and loaded read only, used for capturing the output of shell commands to a read-only editor
class StreamFile(EditFile): """ Class reads a stream to the end and writes it to a temp file which is opened and loaded read only, used for capturing the output of shell commands to a read-only editor """ def __init__(self,name,stream,wait=False): """ takes name which is a display name for this stream, stream is the input stream to read """ self.stream = stream self.stream_thread = None self.lines_lock = threading.Lock() self.wait = wait # store the filename self.filename = name # the root of the backup directory self.backuproot = EditFile.default_backuproot # set the default tab stops self.tabs = [ 4, 8 ] # set the changed flag to false self.changed = False # read only flag self.readonly = EditFile.default_readonly # undo manager self.undo_mgr = undo.UndoManager() # change manager self.change_mgr = changes.ChangeManager() # modification reference incremented for each change self.modref = 0 # the file object self.working = None # the lines in this file self.lines = [] # load the file self.load() def __del__(self): """ clean up stream thread and stream """ self.stream_thread = None EditFile.__del__(self) def open(self): """ override of the open method, starts a thread that reads the stream into a tempfile which then becomes the file for the editor """ if self.stream and not self.working: self.working = tempfile.NamedTemporaryFile(mode="w+") self.setReadOnly(True) self.stream_thread = StreamThread(self,self.stream) self.stream_thread.start_stream() if self.wait: self.stream_thread.wait() self.stream_thread = None return else: EditFile.open(self) def load(self): if self.stream and not self.working: self.open() return else: EditFile.load(self) def close(self): """ override of close method, make sure the stream gets closed """ if self.stream_thread: self.stream_thread.stop_stream() self.stream = None self.stream_thread = None elif self.stream: self.stream.close() self.stream = None EditFile.close(self) def save( self, filename = None ): """ save the file, if filename is passed it'll be saved to that filename and reopened """ if filename: if filename == self.filename and self.isReadOnly(): raise ReadOnlyError() try: self.lines_lock.acquire() o = open(filename,"w",buffering=1,encoding="utf-8") for l in self.lines: txt = l.getContent()+'\n' o.write(txt) o.close() finally: self.lines_lock.release() self.close() self.filename = filename self.load() else: if self.isReadOnly(): raise ReadOnlyError() def set_tabs(self, tabs): try: self.lines_lock.acquire() EditFile.set_tabs(self, tabs) finally: self.lines_lock.release() def numLines(self): try: self.lines_lock.acquire() return EditFile.numLines(self) finally: self.lines_lock.release() def length(self,line): try: self.lines_lock.acquire() return EditFile.length(self,line) finally: self.lines_lock.release() def getLine(self, line, pad = 0, trim = False ): try: self.lines_lock.acquire() return EditFile.getLine(self,line,pad,trim) finally: self.lines_lock.release() def getLines(self, line_start = 0, line_end = -1 ): try: self.lines_lock.acquire() return EditFile.getLines(self,line_start,line_end) finally: self.lines_lock.release()
Ancestors (in MRO)
- StreamFile
- EditFile
- builtins.object
Class variables
var default_backuproot
var default_readonly
Static methods
def __init__(
self, name, stream, wait=False)
takes name which is a display name for this stream, stream is the input stream to read
def __init__(self,name,stream,wait=False): """ takes name which is a display name for this stream, stream is the input stream to read """ self.stream = stream self.stream_thread = None self.lines_lock = threading.Lock() self.wait = wait # store the filename self.filename = name # the root of the backup directory self.backuproot = EditFile.default_backuproot # set the default tab stops self.tabs = [ 4, 8 ] # set the changed flag to false self.changed = False # read only flag self.readonly = EditFile.default_readonly # undo manager self.undo_mgr = undo.UndoManager() # change manager self.change_mgr = changes.ChangeManager() # modification reference incremented for each change self.modref = 0 # the file object self.working = None # the lines in this file self.lines = [] # load the file self.load()
def close(
self)
override of close method, make sure the stream gets closed
def close(self): """ override of close method, make sure the stream gets closed """ if self.stream_thread: self.stream_thread.stop_stream() self.stream = None self.stream_thread = None elif self.stream: self.stream.close() self.stream = None EditFile.close(self)
def deleteLine(
self, line)
delete a line, high level interface
def deleteLine( self, line ): """ delete a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line < len(self.lines): self._deleteLine(line)
def expand_tabs(
self, content)
expand tabs in a line
def expand_tabs(self, content ): """ expand tabs in a line """ idx = 0 while idx < len(content): if content[idx] == '\t': stop = self.get_tab_stop(idx) content = content[0:idx] + ' '*(stop-idx) + content[idx+1:] idx += (stop-idx) else: idx += 1 return content
def flushChanges(
self, view)
reset the change tracking for full screen redraw events
def flushChanges(self,view): """ reset the change tracking for full screen redraw events """ if self.change_mgr: self.change_mgr.flush(view)
def getFilename(
self)
get the filename for this file
def getFilename(self): """ get the filename for this file """ return self.filename
def getLine(
self, line, pad=0, trim=False)
get a line
def getLine(self, line, pad = 0, trim = False ): try: self.lines_lock.acquire() return EditFile.getLine(self,line,pad,trim) finally: self.lines_lock.release()
def getLines(
self, line_start=0, line_end=-1)
get a list of a range of lines
def getLines(self, line_start = 0, line_end = -1 ): try: self.lines_lock.acquire() return EditFile.getLines(self,line_start,line_end) finally: self.lines_lock.release()
def getModref(
self)
modref is a serial number that is incremented for each change to a file, used to detect changes externally
def getModref(self): """ modref is a serial number that is incremented for each change to a file, used to detect changes externally """ return self.modref
def getUndoMgr(
self)
returns our undo_manager
def getUndoMgr(self): """ returns our undo_manager """ return self.undo_mgr
def getWorking(
self)
return the file object
def getWorking(self): """ return the file object """ return self.working
def get_backup_dir(
base='~')
get the backup directory, create it if it doesn't exist
@staticmethod def get_backup_dir( base = "~" ): """ get the backup directory, create it if it doesn't exist """ base = os.path.expanduser(base) if not os.path.exists(base): base = os.path.expanduser("~") pedbackup = os.path.join(base,".pedbackup") if not os.path.exists(pedbackup): os.mkdir(pedbackup) return pedbackup
def get_tab_stop(
self, idx, before=False)
return the next tab stop before or after a given offset
def get_tab_stop(self, idx, before=False ): """ return the next tab stop before or after a given offset """ prev = 0 for stop in self.tabs: if stop > idx: if before: return prev else: return stop prev = stop incr = self.tabs[-1]-self.tabs[-2] while stop <= idx: prev = stop stop += incr if before: return prev else: return stop
def get_tabs(
self)
return the list of tab stops
def get_tabs(self): """ return the list of tab stops """ return self.tabs
def hasChanges(
self, view)
return true if there are pending screen updates
def hasChanges(self,view): """ return true if there are pending screen updates """ return self.change_mgr.has_changes(view)
def insertLine(
self, line, content)
insert a line, high level interface
def insertLine( self, line, content ): """ insert a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line >= len(self.lines): lidx = len(self.lines) while lidx <= line: self._appendLine(MemLine("")) lidx += 1 self._insertLine(line,MemLine(content))
def isChanged(
self)
true if there are unsaved changes, false otherwise
def isChanged(self): """ true if there are unsaved changes, false otherwise """ return self.changed
def isLineChanged(
self, view, line)
return true if a particular line is changed
def isLineChanged(self,view,line): """ return true if a particular line is changed """ if self.change_mgr and line < len(self.lines): return self.change_mgr.is_changed(view,line) else: return True
def isModifiedOnDisk(
self)
return true if the file we're editing has been modified since we started
def isModifiedOnDisk(self): """ return true if the file we're editing has been modified since we started """ if os.path.exists(self.filename): disk_stat = os.stat(self.filename) temp_stat = os.stat(self.working.name) return disk_stat.st_mtime > temp_stat.st_mtime else: return False
def isReadOnly(
self)
true if the file is read only, false otherwise
def isReadOnly(self): """ true if the file is read only, false otherwise """ return self.readonly
def length(
self, line)
return the length of the line
def length(self,line): try: self.lines_lock.acquire() return EditFile.length(self,line) finally: self.lines_lock.release()
def load(
self)
open the file and load the lines into the array
def load(self): if self.stream and not self.working: self.open() return else: EditFile.load(self)
def make_backup_dir(
filename, base='~')
make a backup directory under ~/.pedbackup for filename and return it's name
@staticmethod def make_backup_dir( filename, base = "~" ): """ make a backup directory under ~/.pedbackup for filename and return it's name """ pedbackup = EditFile.get_backup_dir( base ) (filepath,rest) = os.path.split(os.path.abspath(filename)) for part in filepath.split("/"): if part: pedbackup = os.path.join(pedbackup,part) if not os.path.exists(pedbackup): os.mkdir(pedbackup) return os.path.join(pedbackup,rest)
def numLines(
self)
get the number of lines in this file
def numLines(self): try: self.lines_lock.acquire() return EditFile.numLines(self) finally: self.lines_lock.release()
def open(
self)
override of the open method, starts a thread that reads the stream into a tempfile which then becomes the file for the editor
def open(self): """ override of the open method, starts a thread that reads the stream into a tempfile which then becomes the file for the editor """ if self.stream and not self.working: self.working = tempfile.NamedTemporaryFile(mode="w+") self.setReadOnly(True) self.stream_thread = StreamThread(self,self.stream) self.stream_thread.start_stream() if self.wait: self.stream_thread.wait() self.stream_thread = None return else: EditFile.open(self)
def replaceLine(
self, line, content)
replace a line, high level interface
def replaceLine( self, line, content ): """ replace a line, high level interface """ if self.isReadOnly(): raise ReadOnlyError() if line >= len(self.lines): lidx = len(self.lines) while lidx <= line: self._appendLine(MemLine("")) lidx += 1 self._replaceLine(line, MemLine(content))
def save(
self, filename=None)
save the file, if filename is passed it'll be saved to that filename and reopened
def save( self, filename = None ): """ save the file, if filename is passed it'll be saved to that filename and reopened """ if filename: if filename == self.filename and self.isReadOnly(): raise ReadOnlyError() try: self.lines_lock.acquire() o = open(filename,"w",buffering=1,encoding="utf-8") for l in self.lines: txt = l.getContent()+'\n' o.write(txt) o.close() finally: self.lines_lock.release() self.close() self.filename = filename self.load() else: if self.isReadOnly(): raise ReadOnlyError()
def setFilename(
self, filename)
set the filename for this object
def setFilename(self,filename): """ set the filename for this object """ self.filename = filename
def setReadOnly(
self, flag=True)
mark this file as read only
def setReadOnly(self,flag = True): """ mark this file as read only """ self.readonly = flag
def setUndoMgr(
self, undo_mgr)
sets the undo manager object for this EditFile, undo manager is used to record undo records to enable undo in the editor
def setUndoMgr(self,undo_mgr): """ sets the undo manager object for this EditFile, undo manager is used to record undo records to enable undo in the editor """ self.undo_mgr = undo_mgr
def set_tabs(
self, tabs)
set the tab stops for this file to something new
def set_tabs(self, tabs): try: self.lines_lock.acquire() EditFile.set_tabs(self, tabs) finally: self.lines_lock.release()
def touchLine(
self, line_start, line_end)
touch a line so it will redraw
def touchLine(self, line_start, line_end): """ touch a line so it will redraw""" if self.change_mgr: self.change_mgr.changed(min(line_start,line_end),max(line_start,line_end))
Instance variables
var backuproot
var change_mgr
var changed
var filename
var lines
var lines_lock
var modref
var readonly
var stream
var stream_thread
var tabs
var undo_mgr
var wait
var working
class StreamThread
Thread to read from a stream blocking as needed adding lines to the owning EditFile object
class StreamThread: """ Thread to read from a stream blocking as needed adding lines to the owning EditFile object """ def __init__(self, ef, stream ): self.ef = ef self.stream = stream self.thread = None self.read_worker_stop = False def __del__( self ): self.stop_stream() def start_stream( self ): self.thread = threading.Thread(target = self.read_worker) self.thread.start() def wait(self): if self.thread: self.thread.join() def stop_stream( self ): self.read_worker_stop = True if self.thread and self.thread.is_alive(): self.thread.join() if self.stream: self.stream.close() self.stream = None self.thread = None self.read_worker_stop = False def read_worker( self ): pos = 0 lidx = 0 while not self.read_worker_stop: line = self.stream.readline() if not line: break try: self.ef.lines_lock.acquire() self.ef.modref += 1 self.ef.working.seek(0,2) self.ef.working.write(line) line = line.rstrip() self.ef.lines.append(FileLine(self.ef,pos,len(self.ef.expand_tabs(line)))) if self.ef.change_mgr: self.ef.change_mgr.changed(lidx,lidx) pos = self.ef.working.tell() lidx += 1 finally: self.ef.lines_lock.release() time.sleep( 0 ) try: self.ef.lines_lock.acquire() while len(self.ef.lines) and not self.ef.lines[-1].getContent().strip(): del self.ef.lines[-1] if not len(self.ef.lines): self.ef.lines.append(MemLine("")) finally: self.ef.lines_lock.release() self.ef.changed = False self.ef.modref = 0 if self.stream: self.stream.close() self.stream = None self.thread = None self.read_worker_stop = False
Ancestors (in MRO)
- StreamThread
- builtins.object
Static methods
def __init__(
self, ef, stream)
Initialize self. See help(type(self)) for accurate signature.
def __init__(self, ef, stream ): self.ef = ef self.stream = stream self.thread = None self.read_worker_stop = False
def read_worker(
self)
def read_worker( self ): pos = 0 lidx = 0 while not self.read_worker_stop: line = self.stream.readline() if not line: break try: self.ef.lines_lock.acquire() self.ef.modref += 1 self.ef.working.seek(0,2) self.ef.working.write(line) line = line.rstrip() self.ef.lines.append(FileLine(self.ef,pos,len(self.ef.expand_tabs(line)))) if self.ef.change_mgr: self.ef.change_mgr.changed(lidx,lidx) pos = self.ef.working.tell() lidx += 1 finally: self.ef.lines_lock.release() time.sleep( 0 ) try: self.ef.lines_lock.acquire() while len(self.ef.lines) and not self.ef.lines[-1].getContent().strip(): del self.ef.lines[-1] if not len(self.ef.lines): self.ef.lines.append(MemLine("")) finally: self.ef.lines_lock.release() self.ef.changed = False self.ef.modref = 0 if self.stream: self.stream.close() self.stream = None self.thread = None self.read_worker_stop = False
def start_stream(
self)
def start_stream( self ): self.thread = threading.Thread(target = self.read_worker) self.thread.start()
def stop_stream(
self)
def stop_stream( self ): self.read_worker_stop = True if self.thread and self.thread.is_alive(): self.thread.join() if self.stream: self.stream.close() self.stream = None self.thread = None self.read_worker_stop = False
def wait(
self)
def wait(self): if self.thread: self.thread.join()
Instance variables
var ef
var read_worker_stop
var stream
var thread