import os.path, string import highlight2, regex from point 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): margins = ((80, 'blue'),) margins_visible = False 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.btype == 'mini': mode_name = 'mini' elif self.buffer.btype == 'console': mode_name = "fundamental" elif self.buffer.btype == 'dir': mode_name = 'dir' 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() # some useful pass-through to application def set_error(self, s): self.application.set_error(s) def clear_error(self): self.application.clear_error() # mode stuff def set_mode(self, m): self.mode = m modename = m.name() if modename not in self.buffer.highlights and m.lexer is not None: self.buffer.highlights[modename] = highlight2.Highlighter(m.lexer) self.buffer.highlights[modename].highlight(self.buffer.lines) #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) - 1: 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) visible = self.point_is_visible(p) 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) if not visible and l > 1 and self.first.y > p.y: self.first = Point(self.first.x, self.first.y + l - 1) self.redraw() 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() visible = self.point_is_visible(p2) xdelta = p2.x - p1.x ydelta = p2.y - p1.y 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) self.cursor = Point(self.cursor.x - xdelta, p1.y) else: #self.cursor = Point(self.cursor.x, self.cursor.y - p2.y + p1.y) self.cursor = Point(self.cursor.x, self.cursor.y - ydelta) if not visible and ydelta and self.first.y > p2.y: self.first = Point(self.first.x, self.first.y - ydelta) self.redraw() 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(): #raise Exception, "%s < %s" % (self.last, self.logical_cursor()) 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): if self.cursor.y > 0: self.cursor = Point(self.cursor.x, self.cursor.y - 1) self.assure_visible_cursor() def next_line(self): if self.cursor.y < len(self.buffer.lines) - 1: self.cursor = Point(self.cursor.x, self.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, wl=WORD_LETTERS): if len(self.buffer.lines[p.y]) == 0: return None elif self.cursor_char() not in wl: return None x1 = x2 = p.x while x1 > 0 and self.xy_char(x1 - 1, p.y) in wl: x1 -= 1 while x2 < len(self.buffer.lines[p.y]) and self.xy_char(x2, p.y) in wl: x2 += 1 return (Point(x1, p.y), Point(x2, p.y)) def get_word_at_point(self, p, wl=WORD_LETTERS): bounds = self.get_word_bounds_at_point(p, wl) if bounds is None: return None else: return self.buffer.get_substring(bounds[0], bounds[1]) def get_word_bounds(self, wl=WORD_LETTERS): return self.get_word_bounds_at_point(self.logical_cursor(), wl) def get_word(self, wl=WORD_LETTERS): return self.get_word_at_point(self.logical_cursor(), wl) # 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 if y == len(self.buffer.lines): y -= 1 x = len(self.buffer.lines[y]) 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() (x, y) = self.logical_cursor().xy() if x == 0: y -= 1 x = len(self.buffer.lines[y]) else: x -= 1 counter = 0 while counter < self.height - 3: 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 if not self.cursor_is_visible(): self.first = Point(x - (x % self.width), y) self.redraw() # 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 # overwriting def overwrite_char_at_cursor(self, c): self.overwrite_char(self.logical_cursor(), c) def overwrite_char(self, p, c): line = self.buffer.lines[p.y] if p.x >= len(line): self.insert_string(p, c) elif p.x == len(line) - 1: self.buffer.overwrite_char(p, c) if p.y < len(self.buffer.lines): self.cursor = Point(0, p.y + 1) else: self.buffer.overwrite_char(p, c) self.cursor = Point(p.x + 1, p.y) # 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 get_kill(self): return self.application.get_kill() def has_kill(self, i=-1): return self.application.has_kill(i) def pop_kill(self): return self.application.pop_kill() def push_kill(self, s): return self.application.push_kill(s) # querying def cursor_char(self): return 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): p = self.buffer.undo() if not self.point_is_visible(p): self.goto(p) def redo(self): p = self.buffer.redo() if not self.point_is_visible(p): self.goto(p) # highlighting tokens def get_token(self): return self.get_token_at_point(self.logical_cursor()) def get_token2(self): c = self.logical_cursor() p = Point(max(0, c.x - 1), c.y) return self.get_token_at_point(p) def get_token_at_point(self, p): for token in self.get_highlighter().tokens[p.y]: if token.end_x() <= p.x: continue elif token.x > p.x: continue else: return token return None def get_next_token_by_lambda(self, p, f): tokens = self.get_highlighter().tokens[p.y] for token in tokens: if token.x < p.x: continue if f(token): return token return None def get_next_token_by_type(self, p, name): return self.get_next_token_by_lambda(p, lambda t: t.name == name) def get_next_token_except_type(self, p, name): return self.get_next_token_by_lambda(p, lambda t: t.name != name) def get_next_token_by_type_regex(self, p, name, regex): l = lambda t: t.name == name and regex.match(t.string) return self.get_next_token_by_lambda(p, l) def get_next_token_except_type_regex(self, p, name, regex): l = lambda t: t.name != name or regex.match(t.string) return self.get_next_token_by_lambda(p, l) def get_next_token_by_types(self, p, *names): return self.get_next_token_by_lambda(p, lambda t: t.name in names) def get_next_token_except_types(self, p, *names): return self.get_next_token_by_lambda(p, lambda t: t.name not in names)