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.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) self.buffer.add_window(self) # 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() def get_highlighter(self): if self.mode.lexer is None: return None else: return self.buffer.highlights[self.mode.name()] # 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}) # point left def point_left(self, p): if p.y == 0 and p.x == 0: return None elif p.x == 0: return Point(len(self.buffer.lines[p.y - 1]), p.y - 1) else: return Point(p.x - 1, p.y) # point right def point_right(self, p): if p.y == len(self.buffer.lines)-1 and p.x == len(self.buffer.lines[-1]): return None elif p.x == len(self.buffer.lines[p.y]): return Point(0, p.y + 1) else: return Point(p.x + 1, p.y) # cursors def logical_cursor(self): if len(self.buffer.lines) > self.cursor.y: l = len(self.buffer.lines[self.cursor.y]) else: l = 0 x = min(self.cursor.x, l) 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.mode.region_added(p, newlines) self.assure_visible_cursor() # 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.mode.region_removed(p1, p2) self.assure_visible_cursor() 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 - (x % self.width), 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) def get_word_bounds_at_point(self, p): if len(self.buffer.lines[p.y]) == 0: return elif p.x == 0: p1 = self.find_left_word(Point(p.x + 1, p.y)) p2 = self.find_right_word() else: p1 = self.find_left_word() p2 = self.find_right_word(Point(p.x - 1, p.y)) return (p1, p2) def get_word_at_point(self, p): bounds = self.get_word_bounds_at_point(p) if bounds is None: return None else: return self.buffer.get_substring(bounds[0], bounds[1]) def get_word_bounds(self): return self.get_word_bounds_at_point(self.logical_cursor()) def get_word(self): return self.get_word_at_point(self.logical_cursor()) # 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.logical_cursor().y + n, len(self.buffer.lines) - 1) self.goto(Point(0, 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(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 cursor_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): #assert 0 <= y and y < len(self.buffer.lines) #assert 0 <= x and x <= len(self.buffer.lines[y]) if x == len(self.buffer.lines[y]): return "\n" else: return self.buffer.lines[y][x] # undo/redo # XYZ 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()