import os.path, string import buffer, point, regex 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, slot='main', mode_name=None): self.buffer = b self.application = a self.buffer.add_window(self, slot) self.first = point.Point(0, 0, "logical") self.last = point.Point(0, 0, "logical") self.cursor = point.Point(0, 0, "logical") self.mark = None self.active_point = None #self.physical_movement = False self.height = height self.width = width self._logical_offsets = None self._physical_lines = None self._physical_lines_cont = None 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 = "console" mode_name = "fundamental" elif hasattr(self.buffer, 'path'): path = self.buffer.path basename = os.path.basename(path) #ext = os.path.splitext(path)[1].lower() 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) 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() def set_mode(self, m): self.mode = m self.redraw() def get_cursor_offset(self): cursor = self.logical_cursor() return self.buffer.get_point_offset(cursor) # the message is printed when the point is not visible, and the proper # variable is set def set_active_point(self, p, use_msg_when_hidden=True, 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 # # there are three: # the actual cursor (not good for most things) # the logical cursor (good for logical actions on buffer) # the physical cursor (good for drawing) def logical_cursor(self): y = self.cursor.y if self.cursor.x <= len(self.buffer.lines[y]): return self.cursor else: return point.Point(len(self.buffer.lines[y]), y, "logical") def logical_point(self, p): self.get_physical_lines() x = 0 y = 0 for i in range(0, p.y): if self._physical_lines_cont[i]: x += self.width else: x = 0 y += 1 x += p.x return point.Point(x, y, "logical") def physical_cursor(self): p = self.logical_cursor() #return self.physical_point(p) self.get_physical_lines() y = 0 for i in self._logical_offsets[0:p.y]: y += i y += p.x / self.width x = p.x % self.width # this allows the cursor to be in the right margin, rather than on the # next line... i.e. not the behavior you want for actual text. if p.x == len(self.buffer.lines[p.y]) and y > 0 and p.x > 0 and x == 0: #if y > 0 and p.x > 0 and x == 0: x = self.width y -= 1 return point.Point(x, y, "physical") def physical_point(self, p): self.get_physical_lines() y = 0 for i in self._logical_offsets[0:p.y]: y += i y += p.x / self.width x = p.x % self.width return point.Point(x, y, "physical") # debug def get_debug_repr(self): return "" def get_physical_lines(self): if self._physical_lines is None: self._physical_lines = [] self._physical_lines_cont = [] self._logical_offsets = [] for l in self.buffer.lines: pl = [] while len(l) > self.width: pl.append(l[:self.width]) l = l[self.width:] pl.append(l) self._logical_offsets.append(len(pl)) self._physical_lines.extend(pl) for i in range(0, len(pl)-1): self._physical_lines_cont.append(True) self._physical_lines_cont.append(False) return self._physical_lines # redrawing def set_size(self, width, height): self.width = width self.height = height self.redraw() self._invalidate_physical_lines() self.mode.invalidate() def _invalidate_physical_lines(self): self._physical_lines = None def _region_added(self, p, xdiff, ydiff, str=None): cursor = self.logical_cursor() self._invalidate_physical_lines() if cursor.y > p.y: self.cursor = cursor.offset(0, ydiff) elif self.cursor >= p: self.cursor = cursor.offset(xdiff, ydiff) else: pass self.redraw() # added 2006-5-28 if not self.cursor_is_visible(): self.center_view() self.mode.region_added(p, xdiff, ydiff, str) def _region_removed(self, p1, p2, str): pdelta = p1 - p2 xdiff, ydiff = pdelta.x, pdelta.y self._invalidate_physical_lines() if self.cursor.y > p2.y: self.cursor = self.cursor.offset(0, ydiff) elif self.cursor > p2: self.cursor = self.cursor.offset(xdiff, ydiff) elif self.cursor >= p1: self.cursor = p1.offset(0, 0) else: pass if not self.cursor_is_visible(): self.center_view() self.mode.region_removed(p1, p2, str) def visible_offset(self): pfirst = self.physical_point(self.first) return pfirst.y def visible_cursor(self): i = self.visible_offset() return self.physical_cursor().offset(0, -i) def visible_lines(self): i = self.visible_offset() lines = self.get_physical_lines() return lines[i:i+self.height] def continued_visible_line(self, i): return self._physical_lines_cont[i + self.visible_offset()] def redraw(self): plines = self.get_physical_lines() pfirst = self.physical_point(self.first) py = min(pfirst.y + self.height - 1, len(plines) - 1) px = min(self.width, len(plines[py])) plast = point.Point(px, py, "physical") self.last = self.logical_point(plast) if self.last < self.first: raise Exception, "BUGGJGJG:\n%s" % (self.dump()) self._validate_first_last() def point_is_visible(self, p): return self.first <= p and p <= self.last def cursor_is_visible(self): cursor = self.logical_cursor() return self.point_is_visible(cursor) def first_is_visible(self): first_point = self.buffer.get_buffer_start() return self.point_is_visible(first_point) def last_is_visible(self): last_point = self.buffer.get_buffer_end() return self.point_is_visible(last_point) def center_view(self): pcursor = self.physical_cursor() x = 0 if self.height == 1: # we special case this to avoid rounding problems y = max(0, pcursor.y) else: offset = self.height - (self.height / 2) y = max(0, pcursor.y - offset) pfirst = point.Point(x, y, "physical") self.first = self.logical_point(pfirst) self.redraw() def relocate_cursor(self): if not self.cursor_is_visible(): i = self.visible_offset() pp = point.Point(0, i, "physical") lp = self.logical_point(pp) self.goto(lp) # point validation def _validate_cursor(self): self.buffer._validate_point(self.logical_cursor()) def _validate_mark(self): self.buffer._validate_point(self.mark) def _validate_first_last(self): assert self.first <= self.logical_cursor(), "one" assert (self.first.x % self.width) == 0, "two: %d %% %d != 0 (%d)" % (self.first.x, self.width, self.first.x % self.width) assert self.first <= self.last, "four" # moving in buffer def forward(self): cursor = self.logical_cursor() if cursor.x < len(self.buffer.lines[cursor.y]): self.cursor.x = cursor.x + 1 elif cursor.y < len(self.buffer.lines) - 1: self.cursor.y = cursor.y + 1 self.cursor.x = 0 if not self.cursor_is_visible(): self.center_view() def backward(self): cursor = self.logical_cursor() if cursor.x > 0: self.cursor.x = cursor.x - 1 elif self.cursor.y > 0: self.cursor.y = cursor.y - 1 self.cursor.x = len(self.buffer.lines[self.cursor.y]) if not self.cursor_is_visible(): self.center_view() def end_of_line(self): self.cursor.x = len(self.buffer.lines[self.cursor.y]) if not self.cursor_is_visible(): self.center_view() def start_of_line(self): self.cursor.x = 0 if not self.cursor_is_visible(): self.center_view() def previous_line(self): if self.cursor.y > 0: self.cursor.y -= 1 if not self.cursor_is_visible(): self.center_view() def next_line(self): if self.cursor.y < len(self.buffer.lines) - 1: self.cursor.y += 1 if not self.cursor_is_visible(): self.center_view() def pshift(self, p, i): y = max(0, p.y + i) y = min(y, len(self._physical_lines) - 1) x = min(len(self._physical_lines[y]), p.x) return self.logical_point(point.Point(x, y, "physical")) # word handling def find_left_word(self, p=None): if p is None: p = self.logical_cursor().offset(0, 0) start = self.buffer.get_buffer_start() if p == start: return elif p.x == 0: p.y -= 1 p.x = len(self.buffer.lines[p.y]) else: p.x -= 1 while p >= start and self.point_char(p) not in WORD_LETTERS: if p.x == 0: p.y -= 1 p.x = len(self.buffer.lines[p.y]) else: p.x -= 1 found_word = False while p >= start and self.point_char(p) in WORD_LETTERS: found_word = True if p.x == 0: p.y -= 1 p.x = len(self.buffer.lines[p.y]) else: p.x -= 1 if not found_word: pass elif p.x == len(self.buffer.lines[p.y]): p.x = 0 p.y += 1 else: p.x += 1 return p def find_right_word(self, p=None): if p is None: p = self.logical_cursor().offset(0, 0) end = self.buffer.get_buffer_end() while p < end and self.point_char(p) not in WORD_LETTERS: if p.x == len(self.buffer.lines[p.y]): p.x = 0 p.y += 1 else: p.x += 1 while p < end and self.point_char(p) in WORD_LETTERS: if p.x == len(self.buffer.lines[p.y]): p.x = 0 p.y += 1 else: p.x += 1 return p 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 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(self.physical_cursor(), 3 - self.height) if self.first > first_point: self.first = self.pshift(self.physical_point(self.first), 3 - self.height) 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(self.physical_cursor(), self.height - 3) if self.last < last_point: self.first = self.pshift(self.physical_point(self.first), self.height - 3) self.redraw() # jumping in buffer def goto(self, p): self.buffer._validate_point(p) self.cursor.x = p.x self.cursor.y = p.y if not self.cursor_is_visible(): self.center_view() def goto_line(self, y): if y < 0: y = len(self.buffer.lines) + y + 1 self.buffer._validate_y(y) self.cursor.y = y self.cursor.x = 0 if not self.cursor_is_visible(): self.center_view() def forward_lines(self, n): assert n > 0, "illegal number of lines: %d" % n m = 0 p = self.logical_cursor().copy() while m < n and p.y < len(self.buffer.lines): p.y += 1 m += 1 self.goto(p) def forward_chars(self, n): m = 0 p = self.logical_cursor().copy() while p < self.last and m < n: if p.x == len(self.buffer.lines[p.y]): p.y += 1 p.x = 0 m += 1 else: p.x += 1 m += 1 self.goto(p) def goto_char(self, n): self.goto(point.Point(0, 0)) self.forward_chars(n) def goto_beginning(self): self.cursor = self.buffer.get_buffer_start() self.first = self.buffer.get_buffer_start() self.redraw() def goto_end(self): self.cursor = self.buffer.get_buffer_end() if not self.cursor_is_visible(): pcursor = self.physical_cursor() pfirst = pcursor.offset(0, 3 - self.height) pfirst.x = 0 self.first = self.logical_point(pfirst) self.redraw() # mark manipulation def set_mark_point(self, p): self.mark = p.offset(0, 0) def set_mark(self): cursor = self.logical_cursor() self.set_mark_point(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 cursor = self.logical_cursor() self.set_mark_point(cursor) self.goto(p) # deletion def left_delete(self): cursor = self.logical_cursor() if cursor.x > 0: self.buffer.delete_character(cursor.offset(-1, 0, "logical")) elif cursor.y > 0: self.buffer.delete_character(point.Point(len(self.buffer.lines[cursor.y-1]), cursor.y - 1, "logical")) else: pass def right_delete(self): cursor = self.logical_cursor() if cursor < self.last: self.buffer.delete_character(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() if (cursor.x < len(self.buffer.lines[cursor.y]) and not regex.whitespace.match(self.buffer.lines[cursor.y][cursor.x:])): limit = point.Point(len(self.buffer.lines[cursor.y]), cursor.y, "logical") elif cursor.y < len(self.buffer.lines) - 1: limit = point.Point(0, cursor.y + 1, "logical") 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(self, s): cursor = self.logical_cursor() self.insert(cursor, s) def insert(self, p, s): c = s.count('\n') if c > 0: y = p.y + c x = len(s) - (c + 1) - 1 else: y = p.y x = p.x + len(s) self.buffer.insert_string(p, s) def yank(self): s = self.application.get_kill() self.insert_string(s) def pop_kill(self): return self.application.pop_kill() # querying def highlighted_char(self): cursor = self.logical_cursor() self.point_char(cursor) def point_char(self, p): if p.x == len(self.buffer.lines[p.y]): return "\n" else: return self.buffer.lines[p.y][p.x] # region finding (p is a physical point) def get_region(self, p): regions = self.mode.get_regions() assert len(regions[p.y]) > 0, "no regions found; strange" for r in regions[p.y]: if r.start <= p.x and r.end >= p.x + 1: return r return None