diff options
Diffstat (limited to 'buffer.nim')
-rw-r--r-- | buffer.nim | 404 |
1 files changed, 203 insertions, 201 deletions
diff --git a/buffer.nim b/buffer.nim index fcc24b76..06459d11 100644 --- a/buffer.nim +++ b/buffer.nim @@ -1,3 +1,5 @@ +#beware, awful code ahead + import options import uri import tables @@ -15,20 +17,19 @@ import twtstr type Buffer* = ref BufferObj BufferObj = object - text*: string - rawtext*: string - lines*: seq[int] - rawlines*: seq[int] + fmttext*: seq[string] + rawtext*: seq[string] title*: string hovertext*: string - htmlSource*: XmlNode + htmlsource*: XmlNode width*: int height*: int - cursorX*: int - cursorY*: int + cursorx*: int + cursory*: int + cursorchar*: int xend*: int - fromX*: int - fromY*: int + fromx*: int + fromy*: int nodes*: seq[HtmlNode] links*: seq[HtmlNode] clickables*: seq[HtmlNode] @@ -40,73 +41,50 @@ type document*: Document proc newBuffer*(attrs: TermAttributes): Buffer = - return Buffer(lines: @[0], - rawlines: @[0], - width: attrs.termWidth, + return Buffer(width: attrs.termWidth, height: attrs.termHeight, - cursorY: 1, - document: newDocument()) + attrs: attrs) func lastLine*(buffer: Buffer): int = - assert(buffer.rawlines.len == buffer.lines.len) - return buffer.lines.len - 1 + assert(buffer.fmttext.len == buffer.rawtext.len) + return buffer.fmttext.len - 1 func lastVisibleLine*(buffer: Buffer): int = - return min(buffer.fromY + buffer.height - 1, buffer.lastLine()) - -func currentLine*(buffer: Buffer): int = - return buffer.cursorY - 1 + return min(buffer.fromy + buffer.height - 1, buffer.lastLine()) -func textBetween*(buffer: Buffer, s: int, e: int): string = - return buffer.text.runeSubstr(s, e - s) - -#doesn't include newline -func lineLength*(buffer: Buffer, line: int): int = - assert buffer.lines.len > line - let str = buffer.textBetween(buffer.lines[line - 1], buffer.lines[line]) - let len = mk_wcswidth_cjk(str) - if len >= 0: - return len - else: - return 0 - -func rawLineLength*(buffer: Buffer, line: int): int = - assert buffer.rawlines.len > line - let str = buffer.textBetween(buffer.rawlines[line - 1], buffer.rawlines[line]) - let len = mk_wcswidth_cjk(str) - if len >= 0: - return len - else: - return 0 +func realCurrentLineLength*(buffer: Buffer): int = + return mk_wcswidth_cjk(buffer.rawtext[buffer.cursory]) func currentLineLength*(buffer: Buffer): int = - return buffer.lineLength(buffer.cursorY) - -func currentRawLineLength*(buffer: Buffer): int = - return buffer.rawLineLength(buffer.cursorY) - -func cursorAtLineEnd*(buffer: Buffer): bool = - return buffer.cursorX == buffer.currentRawLineLength() + return buffer.rawtext[buffer.cursory].runeLen() func atPercentOf*(buffer: Buffer): int = - return (100 * buffer.cursorY) div buffer.lastLine() - + return (100 * buffer.cursory) div buffer.lastLine() + +func fmtBetween*(buffer: Buffer, sx: int, sy: int, ex: int, ey: int): string = + if sy < ey: + result &= buffer.rawtext[sy].runeSubstr(sx) + var i = sy + 1 + while i < ey - 1: + result &= buffer.rawtext[i] + inc i + result &= buffer.rawtext[i].runeSubstr(0, ex - sx) + else: + result &= buffer.rawtext[sy].runeSubstr(sx, ex - sx) func visibleText*(buffer: Buffer): string = - return buffer.textBetween(buffer.lines[buffer.fromY], buffer.lines[buffer.lastVisibleLine()]) + return buffer.fmttext[buffer.fromy..buffer.lastVisibleLine()].join("\n") func lastNode*(buffer: Buffer): HtmlNode = return buffer.nodes[^1] -func onNewLine*(buffer: Buffer): bool = - return buffer.text.len == 0 or buffer.text[^1] == '\n' - -func onSpace*(buffer: Buffer): bool = - return buffer.text.len > 0 and buffer.text[^1] == ' ' - func cursorOnNode*(buffer: Buffer, node: HtmlNode): bool = - return buffer.rawlines[buffer.cursorY - 1] + buffer.cursorX >= node.rawchar and - buffer.rawlines[buffer.cursorY - 1] + buffer.cursorX < node.rawend + if node.y == node.ey and node.y == buffer.cursory: + return buffer.cursorx >= node.x and buffer.cursorx < node.ex + else: + return (buffer.cursory == node.y and buffer.cursorx >= node.x) or + (buffer.cursory > node.y and buffer.cursory < node.ey) or + (buffer.cursory == node.ey and buffer.cursorx < node.ex) func findSelectedElement*(buffer: Buffer): Option[HtmlElement] = if buffer.selectedlink != nil and buffer.selectedLink.parentNode of HtmlElement: @@ -117,9 +95,6 @@ func findSelectedElement*(buffer: Buffer): Option[HtmlElement] = if buffer.cursorOnNode(node): return some(HtmlElement(node)) return none(HtmlElement) -func cursorAt*(buffer: Buffer): int = - return buffer.rawlines[buffer.currentLine()] + buffer.cursorX - func canScroll*(buffer: Buffer): bool = return buffer.lastLine() > buffer.height @@ -131,7 +106,7 @@ func getElementById*(buffer: Buffer, id: string): HtmlElement = proc findSelectedNode*(buffer: Buffer): Option[HtmlNode] = for node in buffer.nodes: if node.getFmtLen() > 0 and node.displayed(): - if buffer.cursorY >= node.y and buffer.cursorY <= node.y + node.height and buffer.cursorX >= node.x and buffer.cursorX <= node.x + node.width: + if buffer.cursory >= node.y and buffer.cursory <= node.y + node.height and buffer.cursorx >= node.x and buffer.cursorx <= node.x + node.width: return some(node) return none(HtmlNode) @@ -160,12 +135,12 @@ proc addNode*(buffer: Buffer, htmlNode: HtmlNode) = buffer.idelements[elem.id] = elem proc writefmt*(buffer: Buffer, str: string) = - buffer.text &= str + buffer.fmttext &= str if buffer.printwrite: stdout.write(str) proc writefmt*(buffer: Buffer, c: char) = - buffer.text &= c + buffer.rawtext &= $c if buffer.printwrite: stdout.write(c) @@ -173,7 +148,7 @@ proc writeraw*(buffer: Buffer, str: string) = buffer.rawtext &= str proc writeraw*(buffer: Buffer, c: char) = - buffer.rawtext &= c + buffer.rawtext &= $c proc write*(buffer: Buffer, str: string) = buffer.writefmt(str) @@ -184,10 +159,8 @@ proc write*(buffer: Buffer, c: char) = buffer.writeraw(c) proc clearText*(buffer: Buffer) = - buffer.text = "" - buffer.rawtext = "" - buffer.lines = @[0] - buffer.rawlines = @[0] + buffer.fmttext.setLen(0) + buffer.rawtext.setLen(0) proc clearNodes*(buffer: Buffer) = buffer.nodes.setLen(0) @@ -199,229 +172,236 @@ proc clearNodes*(buffer: Buffer) = proc clearBuffer*(buffer: Buffer) = buffer.clearText() buffer.clearNodes() - buffer.cursorX = 0 - buffer.cursorY = 1 - buffer.fromX = 0 - buffer.fromY = 0 + buffer.cursorx = 0 + buffer.cursory = 0 + buffer.fromx = 0 + buffer.fromy = 0 buffer.hovertext = "" buffer.selectedlink = nil proc scrollTo*(buffer: Buffer, y: int): bool = - if y == buffer.fromY: + if y == buffer.fromy: return false - buffer.fromY = min(max(buffer.lastLine() - buffer.height + 1, 0), y) - buffer.cursorY = min(max(buffer.fromY, buffer.cursorY), buffer.fromY + buffer.height) + buffer.fromy = min(max(buffer.lastLine() - buffer.height + 1, 0), y) + buffer.cursory = min(max(buffer.fromy, buffer.cursory), buffer.fromy + buffer.height) return true proc cursorTo*(buffer: Buffer, x: int, y: int): bool = result = false - buffer.cursorY = min(max(y, 1), buffer.lastLine()) - if buffer.fromY >= buffer.cursorY: - buffer.fromY = max(buffer.cursorY - 1, 0) + buffer.cursory = min(max(y, 0), buffer.lastLine()) + if buffer.fromy > buffer.cursory: + buffer.fromy = max(buffer.cursory, 0) result = true - elif buffer.fromY + buffer.height <= buffer.cursorY: - buffer.fromY = max(buffer.cursorY - buffer.height + 1, 0) + elif buffer.fromy + buffer.height - 1 <= buffer.cursory: + buffer.fromy = max(buffer.cursory - buffer.height + 2, 0) result = true - buffer.cursorX = min(max(x, 0), buffer.currentRawLineLength()) - buffer.fromX = min(max(buffer.currentRawLineLength() - buffer.width + 1, 0), 0) #TODO + buffer.cursorx = min(max(x, 0), buffer.currentLineLength()) + #buffer.fromX = min(max(buffer.currentLineLength() - buffer.width + 1, 0), 0) #TODO proc cursorDown*(buffer: Buffer): bool = - if buffer.cursorY < buffer.lastLine(): - buffer.cursorY += 1 - if buffer.cursorX > buffer.currentRawLineLength(): - if buffer.xend == 0: - buffer.xend = buffer.cursorX - buffer.cursorX = buffer.currentRawLineLength() + if buffer.cursory < buffer.lastLine(): + inc buffer.cursory + if buffer.cursorx >= buffer.currentLineLength(): + buffer.cursorx = max(buffer.currentLineLength() - 1, 0) elif buffer.xend > 0: - buffer.cursorX = min(buffer.currentRawLineLength(), buffer.xend) - if buffer.cursorY > buffer.lastVisibleLine(): - buffer.fromY += 1 + buffer.cursorx = min(buffer.currentLineLength() - 1, buffer.xend) + if buffer.cursory >= buffer.lastVisibleLine() and buffer.lastVisibleLine() != buffer.lastLine(): + inc buffer.fromy return true return false proc cursorUp*(buffer: Buffer): bool = - if buffer.cursorY > 1: - buffer.cursorY -= 1 - if buffer.cursorX > buffer.currentRawLineLength(): - if buffer.xend == 0: - buffer.xend = buffer.cursorX - buffer.cursorX = buffer.currentRawLineLength() + if buffer.cursory > 0: + dec buffer.cursory + if buffer.cursorx > buffer.currentLineLength(): + if buffer.cursorx == 0: + buffer.xend = buffer.cursorx + buffer.cursorx = buffer.currentLineLength() elif buffer.xend > 0: - buffer.cursorX = min(buffer.currentRawLineLength(), buffer.xend) - if buffer.cursorY <= buffer.fromY: - buffer.fromY -= 1 + buffer.cursorx = min(buffer.currentLineLength(), buffer.xend) + if buffer.cursory < buffer.fromy: + dec buffer.fromy return true return false proc cursorRight*(buffer: Buffer): bool = - if buffer.cursorX < buffer.currentRawLineLength(): - buffer.cursorX += mk_wcwidth_cjk(buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX)) + if buffer.cursorx < buffer.currentLineLength() - 1: + inc buffer.cursorx buffer.xend = 0 else: - buffer.xend = buffer.cursorX + buffer.xend = buffer.cursorx return false proc cursorLeft*(buffer: Buffer): bool = - if buffer.cursorX > 0: - buffer.cursorX -= mk_wcwidth_cjk(buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX)) - buffer.cursorX -= 1 + if buffer.cursorx > 0: + dec buffer.cursorx buffer.xend = 0 return false proc cursorLineBegin*(buffer: Buffer) = - buffer.cursorX = 0 + buffer.cursorx = 0 buffer.xend = 0 proc cursorLineEnd*(buffer: Buffer) = - buffer.cursorX = buffer.currentRawLineLength() - buffer.xend = buffer.cursorX + buffer.cursorx = buffer.currentLineLength() - 1 + buffer.xend = buffer.cursorx iterator revnodes*(buffer: Buffer): HtmlNode {.inline.} = var i = buffer.nodes.len - 1 while i >= 0: yield buffer.nodes[i] - i -= 1 - -proc cursorNextNode*(buffer: Buffer): bool = - for node in buffer.nodes: - if node.displayed(): - if node.y > buffer.cursorY or (node.y == buffer.cursorY and node.x > buffer.cursorX): - return buffer.cursorTo(node.x, node.y) - buffer.cursorLineEnd() - return false - -proc cursorPrevNode*(buffer: Buffer): bool = - var prevnode: HtmlNode - for node in buffer.nodes: - if node.displayed(): - if node.y >= buffer.cursorY and node.x >= buffer.cursorX and prevnode != nil: - return buffer.cursorTo(prevnode.x, prevnode.y) - prevnode = node - buffer.cursorLineBegin() - return false + dec i proc cursorNextWord*(buffer: Buffer): bool = - if buffer.cursorAtLineEnd(): - if buffer.cursorY < buffer.lastLine(): - let ret = buffer.cursorDown() - buffer.cursorLineBegin() - return ret - else: - buffer.cursorLineEnd() - return false - - var res = buffer.cursorRight() - while buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX) != Rune(' '): - if buffer.cursorAtLineEnd(): - return res - res = res or buffer.cursorRight() + let llen = buffer.currentLineLength() - 1 + var r: Rune + var x = buffer.cursorx + var y = buffer.cursory + fastRuneAt(buffer.rawtext[y], x, r, false) + + while r != Rune(' '): + if x >= llen: + break + inc x + fastRuneAt(buffer.rawtext[y], x, r, false) + + while r == Rune(' '): + if x >= llen: + break + inc x + fastRuneAt(buffer.rawtext[y], x, r, false) + + if x >= llen: + if y < buffer.lastLine(): + inc y + x = 0 + return buffer.cursorTo(x, y) proc cursorPrevWord*(buffer: Buffer): bool = - if buffer.cursorX <= 1: - if buffer.cursorY > 1: - let ret = buffer.cursorUp() - buffer.cursorLineEnd() - return ret - else: - buffer.cursorLineBegin() - return false - - discard buffer.cursorLeft() - while buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX) != Rune(' '): - if buffer.cursorX == 0: - return false - discard buffer.cursorLeft() + var r: Rune + var x = buffer.cursorx + var y = buffer.cursory + fastRuneAt(buffer.rawtext[y], x, r, false) + + while r != Rune(' '): + if x == 0: + break + dec x + fastRuneAt(buffer.rawtext[y], x, r, false) + + while r == Rune(' '): + if x == 0: + break + dec x + fastRuneAt(buffer.rawtext[y], x, r, false) + + if x == 0: + if y < buffer.lastLine(): + dec y + x = buffer.rawtext[y].runeLen() - 1 + return buffer.cursorTo(x, y) iterator revclickables*(buffer: Buffer): HtmlNode {.inline.} = var i = buffer.clickables.len - 1 while i >= 0: yield buffer.clickables[i] - i -= 1 + dec i proc cursorNextLink*(buffer: Buffer): bool = for node in buffer.clickables: - if node.y > buffer.cursorY or (node.y == buffer.cursorY and node.x > buffer.cursorX): - return buffer.cursorTo(node.x, node.y) + if node.y > buffer.cursory or (node.y == buffer.cursorY and node.x > buffer.cursorx): + result = buffer.cursorTo(node.x, node.y) + if buffer.cursorx < buffer.currentLineLength(): + var r: Rune + fastRuneAt(buffer.rawtext[buffer.cursory], buffer.cursorx, r, false) + if r == Rune(' '): + return result or buffer.cursorNextWord() + return result return false proc cursorPrevLink*(buffer: Buffer): bool = for node in buffer.revclickables: - if node.y < buffer.cursorY or (node.y == buffer.cursorY and node.x < buffer.cursorX): + if node.y < buffer.cursorY or (node.y == buffer.cursorY and node.x < buffer.cursorx): return buffer.cursorTo(node.x, node.y) return false proc cursorFirstLine*(buffer: Buffer): bool = - if buffer.fromY > 0: - buffer.fromY = 0 + if buffer.fromy > 0: + buffer.fromy = 0 result = true else: result = false - buffer.cursorY = 1 + buffer.cursorY = 0 buffer.cursorLineBegin() proc cursorLastLine*(buffer: Buffer): bool = - if buffer.fromY < buffer.lastLine() - buffer.height: - buffer.fromY = buffer.lastLine() - (buffer.height - 1) + if buffer.fromy < buffer.lastLine() - buffer.height: + buffer.fromy = buffer.lastLine() - (buffer.height - 2) result = true else: result = false - buffer.cursorY = buffer.lastLine() + buffer.cursory = buffer.lastLine() buffer.cursorLineBegin() +proc cursorTop*(buffer: Buffer): bool = + buffer.cursorY = buffer.fromy + return false + +proc cursorMiddle*(buffer: Buffer): bool = + buffer.cursorY = min(buffer.fromy + (buffer.height - 2) div 2, buffer.lastLine()) + return false + +proc cursorBottom*(buffer: Buffer): bool = + buffer.cursorY = min(buffer.fromy + buffer.height - 2, buffer.lastLine()) + return false + +proc centerLine*(buffer: Buffer): bool = + let ny = max(min(buffer.cursory - buffer.height div 2, buffer.lastLine() - buffer.height + 2), 0) + if ny != buffer.fromy: + buffer.fromy = ny + return true + return false + proc halfPageUp*(buffer: Buffer): bool = - buffer.cursorY = max(buffer.cursorY - buffer.height div 2 + 1, 1) - if buffer.fromY - 1 > buffer.cursorY or true: - buffer.fromY = max(0, buffer.fromY - buffer.height div 2 + 1) + buffer.cursory = max(buffer.cursorY - buffer.height div 2 + 1, 0) + let nfy = max(0, buffer.fromy - buffer.height div 2 + 1) + if nfy != buffer.fromy: + buffer.fromy = nfy return true return false proc halfPageDown*(buffer: Buffer): bool = - buffer.cursorY = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine()) - buffer.fromY = min(max(buffer.lastLine() - buffer.height + 1, 0), buffer.fromY + buffer.height div 2 - 1) - return true + buffer.cursory = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine()) + let nfy = min(max(buffer.lastLine() - buffer.height + 2, 0), buffer.fromy + buffer.height div 2 - 1) + if nfy != buffer.fromy: + buffer.fromy = nfy + return true + return false proc pageUp*(buffer: Buffer): bool = buffer.cursorY = max(buffer.cursorY - buffer.height + 1, 1) - buffer.fromY = max(0, buffer.fromY - buffer.height) + buffer.fromy = max(0, buffer.fromy - buffer.height) return true proc pageDown*(buffer: Buffer): bool = buffer.cursorY = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine()) - buffer.fromY = min(max(buffer.lastLine() - buffer.height + 1, 0), buffer.fromY + buffer.height div 2) - return true - -proc cursorTop*(buffer: Buffer): bool = - buffer.cursorY = buffer.fromY + 1 - return false - -proc cursorMiddle*(buffer: Buffer): bool = - buffer.cursorY = min(buffer.fromY + buffer.height div 2, buffer.lastLine()) - return false - -proc cursorBottom*(buffer: Buffer): bool = - buffer.cursorY = min(buffer.fromY + buffer.height - 1, buffer.lastLine()) - return false - -proc centerLine*(buffer: Buffer): bool = - if min(buffer.cursorY - buffer.height div 2, buffer.lastLine()) == buffer.fromY: - return false - buffer.fromY = min(buffer.cursorY - buffer.height div 2, buffer.lastLine()) + buffer.fromy = min(max(buffer.lastLine() - buffer.height + 1, 0), buffer.fromy + buffer.height div 2) return true proc scrollDown*(buffer: Buffer): bool = - if buffer.fromY + buffer.height <= buffer.lastLine(): - buffer.fromY += 1 - if buffer.fromY >= buffer.cursorY: + if buffer.fromy + buffer.height - 1 <= buffer.lastLine(): + inc buffer.fromy + if buffer.fromy >= buffer.cursory: discard buffer.cursorDown() return true discard buffer.cursorDown() return false proc scrollUp*(buffer: Buffer): bool = - if buffer.fromY > 0: - buffer.fromY -= 1 - if buffer.fromY + buffer.height <= buffer.cursorY: + if buffer.fromy > 0: + dec buffer.fromy + if buffer.fromy + buffer.height - 1 <= buffer.cursorY: discard buffer.cursorUp() return true discard buffer.cursorUp() @@ -437,6 +417,13 @@ proc checkLinkSelection*(buffer: Buffer): bool = buffer.selectedlink.fmttext = buffer.selectedlink.getFmtText() buffer.selectedlink = nil buffer.hovertext = "" + var stack: seq[HtmlNode] + stack.add(anchor) + while stack.len > 0: + let elem = stack.pop() + elem.fmttext = elem.getFmtText() + for child in elem.childNodes: + stack.add(child) for node in buffer.links: if buffer.cursorOnNode(node): buffer.selectedlink = node @@ -444,7 +431,13 @@ proc checkLinkSelection*(buffer: Buffer): bool = assert(anchor != nil) anchor.selected = true buffer.hovertext = HtmlAnchorElement(anchor).href - node.fmttext = node.getFmtText() + var stack: seq[HtmlNode] + stack.add(anchor) + while stack.len > 0: + let elem = stack.pop() + elem.fmttext = elem.getFmtText() + for child in elem.childNodes: + stack.add(child) return true return false @@ -460,3 +453,12 @@ proc setLocation*(buffer: Buffer, uri: Uri) = proc gotoLocation*(buffer: Buffer, uri: Uri) = buffer.document.location = buffer.document.location.combine(uri) + +proc refreshTermAttrs*(buffer: Buffer): bool = + let newAttrs = getTermAttributes() + if newAttrs != buffer.attrs: + buffer.attrs = newAttrs + buffer.width = newAttrs.termWidth + buffer.height = newAttrs.termHeight + return true + return false |