diff --git a/application.py b/application.py index 7cf9ce9..c983352 100755 --- a/application.py +++ b/application.py @@ -581,6 +581,9 @@ class Application(object): if y >= len(w.buffer.lines) or x + swidth >= len(w.buffer.lines[y]): x = 0 y += 1 + #XYZ + while w.ishidden(y) and y < len(w.buffer.lines): + y += 1 else: x += swidth count += 1 @@ -615,25 +618,40 @@ class Application(object): for x in range(sx1, sx2): self.highlight_char(sy, x, fg, bg) + def map_point(self, p): + count = 0 + x, y = w.first.xy() + while count < slot.height: + if p1.y == y and p1.x >= x and p1.x - x < slot.width: + return (count, x) + if x + slot.width > len(w.buffer.lines[y]): + x = 0 + y += 1 + #XYZ + while w.ishidden(y) and y < len(w.buffer.lines): + y += 1 + else: + x += slot.width + count += 1 + def draw_slot(self, i): - assert self.active_slot < len(self.bufferlist.slots), "only two" - assert i < len(self.bufferlist.slots), "only three" + assert self.active_slot < len(self.bufferlist.slots), \ + "strange: %d < %d" % (self.active_slot, len(self.bufferlist.slots)) + assert i < len(self.bufferlist.slots), \ + "puzzling: %d < %d" % (i, len(self.bufferlist.slots)) slot = self.bufferlist.slots[i] if slot.window is None: return w = slot.window modename = w.mode.name() - if modename in w.buffer.highlights: - self._draw_slot_lit(i) - else: - self._draw_slot_raw(i) + self._draw_slot(i) # highlighted regions for (high_w, p1, p2, fg, bg) in self.highlighted_ranges: if w is high_w and p2 >= w.first and p1 <= w.last: count = 0 - (x, y) = w.first.xy() + x, y = w.first.xy() px = p1.x while count < slot.height: if p1.y == y and px >= x and px - x < slot.width: @@ -646,6 +664,9 @@ class Application(object): if x + slot.width > len(w.buffer.lines[y]): x = 0 y += 1 + #XYZ + while w.ishidden(y) and y < len(w.buffer.lines): + y += 1 else: x += slot.width count += 1 @@ -657,123 +678,37 @@ class Application(object): char = chr(self.win.inch(j + slot.y_offset, limit) & 255) attr = color.build('default', shade, 'bold') self.win.addstr(j + slot.y_offset, limit + w.mode.lmargin, char, attr) - - def _draw_line_margins(self, slot, count, w, y, x): - lm, rm = w.mode.lmargin, w.mode.rmargin - if y >= len(w.buffer.lines): - ended = True - cont = False - else: - ended = False - cont = x + slot.width - lm - rm < len(w.buffer.lines[y]) - - i = slot.y_offset + count - if lm: - groups = w.mode.get_lmargin(y, x, ended, cont) - for (x, lmargin, lattr) in groups: - self.win.addstr(i, x, lmargin, lattr) - if rm: - groups = w.mode.get_rmargin(y, x, ended, cont) - for (x, rmargin, rattr) in groups: - self.win.addstr(i, slot.width - rm + x, rmargin, rattr) - def _draw_slot_raw(self, i): + def _draw_slot(self, i): slot = self.bufferlist.slots[i] w = slot.window - modename = w.mode.name() redattr = color.build_attr(color.pairs('red', 'default')) - - (x, y) = w.first.xy() - lines = w.buffer.lines - count = 0 - lm, rm = w.mode.lmargin, w.mode.rmargin - swidth = slot.width - lm - rm + x, y = w.first.xy() + lines = w.buffer.lines + count = 0 + lm, rm = w.mode.lmargin, w.mode.rmargin + modename = w.mode.name() + lit = w.mode.name() in w.buffer.highlights + ended = False while count < slot.height: - #draw some margins - self._draw_line_margins(slot, count, w, y, x) - - # if the window has no more lines, display the "empty" symbol - if y >= len(lines): - self.win.addstr(slot.y_offset + count, 0 + lm, '~', redattr) - count += 1 - continue - - # get the line to draw and draw it - line = lines[y] - s = line[x:x + swidth] - self.win.addstr(slot.y_offset + count, 0 + lm, s) - - # see if we need to draw a line-continuation symbol or not - if x + swidth >= len(line): - x = 0 - y += 1 + if lit: + rlines = w.render_line_lit(y, slot.width - lm - rm) else: - x += swidth - - # move on to the next physical line - count += 1 - - def _draw_slot_lit(self, i): - slot = self.bufferlist.slots[i] - w = slot.window - modename = w.mode.name() - redattr = color.build_attr(color.pairs('red', 'default')) - highlighter = w.buffer.highlights[modename] - - (x, y) = w.first.xy() - j = 0 - count = 0 - (lm, rm) = (w.mode.lmargin, w.mode.rmargin) - swidth = slot.width - lm - rm - assert len(w.buffer.lines) == len(highlighter.tokens) - while count < slot.height: - if y < len(w.buffer.lines): - while j < len(highlighter.tokens[y]): - token = highlighter.tokens[y][j] - if token.string.endswith('\n'): - tstring = token.string[:-1] - else: - tstring = token.string - - assert token.y == y, '%d == %d' % (token.y, y) - - s_offset = max(x - token.x, 0) - x_offset = max(token.x - x, 0) - assert x_offset <= swidth, '%d <= %d' % (x_offset, swidth) - - s = tstring[s_offset:] - token_done = x_offset + len(s) <= swidth - token_wrap = x_offset + len(s) > swidth - - # for debugging things like lexing/relexing/etc. - if token._debug: - attr = color.build('blue', 'green') - elif token.color: - attr = color.build(*token.color) - else: - attr = color.build("default", "default") - - k = slot.y_offset + count - self.win.addstr(k, x_offset + lm, s[:swidth - x_offset], attr) - - if token_wrap: - self._draw_line_margins(slot, count, w, y, x) - x += swidth - count += 1 - if token_done: - j += 1 - if count >= slot.height: - break - - # we have finished this logical line of tokens - self._draw_line_margins(slot, count, w, y, x) - j = x = 0 - y += 1 - count += 1 - else: - self._draw_line_margins(slot, count, w, y, x) - self.win.addstr(slot.y_offset + count, 0 + lm, '~', redattr) + rlines = w.render_line_raw(y, slot.width - lm - rm) + for j in range(0, len(rlines)): + if lm: + lcont = j > 0 + for rstr in slot.window.mode.get_lmargin(w, y, x, ended, lcont): + rstr.draw(self.win, slot.y_offset + count, 0) + for rstr in rlines[j]: + rstr.draw(self.win, slot.y_offset + count, 0 + lm) + if rm: + rcont = j < len(rlines) - 1 + for rstr in slot.window.mode.get_rmargin(w, y, x, ended, rcont): + rstr.draw(self.win, slot.y_offset + count, slot.width - rm) count += 1 + y += 1 + ended = ended or y >= len(w.buffer.lines) def draw_status_bar(self, slotname): slot = self.bufferlist.slots[slotname] diff --git a/method/__init__.py b/method/__init__.py index 3e2087f..259ed64 100644 --- a/method/__init__.py +++ b/method/__init__.py @@ -989,3 +989,23 @@ class SetModeTabWidth(Method): return app.modes[mode].tabwidth = vargs['width'] w.set_error('Default tab width set to %d' % app.modes[mode].tabwidth) + +class HideRange(Method): + def _execute(self, w, **vargs): + cursor = w.logical_cursor() + if cursor.y < w.mark.y: + y1, y2 = cursor.y, w.mark.y + elif w.mark.y < cursor.y: + y1, y2 = w.mark.y, cursor.y + else: + w.set_error("No line range selected") + w.hide(y1, y2) + w.set_error("Lines %d through %d hidden" % (y1, y2)) +class UnhideLine(Method): + def _execute(self, w, **vargs): + cursor = w.logical_cursor() + if w.ishidden(cursor.y): + w.unhide(cursor.y) + w.set_error("Line %d is no longer hidden" % cursor.y) + else: + w.set_error("Line %d is not hidden" % cursor.y) diff --git a/mode/__init__.py b/mode/__init__.py index 0121755..8f33818 100644 --- a/mode/__init__.py +++ b/mode/__init__.py @@ -2,6 +2,7 @@ import math, os, sets, string import color, method from lex import Lexer from point import Point +from render import RenderString DEBUG = False @@ -224,27 +225,27 @@ class Fundamental(Handler): self.show_line_numbers = True l = len(self.window.buffer.lines) self.lmargin = int(math.log(l, 10)) + 3 - self.rmargin = 0 def disable_line_numbers(self): self.show_line_numbers = False self.lmargin = 0 - self.rmargin = 1 - def get_rmargin(self, y, x, ended=False, cont=False): + def get_rmargin(self, w, y, x, ended=False, cont=False): + c, fg, bg = " ", "red", "default" if cont: - return ((0, '\\', color.build('red', 'default')),) - else: - return ((0, ' ', color.build('red', 'default')),) - def get_lmargin(self, y, x=0, ended=False, cont=False): + c = "\\" + if w.hiddenindicator(y): + bg = "green" + return [RenderString(c, 0, color.build(fg, bg))] + def get_lmargin(self, w, y, x, ended=False, cont=False): lm = self.lmargin if ended: i = int(math.log(y, 10)) + 1 s = ('% *s' % (lm - 1, '-' * i))[-lm:] + ' ' - elif x == 0: + elif not cont: s = ('% *d' % (lm - 1, y + 1))[-lm:] + ' ' else: s = ' ' * lm - return ((0, s, color.build('default', 'default', 'bold')),) + return [RenderString(s, 0, color.build('default', 'default', 'bold'))] # get mode name def name(self): diff --git a/mode/hex.py b/mode/hex.py index cf698eb..8c56b7a 100644 --- a/mode/hex.py +++ b/mode/hex.py @@ -4,6 +4,7 @@ import color, mode from lex import Grammar, PatternRule, RegionRule from method import Method, Argument from point import Point +from render import RenderString class HexSetByteOrder(Method): '''Sets the byte-order to use to 'little', 'big', or 'native' order''' @@ -310,16 +311,16 @@ class Hex(mode.Fundamental): def get_address(self, y, x): return (y * 16) + x - def get_lmargin(self, y, x=0, ended=False, cont=False): + def get_lmargin(self, w, y, x, ended=False, cont=False): lm = self.lmargin if ended: s = ' -------- ' else: addr = self.get_address(y, x) s = '0x%08x ' % addr - return ((0, s, self.ccyan),) + return (RenderString(s, 0, self.ccyan),) - def get_rmargin(self, y, x=0, ended=False, cont=False): + def get_rmargin(self, w, y, x, ended=False, cont=False): if ended: return ((0, '', 0),) else: @@ -329,17 +330,17 @@ class Hex(mode.Fundamental): i = self.window.buffer.cursorx_to_datax(cy, cx) if i is None: - rmargins = ((0, s, self.cgreen),) + rmargins = (RenderString(s, 0, self.cgreen),) elif i < len(s): - rmargins = ((0, s[0:i], self.cgreen), - (i, s[i], self.ccursor), - (i + 1, s[i+1:], self.cgreen)) + rmargins = (RenderString(s[0:i], 0, self.cgreen), + RenderString(s[i], i, self.ccursor), + RenderString(s[i+1:], i + 1, self.cgreen)) else: - rmargins= ((0, s[0:i], self.cgreen), - (i, s[i], self.cgreen),) + rmargins= (RenderString(s[0:i], 0, self.cgreen), + RenderString(s[i], i, self.cgreen)) return rmargins else: - return ((0, s, self.cgreen),) + return (RenderString(s, 0, self.cgreen),) def read_data(self, cy, ix, size): b = self.window.buffer diff --git a/render.py b/render.py new file mode 100644 index 0000000..8b10cdf --- /dev/null +++ b/render.py @@ -0,0 +1,12 @@ +import color +from point import Point + +class RenderString(object): + def __init__(self, s, x=0, attrs=None): + if attrs is None: + attrs = color.build('default', 'default') + self.string = s + self.x = x + self.attrs = attrs + def draw(self, cwin, y, x): + cwin.addstr(y, self.x + x, self.string, self.attrs) diff --git a/window.py b/window.py index 904f4d0..409824d 100644 --- a/window.py +++ b/window.py @@ -1,6 +1,7 @@ import os.path, string import color, highlight, regex from point import Point +from render import RenderString WORD_LETTERS = list(string.letters + string.digits) @@ -11,23 +12,22 @@ WORD_LETTERS = list(string.letters + string.digits) # error. both buffer and window need to be aware of this possibility for points. class Window(object): - boxtype = 'window' 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.height = height + self.width = width - 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 = "" + self.first = Point(0, 0) + self.last = None + self.cursor = Point(0, 0) + self.mark = None + self.active_point = None + self.input_line = "" + self.hidden_ranges = {} + self.hidden_lines = {} if mode_name is not None: pass @@ -585,6 +585,31 @@ class Window(object): else: return self.buffer.lines[y][x] + # hiding stuff + def hide(self, y1, y2): + if y2 == 0: + return + for (py1, py2) in self.hidden_ranges.itervalues(): + if ((y1 >= py1 and y1 < py2) or + (y2 >= py1 and y2 < py2) or + (y1 <= py1 and y2 >= py2)): + return + for i in range(y1, y2): + self.hidden_lines[i] = y2 + self.hidden_ranges[y2] = (y1, y2) + def ishidden(self, y): + return self.hidden_lines.setdefault(y, False) + def hiddenindicator(self, y): + return y in self.hidden_ranges + def unhide(self, y): + py2 = self.ishidden(y) + if not py2: + return + py1 = self.hidden_ranges[py2][0] + del self.hidden_ranges[py2] + for i in range(py1, py2): + self.hidden_lines[i] = False + # undo/redo def undo(self): p = self.buffer.undo() @@ -629,8 +654,78 @@ class Window(object): 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) + + # application drawing + # + # render methods return a list of lists of RenderString objects + # (i.e. multiple physical lines, each containing multiple areas to be drawn) + def render_line(self, y, width): + modename = self.mode.name() + if modename in self.buffer.highlights: + return self.render_line_lit(y, width) + else: + return self.render_line_raw(y, width) + + def render_line_raw(self, y, width): + if y >= len(self.buffer.lines): + return [[RenderString('~', 0, color.build('red', 'default'))]] + x = 0 + line = self.buffer.lines[y] + lines = [] + if line: + while x < len(line): + lines.append(tuple([RenderString(line[x:x + width], 0)])) + x += width + else: + lines.append(tuple([RenderString('', 0)])) + return lines + + def render_line_lit(self, y, width): + if y >= len(self.buffer.lines): + return [[RenderString('~', 0, color.build('red', 'default'))]] + + modename = self.mode.name() + highlighter = self.buffer.highlights[modename] + + line = [] + lines = [] + + j = x = 0 + while j < len(highlighter.tokens[y]): + # get our token, and do some basic checking/bleaching + token = highlighter.tokens[y][j] + if token.string.endswith('\n'): + tstring = token.string[:-1] + else: + tstring = token.string + assert token.y == y, '%d == %d' % (token.y, y) + + # figure out what portion of the string to use + s_offset = max(x - token.x, 0) + s = tstring[s_offset:] + + # for debugging things like lexing/relexing/etc. + if token._debug: + attr = color.build('blue', 'green') + elif token.color: + attr = color.build(*token.color) + else: + attr = color.build("default", "default") + + # ok, so add a region with data, position, and color info + x_offset = max(token.x - x, 0) + line.append(RenderString(s[:width - x_offset], x_offset, attr)) + + # see if the token is wrapping, or if we move on to the next one + if x_offset + len(s) > width: + lines.append(line) + line = [] + x += width + else: + j += 1 + lines.append(line) + return lines