From 1b082d0e29741666ea25892744e0cc8fe6c9a9cc Mon Sep 17 00:00:00 2001 From: moculus Date: Tue, 5 Jun 2007 02:30:58 +0000 Subject: [PATCH] --HG-- branch : pmacs2 --- buffer2.py | 441 +++++++++++++++++++++++++++++++++++++++++++++++++ point2.py | 24 +++ window2.py | 470 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 935 insertions(+) create mode 100644 buffer2.py create mode 100644 point2.py create mode 100644 window2.py diff --git a/buffer2.py b/buffer2.py new file mode 100644 index 0000000..0cfc6cb --- /dev/null +++ b/buffer2.py @@ -0,0 +1,441 @@ +import md5, os, sets, shutil +import aes, regex +from point2 import Point + +# undo/redo stack constants +ACT_NORM = 0 +ACT_UNDO = 1 +ACT_REDO = 2 +STACK_LIMIT = 1024 + +# used for undo/redo stacks when text will need to be added back +class AddMove: + def __init__(self, buffer, p, lines): + self.buffer = buffer + self.p = p + self.lines = lines + def restore(self, act=ACT_UNDO): + assert act == ACT_UNDO or act == ACT_REDO + self.buffer.insert_lines(self.p, self.lines, act) + +# used for undo/redo stacks when text will need to be removed +class DelMove: + def __init__(self, buffer, p1, p2): + self.buffer = buffer + self.p1 = p1 + self.p2 = p2 + def restore(self, act): + assert act == ACT_UNDO or act == ACT_REDO + self.buffer.delete(self.p1, self.p2, act) + +# abstract class +class Buffer(object): + def __init__(self, nl='\n', stack_limit=STACK_LIMIT): + assert nl in ('\n', '\r', '\r\n'), "Invalid line ending" + self.lines = [""] + self.windows = [] + self.undo_stack = [] + self.redo_stack = [] + self.stack_limit = stack_limit + self.nl = nl + self.modified = False + + # basic file operation stuff + def _open_file_r(self, path): + path = os.path.realpath(path) + if not os.path.isfile(path): + raise Exception, "Path '%s' does not exist" % (path) + if not os.access(path, os.R_OK): + raise Exception, "Path '%s' cannot be read" % (path) + f = open(path, 'r') + return f + def _open_file_w(self, path): + if os.path.isfile(path): + raise Exception, "Path '%s' already exists" % (path) + d = os.path.dirname(path) + if not os.access(d, os.R_OK): + raise Exception, "Dir '%s' cannot be read" % (path) + if not os.access(d, os.W_OK): + raise Exception, "Dir '%s' cannot be written" % (path) + f = open(path, 'w') + return f + def _temp_path(self, path): + (dirname, basename) = os.path.split(path) + return os.path.join(dirname, ".__%s__pmacs" % (basename)) + + # undo/redo stack + def _stack_trim(self, stack): + if self.stack_limit: + while len(stack) > self.stack_limit: + stack.pop(0) + def add_to_stack(self, move, act): + if act == ACT_NORM: + self.redo_stack = [] + self.undo_stack.append(move) + self._stack_trim(self.undo_stack) + elif act == ACT_UNDO: + self.redo_stack.append(move) + self._stack_trim(self.redo_stack) + elif act == ACT_REDO: + self.undo_stack.append(move) + self._stack_trim(self.undo_stack) + else: + raise Exception, "Invalid act: %d" % (act) + def undo(self): + if len(self.undo_stack): + move = self.undo_stack.pop(-1) + move.restore(move, ACT_UNDO) + else: + raise Exception, "Nothing to Undo!" + def redo(self): + if len(self.redo_stack): + move = self.redo_stack.pop(-1) + move.restore(move, ACT_REDO) + else: + raise Exception, "Nothing to Redo!" + + # window-buffer communication + def add_window(self, w): + if w not in self.windows: + self.windows.append(w) + def remove_window(self, w): + if w in self.windows: + self.windows.remove(w) + def _region_add(self, p1, p2, lines, act): + move = DelMove(self, p1, p2) + self.add_to_stack(move, act) + for w in self.windows: + w.region_added(p1, lines) + def _region_del(self, p1, p2, lines, act): + move = AddMove(self, p1, lines) + self.add_to_stack(move, act) + for w in self.windows: + w.region_removed(p1, p2) + + # internal validation + def _validate_point(self, p): + self._validate_xy(p.x, p.y) + def _validate_xy(self, x, y): + assert y >= 0 and y < len(self.lines), \ + "xy1: %d >= 0 and %d < %d" % (y, y, len(self.lines)) + assert x >= 0 and x <= len(self.lines[y]), \ + "xy2: %d >= 0 and %d <= %d" % (x, x, len(self.lines[y])) + def _validate_y(self, y): + assert y >= 0 and y < len(self.lines), \ + "y: %d >= 0 and %d < %d" % (y, y, len(self.lines)) + + # deal with the actual logical document string + def num_chars(self): + n = 0 + for line in self.lines[:-1]: + n += len(line) + 1 + n += len(self.lines[-1]) + return n + def num_lines(self): + return len(self.lines) + def make_string(self, nl='\n'): + return nl.join(self.lines) + + # methods to be overridden by subclasses + def name(self): + return "Generic" + def close(self): + pass + def open(self): + pass + def changed(self): + return self.modified + def reload(self): + raise Exception, "%s reload: Unimplemented" % (self.name()) + def save_as(self, path, force=False): + # check to see if the path exists, and if we're prepared to overwrite it + # if yes to both, get its mode so we can preserve the path's permissions + mode = None + if os.path.exists(path): + if force: + mode = os.stat(self.path)[0] + else: + raise Exception, "oh no! %r already exists" % path + + # create the string that we're going to write into the file + data = self.write_filter(self.make_string(nl=self.nl)) + + # create a safe temporary path to write to, and write out data to it + temp_path = self._temp_path() + f2 = self._open_file_w(temp_path) + f2.write(data) + f2.close() + + # move the temporary file to the actual path; maybe change permissions + shutil.move(temp_path, path) + if mode: + os.chmod(path, mode) + + # the file has not been modified now + self.modified = False + def readonly(self): + return False + def read_filter(self, data): + return data + def write_filter(self, data): + return data + + # point retrieval + def get_buffer_start(self): + return Point(0, 0) + def get_buffer_end(self): + return Point(len(self.lines[-1]), len(self.lines) - 1) + + # data retrieval + def get_sublines(self, p1, p2): + self._validate_point(p1) + self._validate_point(p2) + assert p1 <= p2, "p1.x (%d) > p2.x (%d)" % (p1.x, p2.x) + lines = [] + x = p1.x + for i in range(p1.y, p2.y): + lines.append(self.lines[i][x:]) + x = 0 + lines.append(self.lines[p2.y][x:p2.x]) + return lines + def get_substring(self, p1, p2): + lines = self.get_sublines(p1, p2) + return '\n'.join(lines) + + # buffer set + def set_lines(self, lines, force=False): + if not force and self.readonly(): + raise Exception, "set_data: buffer is readonly" + start = self.get_buffer_start() + end = self.get_buffer_end() + self.delete(start, end, force=force) + self.insert_lines(start, lines, force=force) + self.modified = True + def set_data(self, data, force=False): + lines = data.split('\n') + self.set_lines(lines, force) + + # insertion into buffer + def insert_lines(self, p, lines, act=ACT_NORM, force=False): + llen = len(lines) + assert llen > 0 + if not force: + assert not self.readonly(), "insert_string: buffer is read-only" + p2 = p.vadd(len(lines[-1]), llen - 1) + if llen > 1: + self.lines.insert(p.y + 1, []) + self.lines[p.y + 1] = lines[-1] + self.lines[p.y][p.x:] + self.lines[p.y] = self.lines[p.y][:p.x] + lines[0] + for i in range(1, llen - 1): + self.lines.insert(p.y + i, lines[i]) + else: + self.lines[p.y] = self.lines[p.y][:p.x] + lines[-1] + self.lines[p.y][p.x:] + self._region_add(p, p2, lines, act) + self.modified = True + def insert_string(self, p, s, act=ACT_NORM, force=False): + lines = s.split("\n") + self.insert_lines(p, lines, act, force) + + # deletion from buffer + def delete(self, p1, p2, act=ACT_NORM, force=False): + """delete characters from p1 up to p2 from the buffer""" + if not force: + assert not self.readonly(), "delete_string: buffer is read-only" + self._validate_point(p1) + self._validate_point(p2) + if p1 == p2: + return + assert p1 < p2, "p1 %r > p2 %r" % (p1, p2) + lines = self.get_sublines(p1, p2) + line1 = self.lines[p1.y] + line2 = self.lines[p2.y] + self.lines[p1.y:p2.y+1] = ["%s%s" % (line1[:p1.x], line2[p2.x:])] + self._region_del(p1, p2, lines, act) + self.modified = True + def delete_char(self, p, act=ACT_NORM, force=False): + if p.x == len(self.lines[p.y]): + p2 = Point(0, p.y + 1) + else: + p2 = Point(p.x + 1, p.y) + self.delete(p, p2, act, force) + + # random + def count_leading_whitespace(self, y): + line = self.lines[y] + m = regex.leading_whitespace.match(line) + if m: + return m.end() + else: + # should not happen + raise Exception, "iiiijjjj" + +# scratch is a singleton +scratch = None +class ScratchBuffer(Buffer): + def __new__(cls, *args, **kwargs): + global scratch + if scratch is None: + scratch = object.__new__(ScratchBuffer, *args, **kwargs) + return scratch + def name(self): + return "*Scratch*" + def close(self): + global scratch + scratch = None + +class DataBuffer(Buffer): + def __init__(self, name, data, nl='\n'): + Buffer.__init__(self, nl) + self._name = name + self.lines = data.split("\n") + def name(self): + return self._name + def close(self): + pass + def readonly(self): + return True + +# console is another singleton +console = None +class ConsoleBuffer(Buffer): + def __new__(cls, *args, **kwargs): + global console + if console is None: + b = object.__new__(ConsoleBuffer, *args, **kwargs) + console = b + return console + def __init__(self, nl='\n'): + Buffer.__init__(self, nl) + lines = ['Python Console\n', + "Evaluate python expressions in the editor's context (self)\n", + 'Press Control-] to exit\n', + '\n'] + console.set_data(''.join(lines), force=True) + def name(self): + return '*Console*' + def changed(self): + return False + def close(self): + global console + console = None + def readonly(self): + return True + +class FileBuffer(Buffer): + def __init__(self, path, nl='\n', name=None): + '''fb = FileBuffer(path)''' + Buffer.__init__(self, nl) + self.path = os.path.realpath(path) + self.checksum = None + if name is None: + self._name = os.path.basename(self.path) + else: + self._name = name + if os.path.exists(self.path) and not os.access(self.path, os.W_OK): + self._readonly = True + else: + self._readonly = False + def readonly(self): + return self._readonly + + def _open_file_r(self, path=None): + if path is None: + path = self.path + path = os.path.realpath(path) + self.path = path + if not os.path.isfile(path): + raise Exception, "Path '%s' does not exist" % (path) + if not os.access(path, os.R_OK): + raise Exception, "Path '%s' cannot be read" % (path) + f = open(path, 'r') + return f + def _open_file_w(self, path=None): + if path is None: + path = self.path + if os.path.isfile(path): + raise Exception, "Path '%s' already exists" % (path) + d = os.path.dirname(path) + if not os.access(d, os.R_OK): + raise Exception, "Dir '%s' cannot be read" % (path) + if not os.access(d, os.W_OK): + raise Exception, "Dir '%s' cannot be written" % (path) + f = open(path, 'w') + return f + def _temp_path(self, path=None): + if path is None: + path = self.path + (dirname, basename) = os.path.split(path) + return os.path.join(dirname, ".__%s__pmacs" % (basename)) + + # methods for dealing with the underlying resource, etc. + def name(self): + #return self.path + return self._name + def path_exists(self): + return os.path.exists(self.path) + def store_checksum(self, data): + self.checksum = md5.new(data) + def read(self): + if self.path_exists(): + f = self._open_file_r() + data = f.read() + f.close() + self.store_checksum(data) + else: + data = '' + data = self.read_filter(data) + #FIXME: this is horrible...but maybe not as horrible as using tabs?? + data = data.replace("\t", " ") + return data + def open(self): + data = self.read() + self.lines = data.split(self.nl) + def reload(self): + self.open() + def changed_on_disk(self): + assert self.checksum is not None + f = open(self.path) + data = f.read() + f.close() + m = md5.new(data) + return self.checksum.digest() != m.digest() + def save(self, force=False): + if self.readonly(): + raise Exception, "can't save a read-only file" + + if self.checksum is not None and force is False: + # the file already existed and we took a checksum so make sure it's + # still the same right now + if not self.path_exists(): + raise Exception, "oh no! %r disappeared!" % self.path + if self.changed_on_disk(): + raise Exception, "oh no! %r has changed on-disk!" % self.path + + temp_path = self._temp_path() + data = self.make_string(nl=self.nl) + data = self.write_filter(data) + + f2 = self._open_file_w(temp_path) + f2.write(data) + f2.close() + + if self.path_exists(): + mode = os.stat(self.path)[0] + os.chmod(temp_path, mode) + + shutil.move(temp_path, self.path) + self.store_checksum(data) + self.modified = False + def save_as(self, path): + self.path = path + self.save() + +class AesBuffer(FileBuffer): + def __init__(self, path, password, nl='\n'): + '''fb = FileBuffer(path)''' + FileBuffer.__init__(self, path, nl) + self.password = password + def read_filter(self, data): + return aes.decrypt(data, self.password) + def write_filter(self, data): + return aes.encrypt(data, self.password) diff --git a/point2.py b/point2.py new file mode 100644 index 0000000..54e5392 --- /dev/null +++ b/point2.py @@ -0,0 +1,24 @@ +class Point(tuple): + def __new__(cls, x, y): + return tuple.__new__(cls, (y, x)) + def __getattr__(self, name): + if name == "x": + return self[1] + elif name == 'y': + return self[0] + else: + raise AttributeError + def __repr__(self): + return '' % (self[1], self[0]) + + def xy(self): + return (self[1], self[0]) + def add(self, x, y): + assert x >= 0, y >= 0 + return Point(self[1] + x, self[0] + y) + def vadd(self, x, y): + assert x >= 0, y >= 0 + if y > 0: + return Point(x, self[0] + y) + else: + return Point(self[1] + x, self[0]) diff --git a/window2.py b/window2.py new file mode 100644 index 0000000..57c77b4 --- /dev/null +++ b/window2.py @@ -0,0 +1,470 @@ +import os.path, string +import regex +from point2 import Point + +WORD_LETTERS = list(string.letters + string.digits) + +# note about the cursor: the cursor position will insert in front of +# the character it highlights. to this end, it needs to be able to +# highlight behind the last character on a line. thus, the x +# coordinate of the (logical) cursor can equal the length of lines[y], +# even though lines[y][x] throws an index error. both buffer and +# window need to be aware of this possibility for points. + +class Window(object): + def __init__(self, b, a, height=24, width=80, mode_name=None): + self.buffer = b + self.application = a + self.buffer.add_window(self) + + self.first = Point(0, 0) + self.last = None + self.cursor = Point(0, 0) + self.mark = None + self.active_point = None + + self.height = height + self.width = width + + self.input_line = "" + + if mode_name is not None: + pass + elif hasattr(self.buffer, 'modename') and self.buffer.modename is not None: + mode_name = self.buffer.modename + elif self.buffer.name() == "*Minibuffer*": + mode_name = 'mini' + elif self.buffer.name() == "*Console*": + mode_name = "fundamental" + elif hasattr(self.buffer, 'path'): + path = self.buffer.path + basename = os.path.basename(path) + ext = self._get_path_ext(path) + + if path in self.application.mode_paths: + mode_name = self.application.mode_paths[path] + elif basename in self.application.mode_basenames: + mode_name = self.application.mode_basenames[basename] + elif ext in self.application.mode_extensions: + mode_name = self.application.mode_extensions[ext] + elif len(self.buffer.lines) > 0 and \ + self.buffer.lines[0].startswith('#!'): + line = self.buffer.lines[0] + for word in self.application.mode_detection: + if word in line: + mode_name = self.application.mode_detection[word] + + if mode_name is None: + mode_name = "fundamental" + + m = self.application.modes[mode_name](self) + self.set_mode(m) + + # private method used in window constructor + def _get_path_ext(self, path): + name = os.path.basename(path).lower() + tokens = name.split('.') + if len(tokens) > 2 and tokens[-1] in ('gz', 'in', 'zip'): + return '.%s.%s' % (tokens[-2], tokens[-1]) + else: + return os.path.splitext(path)[1].lower() + + # mode stuff + def set_mode(self, m): + self.mode = m + #self.redraw() + + # this is used to temporarily draw the user's attention to another point + def set_active_point(self, p, msg='marking on line %(y)d, character %(x)d'): + self.active_point = p + if not self.point_is_visible(p): + self.application.set_error(msg % {'x': p.x, 'y': p.y}) + + # cursors + def logical_cursor(self): + x = min(self.cursor.x, len(self.buffer.lines[self.cursor.y])) + return Point(x, self.cursor.y) + + # last visible point + def _calc_last(self): + (x, y) = self.first.xy() + count = 0 + while count < self.height - 1 and y < len(self.buffer.lines): + line = self.buffer.lines[y] + if x >= len(line) or len(line[x:]) <= self.width: + x = 0 + y += 1 + count += 1 + else: + count += 1 + x += self.width + + if y < len(self.buffer.lines): + x = min(x + self.width, len(self.buffer.lines[y])) + self.last = Point(x, y) + + # redrawing + def redraw(self): + self._calc_last() + + def set_size(self, width, height): + assert type(width) == type(0), width + assert type(height) == type(0), height + self.width = width + self.height = height + self.redraw() + + # region added + def region_added(self, p, newlines): + (x, y) = self.logical_cursor().xy() + l = len(newlines) + assert l > 0, repr(newlines) + if l > 1: + if y > p.y: + self.cursor = Point(x, y + l - 1) + elif y == p.y and x >= p.x: + self.cursor = Point(len(newlines[-1]) + x - p.x, y + l - 1) + elif y == p.y and x >= p.x: + self.cursor = Point(x + len(newlines[0]), y) + self.assure_visible_cursor() + #self.mode.region_added(p, newlines) + + # region removed + def region_removed(self, p1, p2): + cursor = self.logical_cursor() + (x, y) = cursor.xy() + if cursor < p1: + pass + elif cursor < p2: + self.cursor = p1 + elif cursor.y == p2.y: + self.cursor = Point(self.cursor.x - p2.x + p1.x, p1.y) + else: + self.cursor = Point(self.cursor.x, self.cursor.y - p2.y + p1.y) + self.assure_visible_cursor() + #self.mode.region_removed(p1, p2) + + def point_is_visible(self, p): + return self.first <= p and p <= self.last + def cursor_is_visible(self): + return self.point_is_visible(self.logical_cursor()) + def first_is_visible(self): + return self.point_is_visible(self.buffer.get_buffer_start()) + def last_is_visible(self): + return self.point_is_visible(self.buffer.get_buffer_end()) + + def center_view(self): + (x, y) = self.logical_cursor().xy() + counter = 0 + while counter < self.height / 2: + if x > self.width: + x -= self.width + elif y > 0: + y -= 1 + x = len(self.buffer.lines[y]) + else: + (x, y) = (0, 0) + break + counter += 1 + self.first = Point(x, y) + self.redraw() + def assure_visible_cursor(self): + if not self.cursor_is_visible(): + self.center_view() + + # moving in buffer + def forward(self): + cursor = self.logical_cursor() + if cursor.x < len(self.buffer.lines[cursor.y]): + self.cursor = Point(cursor.x + 1, cursor.y) + elif cursor.y < len(self.buffer.lines) -1: + self.cursor = Point(0, cursor.y + 1) + self.assure_visible_cursor() + def backward(self): + cursor = self.logical_cursor() + if cursor.x > 0: + self.cursor = Point(cursor.x - 1, cursor.y) + elif cursor.y > 0: + self.cursor = Point(len(self.buffer.lines[cursor.y - 1]), cursor.y - 1) + self.assure_visible_cursor() + def end_of_line(self): + cursor = self.logical_cursor() + self.cursor = Point(len(self.buffer.lines[cursor.y]), cursor.y) + self.assure_visible_cursor() + def start_of_line(self): + cursor = self.logical_cursor() + self.cursor = Point(0, cursor.y) + self.assure_visible_cursor() + def previous_line(self): + cursor = self.logical_cursor() + if cursor.y > 0: + self.cursor = Point(cursor.x, cursor.y - 1) + self.assure_visible_cursor() + def next_line(self): + cursor = self.logical_cursor() + if cursor.y < len(self.buffer.lines) - 1: + self.cursor = Point(cursor.x, cursor.y + 1) + self.assure_visible_cursor() + + # word handling + def find_left_word(self, p=None): + if p is None: + (x, y) = self.logical_cursor().xy() + else: + (x, y) = p.xy() + + start = self.buffer.get_buffer_start() + if (x, y) == start: + return + elif x == 0: + y -= 1 + x = len(self.buffer.lines[y]) + else: + x -= 1 + while (y, x) >= start and self.xy_char(x, y) not in WORD_LETTERS: + if x == 0: + y -= 1 + x = len(self.buffer.lines[y]) + else: + x -= 1 + found_word = False + while (y, x) >= start and self.xy_char(x, y) in WORD_LETTERS: + found_word = True + if x == 0: + y -= 1 + x = len(self.buffer.lines[y]) + else: + x -= 1 + if not found_word: + return None + elif x == len(self.buffer.lines[y]): + x = 0 + y += 1 + else: + x += 1 + return Point(x, y) + def find_right_word(self, p=None): + if p is None: + (x, y) = self.logical_cursor().xy() + else: + (x, y) = p.xy() + end = self.buffer.get_buffer_end() + while (y, x) < end and self.xy_char(x, y) not in WORD_LETTERS: + if x == len(self.buffer.lines[y]): + x = 0 + y += 1 + else: + x += 1 + while (y, x) < end and self.xy_char(x, y) in WORD_LETTERS: + if x == len(self.buffer.lines[y]): + x = 0 + y += 1 + else: + x += 1 + return Point(x, y) + def left_word(self): + p = self.find_left_word() + if p is not None: + self.goto(p) + def right_word(self): + p = self.find_right_word() + if p is not None: + self.goto(p) + + # page up/down + def _pshift_up(self, p, num): + (x, y) = p.xy() + orig_x = x + counter = 0 + while counter < num and y > 0: + if x > self.width: + x -= self.width + else: + y -= 1 + x = len(self.buffer.lines[y]) + counter += 1 + return Point(orig_x, y) + def _pshift_down(self, p, num): + (x, y) = p.xy() + orig_x = x + counter = 0 + while counter < num and y < len(self.buffer.lines): + if x + self.width >= len(self.buffer.lines[y]): + y += 1 + x = 0 + else: + x += self.width + counter += 1 + return Point(orig_x, y) + def page_up(self): + first_point = self.buffer.get_buffer_start() + if self.point_is_visible(first_point): + self.goto_beginning() + return + self.cursor = self._pshift_up(self.cursor, self.height - 3) + if self.first > first_point: + self.first = self._pshift_up(self.first, self.height - 3) + self.redraw() + def page_down(self): + last_point = self.buffer.get_buffer_end() + if self.point_is_visible(last_point): + self.goto_end() + return + self.cursor = self._pshift_down(self.cursor, self.height - 3) + if self.last < last_point: + self.first = self._pshift_down(self.first, self.height - 3) + self.redraw() + + # jumping in buffer + def goto(self, p): + self.cursor = p + self.assure_visible_cursor() + def goto_line(self, n): + assert n > 0 and n <= len(self.buffer.lines) , "illegal line: %d" % n + self.cursor = Point(0, n - 1) + self.assure_visible_cursor() + def forward_lines(self, n): + assert n > 0, "illegal number of lines: %d" % n + y = min(self.cursor[1] + n, len(self.buffer.lines) - 1) + self.goto_line(y) + def forward_chars(self, n): + (x, y) = self.logical_cursor().xy() + for i in range(0, n): + if x == len(self.buffer.lines[y]): + y += 1 + x = 0 + if y >= len(self.buffer.lines): + break + else: + x += 1 + self.goto(Point(x, y)) + def goto_char(self, n): + self.goto_beginning() + self.forward_chars(n) + def goto_beginning(self): + self.cursor = Point(0, 0) + self.assure_visible_cursor() + def goto_end(self): + self.cursor = self.buffer.get_buffer_end() + self.assure_visible_cursor() + + # mark manipulation + def set_mark_point(self, p): + self.mark = p + def set_mark(self): + self.set_mark_point(self.logical_cursor()) + self.application.set_error("Mark set") + def goto_mark(self): + self.goto(self.mark) + def switch_mark(self): + if self.mark: + p = self.mark + self.set_mark_point(self.logical_cursor()) + self.goto(p) + + # deletion + def left_delete(self): + (x, y) = self.logical_cursor().xy() + if x > 0: + self.buffer.delete_char(Point(x - 1, y)) + elif y > 0: + self.buffer.delete_char(Point(len(self.buffer.lines[y - 1]), y - 1)) + def right_delete(self): + cursor = self.logical_cursor() + if cursor < self.last: + self.buffer.delete_char(cursor) + else: + pass + + # killing + def kill_line(self): + return self.copy_line(kill=True) + def kill_region(self): + return self.copy_region(kill=True) + def kill_left_word(self): + p1 = self.find_left_word() + p2 = self.logical_cursor() + if p1 == p2: + return + return self.kill(p1, p2) + def kill_right_word(self): + p1 = self.logical_cursor() + p2 = self.find_right_word() + if p1 == p2: + return + return self.kill(p1, p2) + def copy_line(self, kill=False): + cursor = self.logical_cursor() + (x, y) = cursor.xy() + lines = self.buffer.lines + if (x < len(lines[y]) and not regex.whitespace.match(lines[y][x:])): + limit = Point(len(lines[y]), y) + elif y < len(lines) - 1: + limit = Point(0, y + 1) + else: + return + if kill: + return self.kill(cursor, limit) + else: + return self.copy(cursor, limit) + def copy_region(self, kill=False): + cursor = self.logical_cursor() + if cursor < self.mark: + p1 = cursor + p2 = self.mark + elif self.mark < cursor: + p1 = self.mark + p2 = cursor + else: + self.input_line = "Empty kill region" + return + if kill: + return self.kill(p1, p2) + else: + return self.copy(p1, p2) + def kill(self, p1, p2): + killed = self.buffer.get_substring(p1, p2) + self.buffer.delete_string(p1, p2) + self.application.push_kill(killed) + return killed + def copy(self, p1, p2): + copied = self.buffer.get_substring(p1, p2) + self.application.push_kill(copied) + return copied + + # insertion + def insert_string_at_cursor(self, s): + self.insert_string(self.logical_cursor(), s) + def insert_string(self, p, s): + lines = s.split('\n') + self.insert_lines(p, lines) + def insert_lines_at_cursor(self, lines): + self.insert_lines(self.logical_cursor(), lines) + def insert_lines(self, p, lines): + self.buffer.insert_lines(p, lines) + self.redraw() + + # yank/pop + def yank(self): + self.insert_string_at_cursor(self.application.get_kill()) + def pop_kill(self): + return self.application.pop_kill() + + # querying + def highlighted_char(self): + self.point_char(self.logical_cursor()) + def point_char(self, p): + return self.xy_char(p.x, p.y) + def xy_char(self, x, y): + if x == len(self.buffer.lines[y]): + return "\n" + else: + return self.buffer.lines[y][x] + + # undo/redo + def undo(self): + # TODO: put something here to move the cursor to the area of the undo + self.buffer.undo() + def redo(self): + # TODO: put something here to move the cursor to the area of the redo + self.buffer.redo()