diff options
author | bptato <nincsnevem662@gmail.com> | 2021-08-05 22:57:38 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-08-05 23:13:55 +0200 |
commit | b94597a68eb8572cf8f521ee9c39cc7d9d310827 (patch) | |
tree | a487471466f80a4e95cfefb4d7084f29a840eacc | |
parent | 087f830528b41b00d0bf7a501f7b0472f75ffb18 (diff) | |
download | chawan-b94597a68eb8572cf8f521ee9c39cc7d9d310827.tar.gz |
Implement new buffer model which supports X coords
...also, one can kinda use this as a file viewer now I guess
-rw-r--r-- | res/config | 6 | ||||
-rw-r--r-- | src/config.nim | 8 | ||||
-rw-r--r-- | src/css/cssparser.nim | 3 | ||||
-rw-r--r-- | src/css/style.nim | 2 | ||||
-rw-r--r-- | src/io/buffer.nim | 511 | ||||
-rw-r--r-- | src/io/display.nim | 109 | ||||
-rw-r--r-- | src/io/term.nim (renamed from src/utils/termattrs.nim) | 3 | ||||
-rw-r--r-- | src/io/twtio.nim | 17 | ||||
-rw-r--r-- | src/main.nim | 20 | ||||
-rw-r--r-- | src/utils/eprint.nim | 11 | ||||
-rw-r--r-- | src/utils/radixtree.nim | 3 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 8 |
12 files changed, 334 insertions, 367 deletions
diff --git a/res/config b/res/config index a4f5ebd0..c9a7c099 100644 --- a/res/config +++ b/res/config @@ -23,8 +23,12 @@ nmap C-f PAGE_DOWN nmap C-b PAGE_UP nmap M-[6~ PAGE_DOWN nmap M-[5~ PAGE_UP +nmap > PAGE_RIGHT +nmap < PAGE_LEFT nmap C-e SCROLL_DOWN nmap C-y SCROLL_UP +nmap zh SCROLL_LEFT +nmap zl SCROLL_RIGHT nmap J SCROLL_DOWN nmap K SCROLL_UP nmap C-m CLICK @@ -37,7 +41,7 @@ nmap gg CURSOR_FIRST_LINE nmap G CURSOR_LAST_LINE nmap M-[H CURSOR_FIRST_LINE nmap M-[F CURSOR_LAST_LINE -nmap z CENTER_LINE +nmap zz CENTER_LINE nmap C-g LINE_INFO #line editing keybindings diff --git a/src/config.nim b/src/config.nim index 002532cc..41d4a6d4 100644 --- a/src/config.nim +++ b/src/config.nim @@ -15,9 +15,9 @@ type ACTION_CURSOR_NEXT_WORD, ACTION_CURSOR_PREV_WORD, ACTION_CURSOR_NEXT_NODE, ACTION_CURSOR_PREV_NODE, ACTION_CURSOR_NEXT_LINK, ACTION_CURSOR_PREV_LINK, - ACTION_PAGE_DOWN, ACTION_PAGE_UP, + ACTION_PAGE_DOWN, ACTION_PAGE_UP, ACTION_PAGE_LEFT, ACTION_PAGE_RIGHT, ACTION_HALF_PAGE_DOWN, ACTION_HALF_PAGE_UP, - ACTION_SCROLL_DOWN, ACTION_SCROLL_UP, + ACTION_SCROLL_DOWN, ACTION_SCROLL_UP, ACTION_SCROLL_LEFT, ACTION_SCROLL_RIGHT, ACTION_CLICK, ACTION_CHANGE_LOCATION, ACTION_RELOAD, ACTION_RESHAPE, ACTION_REDRAW, @@ -62,7 +62,7 @@ func getRealKey(key: string): string = elif c == '-' and meta == 1: inc meta elif meta == 1: - realk &= 'C' & c + realk &= 'M' & c meta = 0 elif meta == 2: realk &= '\e' @@ -77,6 +77,8 @@ func getRealKey(key: string): string = realk &= c if control == 1: realk &= 'C' + if meta == 1: + realk &= 'M' return realk func constructActionTable*(origTable: ActionMap): ActionMap = diff --git a/src/css/cssparser.nim b/src/css/cssparser.nim index 5e76bcc0..732a7da2 100644 --- a/src/css/cssparser.nim +++ b/src/css/cssparser.nim @@ -9,9 +9,8 @@ import options import sequtils import sugar -import ../io/twtio - import ../utils/twtstr +import ../utils/eprint import ../types/enums diff --git a/src/css/style.nim b/src/css/style.nim index d98c1f6c..6df59f22 100644 --- a/src/css/style.nim +++ b/src/css/style.nim @@ -2,9 +2,9 @@ import unicode import terminal import tables -import ../io/twtio import ../utils/twtstr +import ../utils/eprint import ../types/enums diff --git a/src/io/buffer.nim b/src/io/buffer.nim index 6d79700f..31c02aa9 100644 --- a/src/io/buffer.nim +++ b/src/io/buffer.nim @@ -7,27 +7,35 @@ import unicode import ../types/enums import ../types/color -import ../utils/termattrs import ../utils/twtstr +import ../utils/eprint import ../html/dom import ./twtio +import ./term type - BufferCell = object - rune*: Rune + Cell = object of RootObj fgcolor*: CellColor bgcolor*: CellColor italic: bool bold: bool underline: bool + BufferCell = object of Cell + rune*: Rune + +# xterm supports max 2 characters per cell by default. might make the tuple a +# seq in the future but for now it's fine like this + DisplayCell = object of Cell + runes*: tuple[a: Rune, b: Rune] + Buffer* = ref BufferObj BufferObj = object title*: string lines*: seq[seq[BufferCell]] - display*: seq[BufferCell] + display*: seq[DisplayCell] hovertext*: string width*: int height*: int @@ -38,7 +46,6 @@ type fromy*: int nodes*: seq[Node] links*: seq[Node] - clickables*: seq[Node] elements*: seq[Element] idelements*: Table[string, Element] selectedlink*: Node @@ -46,82 +53,52 @@ type attrs*: TermAttributes document*: Document displaycontrols*: bool + redraw*: bool + location*: Uri - #TODO remove these - fmttext*: seq[string] - rawtext*: seq[string] - -proc newBuffer*(attrs: TermAttributes): Buffer = +func newBuffer*(attrs: TermAttributes): Buffer = new(result) result.width = attrs.termWidth result.height = attrs.termHeight result.attrs = attrs - let cells = result.width * result.height - result.display = newSeq[BufferCell](cells) - -proc setText*(buffer: Buffer, x: int, y: int, text: seq[Rune]) = - discard - -proc setDisplayText(buffer: Buffer, x: int, y: int, text: seq[Rune]) = - let pos = y * buffer.width + x - var i = 0 - while i < text.len: - buffer.display[pos + i].rune = text[i] - -proc refreshDisplay*(buffer: Buffer) = - var y = 0 - for line in buffer.lines[buffer.fromy..buffer.fromy+buffer.height]: - var w = 0 - var i = 0 - while w < buffer.fromx and i < line.len: - w += line[i].rune.width() - inc i - - let dls = y * buffer.width - var j = 0 - while w < buffer.fromx + buffer.width and i < line.len: - w += line[i].rune.width() - buffer.display[dls + j] = line[i] - inc i - - inc y + result.display = newSeq[DisplayCell](result.width * result.height) func generateFullOutput*(buffer: Buffer): string = var x = 0 var y = 0 for cell in buffer.display: - - discard + if x >= buffer.width: + inc y + result &= '\n' + x = 0 + + if cell.runes.a != Rune(0): + result &= $cell.runes.a + if cell.runes.b != Rune(0): + result &= $cell.runes.b + inc x -#TODO go through these and remove ones that don't make sense in the new model func lastLine*(buffer: Buffer): int = - assert(buffer.fmttext.len == buffer.rawtext.len) - return buffer.fmttext.len - 1 + return buffer.lines.len - 1 func lastVisibleLine*(buffer: Buffer): int = return min(buffer.fromy + buffer.height - 1, buffer.lastLine()) -func currentLineLength*(buffer: Buffer): int = - return buffer.rawtext[buffer.cursory].width() +func width(line: seq[BufferCell]): int = + for c in line: + result += c.rune.width() -func atPercentOf*(buffer: Buffer): int = - if buffer.fmttext.len == 0: return 100 - return (100 * (buffer.cursory + 1)) div (buffer.lastLine() + 1) +func currentLineWidth*(buffer: Buffer): int = + return buffer.lines[buffer.cursory].width() -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 maxScreenWidth*(buffer: Buffer): int = + for line in buffer.lines[buffer.fromy..buffer.lastVisibleLine()]: + result = max(line.width(), result) -func visibleText*(buffer: Buffer): string = - return buffer.fmttext[buffer.fromy..buffer.lastVisibleLine()].join("\n") +func atPercentOf*(buffer: Buffer): int = + if buffer.lines.len == 0: return 100 + return (100 * (buffer.cursory + 1)) div (buffer.lastLine() + 1) func lastNode*(buffer: Buffer): Node = return buffer.nodes[^1] @@ -158,45 +135,17 @@ proc findSelectedNode*(buffer: Buffer): Option[Node] = return some(node) return none(Node) -proc addNode*(buffer: Buffer, node: Node) = - buffer.nodes.add(node) - - if node.isTextNode() and node.parentElement != nil and node.parentElement.getStyle().islink: - buffer.links.add(node) - - if node.isElemNode(): - case Element(node).tagType - of TAG_INPUT, TAG_OPTION: - if not Element(node).hidden: - buffer.clickables.add(node) - else: discard - elif node.isTextNode(): - if node.parentElement != nil and node.getStyle().islink: - let anchor = node.ancestor(TAG_A) - assert(anchor != nil) - buffer.clickables.add(anchor) - - if node.isElemNode(): - let elem = Element(node) - buffer.elements.add(elem) - if elem.id != "" and not buffer.idelements.hasKey(elem.id): - buffer.idelements[elem.id] = elem - proc writefmt*(buffer: Buffer, str: string) = - buffer.fmttext &= str - if buffer.printwrite: - stdout.write(str) + discard proc writefmt*(buffer: Buffer, c: char) = - buffer.rawtext &= $c - if buffer.printwrite: - stdout.write(c) + discard proc writeraw*(buffer: Buffer, str: string) = - buffer.rawtext &= str + discard proc writeraw*(buffer: Buffer, c: char) = - buffer.rawtext &= $c + discard proc write*(buffer: Buffer, str: string) = buffer.writefmt(str) @@ -207,13 +156,11 @@ proc write*(buffer: Buffer, c: char) = buffer.writeraw(c) proc clearText*(buffer: Buffer) = - buffer.fmttext.setLen(0) - buffer.rawtext.setLen(0) + buffer.lines.setLen(0) proc clearNodes*(buffer: Buffer) = buffer.nodes.setLen(0) buffer.links.setLen(0) - buffer.clickables.setLen(0) buffer.elements.setLen(0) buffer.idelements.clear() @@ -227,72 +174,77 @@ proc clearBuffer*(buffer: Buffer) = buffer.hovertext = "" buffer.selectedlink = nil -proc scrollTo*(buffer: Buffer, y: int): bool = +proc restoreCursorX(buffer: Buffer) = + buffer.cursorx = min(buffer.currentLineWidth() - 1, buffer.xend) + +proc scrollTo*(buffer: Buffer, y: int) = if y == buffer.fromy: - return false + return 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 + buffer.redraw = true + buffer.restoreCursorX() -proc cursorTo*(buffer: Buffer, x: int, y: int): bool = - result = false +proc cursorTo*(buffer: Buffer, x: int, y: int) = + buffer.redraw = false buffer.cursory = min(max(y, 0), buffer.lastLine()) if buffer.fromy > buffer.cursory: buffer.fromy = max(buffer.cursory, 0) - result = true + buffer.redraw = true 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.currentLineLength()) - #buffer.fromX = min(max(buffer.currentLineLength() - buffer.width + 1, 0), 0) #TODO + buffer.redraw = true + buffer.cursorx = min(max(x, 0), buffer.currentLineWidth()) + #buffer.fromX = min(max(buffer.currentLineWidth() - buffer.width + 1, 0), 0) #TODO -proc cursorDown*(buffer: Buffer): bool = +proc cursorDown*(buffer: Buffer) = 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.currentLineLength() - 1, buffer.xend) + buffer.restoreCursorX() if buffer.cursory >= buffer.lastVisibleLine() and buffer.lastVisibleLine() != buffer.lastLine(): inc buffer.fromy - return true - return false + buffer.redraw = true -proc cursorUp*(buffer: Buffer): bool = +proc cursorUp*(buffer: Buffer) = if buffer.cursory > 0: dec buffer.cursory - if buffer.cursorx > buffer.currentLineLength(): - if buffer.cursorx == 0: - buffer.xend = buffer.cursorx - buffer.cursorx = max(buffer.currentLineLength() - 1, 0) - elif buffer.xend > 0: - buffer.cursorx = min(buffer.currentLineLength() - 1, buffer.xend) + buffer.restoreCursorX() if buffer.cursory < buffer.fromy: dec buffer.fromy - return true - return false + buffer.redraw = true -proc cursorRight*(buffer: Buffer): bool = - if buffer.cursorx < buffer.currentLineLength() - 1: +proc cursorRight*(buffer: Buffer) = + if buffer.cursorx < buffer.currentLineWidth() - 1: inc buffer.cursorx - buffer.xend = 0 - else: buffer.xend = buffer.cursorx - return false - -proc cursorLeft*(buffer: Buffer): bool = - if buffer.cursorx > 0: + if buffer.cursorx - buffer.width >= buffer.fromx: + inc buffer.fromx + buffer.redraw = true + +proc cursorLeft*(buffer: Buffer) = + if buffer.fromx > buffer.cursorx: + buffer.fromx = buffer.cursorx + buffer.redraw = true + elif buffer.cursorx > 0: dec buffer.cursorx - buffer.xend = 0 - return false + if buffer.fromx > buffer.cursorx: + buffer.fromx = buffer.cursorx + buffer.redraw = true + + buffer.xend = buffer.cursorx proc cursorLineBegin*(buffer: Buffer) = buffer.cursorx = 0 buffer.xend = 0 + if buffer.fromx > 0: + buffer.fromx = 0 + buffer.redraw = true proc cursorLineEnd*(buffer: Buffer) = - buffer.cursorx = buffer.currentLineLength() - 1 + buffer.cursorx = buffer.currentLineWidth() - 1 buffer.xend = buffer.cursorx + buffer.fromx = max(buffer.cursorx - buffer.width + 1, 0) + buffer.redraw = buffer.fromx > 0 iterator revnodes*(buffer: Buffer): Node {.inline.} = var i = buffer.nodes.len - 1 @@ -300,207 +252,174 @@ iterator revnodes*(buffer: Buffer): Node {.inline.} = yield buffer.nodes[i] dec i -proc cursorNextWord*(buffer: Buffer): bool = - let llen = buffer.currentLineLength() - 1 - var r: Rune +proc cursorNextWord*(buffer: Buffer) = + let llen = buffer.currentLineWidth() - 1 var x = buffer.cursorx var y = buffer.cursory if llen >= 0: - fastRuneAt(buffer.rawtext[y], x, r, false) - while r != Rune(' '): + while buffer.lines[y][x].rune != Rune(' '): if x >= llen: break inc x - fastRuneAt(buffer.rawtext[y], x, r, false) - while r == Rune(' '): + while buffer.lines[y][x].rune == 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) + buffer.cursorTo(x, y) -proc cursorPrevWord*(buffer: Buffer): bool = - var r: Rune +proc cursorPrevWord*(buffer: Buffer) = var x = buffer.cursorx var y = buffer.cursory - if buffer.currentLineLength() > 0: - fastRuneAt(buffer.rawtext[y], x, r, false) - - while r != Rune(' '): + if buffer.currentLineWidth() > 0: + while buffer.lines[y][x].rune != Rune(' '): if x == 0: break dec x - fastRuneAt(buffer.rawtext[y], x, r, false) - while r == Rune(' '): + while buffer.lines[y][x].rune == Rune(' '): if x == 0: break dec x - fastRuneAt(buffer.rawtext[y], x, r, false) if x == 0: if y > 0: dec y - x = buffer.rawtext[y].runeLen() - 1 - return buffer.cursorTo(x, y) + x = buffer.lines[y].len - 1 + buffer.cursorTo(x, y) -iterator revclickables*(buffer: Buffer): Node {.inline.} = - var i = buffer.clickables.len - 1 - while i >= 0: - yield buffer.clickables[i] - dec i +proc cursorNextLink*(buffer: Buffer) = + #TODO + return -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): - 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): - return buffer.cursorTo(node.x, node.y) - return false +proc cursorPrevLink*(buffer: Buffer) = + #TODO + return -proc cursorFirstLine*(buffer: Buffer): bool = +proc cursorFirstLine*(buffer: Buffer) = if buffer.fromy > 0: buffer.fromy = 0 - result = true + buffer.redraw = true else: - result = false + buffer.redraw = false - buffer.cursorY = 0 - buffer.cursorLineBegin() + buffer.cursory = 0 + buffer.restoreCursorX() -proc cursorLastLine*(buffer: Buffer): bool = +proc cursorLastLine*(buffer: Buffer) = if buffer.fromy < buffer.lastLine() - buffer.height: buffer.fromy = buffer.lastLine() - (buffer.height - 2) - result = true + buffer.redraw = true else: - result = false + buffer.redraw = false buffer.cursory = buffer.lastLine() - buffer.cursorLineBegin() + buffer.restoreCursorX() -proc cursorTop*(buffer: Buffer): bool = - buffer.cursorY = buffer.fromy - return false +proc cursorTop*(buffer: Buffer) = + buffer.cursory = buffer.fromy + buffer.restoreCursorX() -proc cursorMiddle*(buffer: Buffer): bool = - buffer.cursorY = min(buffer.fromy + (buffer.height - 2) div 2, buffer.lastLine()) - return false +proc cursorMiddle*(buffer: Buffer) = + buffer.cursory = min(buffer.fromy + (buffer.height - 2) div 2, buffer.lastLine()) + buffer.restoreCursorX() -proc cursorBottom*(buffer: Buffer): bool = - buffer.cursorY = min(buffer.fromy + buffer.height - 2, buffer.lastLine()) - return false +proc cursorBottom*(buffer: Buffer) = + buffer.cursory = min(buffer.fromy + buffer.height - 2, buffer.lastLine()) + buffer.restoreCursorX() -proc centerLine*(buffer: Buffer): bool = +proc centerLine*(buffer: Buffer) = 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 + buffer.redraw = true -proc halfPageUp*(buffer: Buffer): bool = - buffer.cursory = max(buffer.cursorY - buffer.height div 2 + 1, 0) +proc halfPageUp*(buffer: Buffer) = + 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 + buffer.redraw = true + buffer.restoreCursorX() -proc halfPageDown*(buffer: Buffer): bool = - buffer.cursory = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine()) +proc halfPageDown*(buffer: Buffer) = + 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 + buffer.redraw = true + buffer.restoreCursorX() -proc pageUp*(buffer: Buffer): bool = - buffer.cursorY = max(buffer.cursorY - buffer.height + 1, 1) +proc pageUp*(buffer: Buffer) = + buffer.cursory = max(buffer.cursory - buffer.height + 1, 1) buffer.fromy = max(0, buffer.fromy - buffer.height) - return true + buffer.redraw = true + buffer.restoreCursorX() -proc pageDown*(buffer: Buffer): bool = - buffer.cursorY = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine()) +proc pageDown*(buffer: Buffer) = + 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 + buffer.redraw = true + buffer.restoreCursorX() + +proc pageLeft*(buffer: Buffer) = + buffer.cursorx = max(buffer.cursorx - buffer.width, 0) + buffer.fromx = max(0, buffer.fromx - buffer.width) + buffer.redraw = true + +proc pageRight*(buffer: Buffer) = + buffer.cursorx = min(buffer.fromx, buffer.currentLineWidth()) + buffer.fromx = min(max(buffer.maxScreenWidth() - buffer.width, 0), buffer.fromx + buffer.width) + buffer.redraw = true -proc scrollDown*(buffer: Buffer): bool = +proc scrollDown*(buffer: Buffer) = 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 + if buffer.fromy > buffer.cursory: + buffer.cursorDown() + buffer.redraw = true + else: + buffer.cursorDown() -proc scrollUp*(buffer: Buffer): bool = +proc scrollUp*(buffer: Buffer) = if buffer.fromy > 0: dec buffer.fromy - if buffer.fromy + buffer.height - 1 <= buffer.cursorY: - discard buffer.cursorUp() - return true - discard buffer.cursorUp() - return false - -proc checkLinkSelection*(buffer: Buffer): bool = - if buffer.selectedlink != nil: - if buffer.cursorOnNode(buffer.selectedlink): - return false - else: - let anchor = buffer.selectedlink.ancestor(TAG_A) - buffer.selectedlink.fmttext = buffer.selectedlink.getFmtText() - buffer.selectedlink = nil - buffer.hovertext = "" - var stack: seq[Node] - 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 - let anchor = node.ancestor(TAG_A) - assert(anchor != nil) - buffer.hovertext = HtmlAnchorElement(anchor).href - var stack: seq[Node] - 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 + if buffer.fromy + buffer.height - 1 <= buffer.cursory: + buffer.cursorUp() + buffer.redraw = true + else: + buffer.cursorUp() + +proc scrollRight*(buffer: Buffer) = + if buffer.fromx + buffer.width < buffer.maxScreenWidth(): + inc buffer.fromx + if buffer.fromx >= buffer.cursorx: + buffer.cursorRight() + buffer.redraw = true + +proc scrollLeft*(buffer: Buffer) = + if buffer.fromx > 0: + dec buffer.fromx + if buffer.fromx + buffer.height <= buffer.cursorx: + buffer.cursorLeft() + buffer.redraw = true proc gotoAnchor*(buffer: Buffer): bool = - if buffer.document.location.anchor != "": - let node = buffer.getElementById(buffer.document.location.anchor) + if buffer.location.anchor != "": + let node = buffer.getElementById(buffer.location.anchor) if node != nil: - return buffer.scrollTo(max(node.y - buffer.height div 2, 0)) - return false + buffer.scrollTo(max(node.y - buffer.height div 2, 0)) proc setLocation*(buffer: Buffer, uri: Uri) = - buffer.document.location = uri + buffer.location = uri proc gotoLocation*(buffer: Buffer, uri: Uri) = - buffer.document.location = buffer.document.location.combine(uri) + buffer.location = buffer.location.combine(uri) proc refreshTermAttrs*(buffer: Buffer): bool = let newAttrs = getTermAttributes() @@ -510,3 +429,77 @@ proc refreshTermAttrs*(buffer: Buffer): bool = buffer.height = newAttrs.termHeight return true return false + +proc setText*(buffer: Buffer, x: int, y: int, text: seq[Rune]) = + while buffer.lines.len <= y: + buffer.lines.add(newSeq[BufferCell]()) + + while buffer.lines[y].len < x + text.len: + buffer.lines[y].add(BufferCell()) + + var i = 0 + while i < text.len: + buffer.lines[y][i].rune = text[i] + inc i + +proc setDisplayText(buffer: Buffer, x: int, y: int, text: seq[Rune]) = + let pos = y * buffer.width + x + var i = 0 + var n = 0 + while i < text.len: + if text[i].width() == 0: + buffer.display[pos + i - n].runes.b = text[i] + inc n + else: + buffer.display[pos + i - n].runes.a = text[i] + inc i + +proc reshape*(buffer: Buffer) = + buffer.display = newSeq[DisplayCell](buffer.width * buffer.height) + +proc refreshDisplay*(buffer: Buffer) = + var y = 0 + for line in buffer.lines[buffer.fromy..buffer.lastVisibleLine()]: + var w = 0 + var i = 0 + var n = 0 + while w < buffer.fromx and i < line.len: + w += line[i].rune.width() + inc i + + let dls = y * buffer.width + var j = 0 + while w < buffer.fromx + buffer.width and i < line.len: + w += line[i].rune.width() + if line[i].rune.width() == 0: + buffer.display[dls + j].runes.b = line[i].rune + else: + buffer.display[dls + j].runes.a = line[i].rune + inc i + inc j + + while j < buffer.width: + buffer.display[dls + j].runes.a = Rune(0) + buffer.display[dls + j].runes.b = Rune(0) + inc j + + inc y + +proc renderPlainText*(buffer: Buffer, text: string) = + var i = 0 + var y = 0 + var line = "" + while i < text.len: + if text[i] == '\n': + buffer.setText(0, y, line.toRunes()) + inc y + line = "" + elif text[i] == '\t': + line &= ' '.repeat(8) + else: + line &= text[i] + inc i + if line.len > 0: + buffer.setText(0, y, line.toRunes()) + + buffer.refreshDisplay() diff --git a/src/io/display.nim b/src/io/display.nim index d4c772d6..6f7be014 100644 --- a/src/io/display.nim +++ b/src/io/display.nim @@ -6,7 +6,6 @@ import unicode import ../types/enums -import ../utils/termattrs import ../utils/twtstr import ../html/dom @@ -15,6 +14,7 @@ import ../config import ./buffer import ./twtio +import ./term proc clearStatusMsg*(at: int) = setCursorPos(0, at) @@ -243,7 +243,6 @@ proc renderHtml*(buffer: Buffer) = var state = newRenderState() while stack.len > 0: let currElem = stack.pop() - buffer.addNode(currElem) buffer.renderNode(currElem, state) var i = currElem.childNodes.len - 1 while i >= 0: @@ -267,20 +266,21 @@ proc statusMsgForBuffer(buffer: Buffer) = statusMsg(msg.maxString(buffer.width), buffer.height) proc cursorBufferPos(buffer: Buffer) = - var x = buffer.cursorx - var y = buffer.cursory - 1 - buffer.fromY - termGoto(x, y + 1) + var x = max(buffer.cursorx - buffer.fromx, 0) + var y = buffer.cursory - buffer.fromy + termGoto(x, y) proc displayBuffer(buffer: Buffer) = eraseScreen() termGoto(0, 0) - print(buffer.visibleText().ansiReset()) + print(buffer.generateFullOutput().ansiReset()) proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = var s = "" var feedNext = false while true: + buffer.redraw = false stdout.showCursor() buffer.cursorBufferPos() if not feedNext: @@ -296,46 +296,38 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = case action of ACTION_QUIT: eraseScreen() + setCursorPos(0, 0) return false - of ACTION_CURSOR_LEFT: redraw = buffer.cursorLeft() - of ACTION_CURSOR_DOWN: redraw = buffer.cursorDown() - of ACTION_CURSOR_UP: redraw = buffer.cursorUp() - of ACTION_CURSOR_RIGHT: redraw = buffer.cursorRight() + of ACTION_CURSOR_LEFT: buffer.cursorLeft() + of ACTION_CURSOR_DOWN: buffer.cursorDown() + of ACTION_CURSOR_UP: buffer.cursorUp() + of ACTION_CURSOR_RIGHT: buffer.cursorRight() of ACTION_CURSOR_LINEBEGIN: buffer.cursorLineBegin() of ACTION_CURSOR_LINEEND: buffer.cursorLineEnd() - of ACTION_CURSOR_NEXT_WORD: redraw = buffer.cursorNextWord() - of ACTION_CURSOR_PREV_WORD: redraw = buffer.cursorPrevWord() - of ACTION_CURSOR_NEXT_LINK: redraw = buffer.cursorNextLink() - of ACTION_CURSOR_PREV_LINK: redraw = buffer.cursorPrevLink() - of ACTION_PAGE_DOWN: redraw = buffer.pageDown() - of ACTION_PAGE_UP: redraw = buffer.pageUp() - of ACTION_HALF_PAGE_DOWN: redraw = buffer.halfPageDown() - of ACTION_HALF_PAGE_UP: redraw = buffer.halfPageUp() - of ACTION_CURSOR_FIRST_LINE: redraw = buffer.cursorFirstLine() - of ACTION_CURSOR_LAST_LINE: redraw = buffer.cursorLastLine() - of ACTION_CURSOR_TOP: redraw = buffer.cursorTop() - of ACTION_CURSOR_MIDDLE: redraw = buffer.cursorMiddle() - of ACTION_CURSOR_BOTTOM: redraw = buffer.cursorBottom() - of ACTION_CENTER_LINE: redraw = buffer.centerLine() - of ACTION_SCROLL_DOWN: redraw = buffer.scrollDown() - of ACTION_SCROLL_UP: redraw = buffer.scrollUp() + of ACTION_CURSOR_NEXT_WORD: buffer.cursorNextWord() + of ACTION_CURSOR_PREV_WORD: buffer.cursorPrevWord() + of ACTION_CURSOR_NEXT_LINK: buffer.cursorNextLink() + of ACTION_CURSOR_PREV_LINK: buffer.cursorPrevLink() + of ACTION_PAGE_DOWN: buffer.pageDown() + of ACTION_PAGE_UP: buffer.pageUp() + of ACTION_PAGE_RIGHT: buffer.pageRight() + of ACTION_PAGE_LEFT: buffer.pageLeft() + of ACTION_HALF_PAGE_DOWN: buffer.halfPageDown() + of ACTION_HALF_PAGE_UP: buffer.halfPageUp() + of ACTION_CURSOR_FIRST_LINE: buffer.cursorFirstLine() + of ACTION_CURSOR_LAST_LINE: buffer.cursorLastLine() + of ACTION_CURSOR_TOP: buffer.cursorTop() + of ACTION_CURSOR_MIDDLE: buffer.cursorMiddle() + of ACTION_CURSOR_BOTTOM: buffer.cursorBottom() + of ACTION_CENTER_LINE: buffer.centerLine() + of ACTION_SCROLL_DOWN: buffer.scrollDown() + of ACTION_SCROLL_UP: buffer.scrollUp() + of ACTION_SCROLL_LEFT: buffer.scrollLeft() + of ACTION_SCROLL_RIGHT: buffer.scrollRight() of ACTION_CLICK: - let selectedElem = buffer.findSelectedElement() - if selectedElem.isSome: - case selectedElem.get().tagType - of TAG_INPUT: - clearStatusMsg(buffer.height) - let status = readLine("TEXT: ", HtmlInputElement(selectedElem.get()).value, buffer.width) - if status: - reshape = true - redraw = true - else: discard - if selectedElem.get().getStyle().islink: - let anchor = HtmlAnchorElement(buffer.selectedlink.ancestor(TAG_A)).href - buffer.gotoLocation(parseUri(anchor)) - return true + discard of ACTION_CHANGE_LOCATION: - var url = $buffer.document.location + var url = $buffer.location clearStatusMsg(buffer.height) let status = readLine("URL: ", url, buffer.width) @@ -343,7 +335,7 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = buffer.setLocation(parseUri(url)) return true of ACTION_LINE_INFO: - statusMsg("line " & $buffer.cursory & "/" & $buffer.lastLine() & " col " & $(buffer.cursorx + 1) & "/" & $buffer.currentLineLength(), buffer.width) + statusMsg("line " & $buffer.cursory & "/" & $buffer.lastLine() & " col " & $(buffer.cursorx + 1) & "/" & $buffer.currentLineWidth(), buffer.width) nostatus = true of ACTION_FEED_NEXT: feedNext = true @@ -355,40 +347,17 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = else: discard stdout.hideCursor() - let prevlink = buffer.selectedlink - let sel = buffer.checkLinkSelection() - if sel: - buffer.clearText() - buffer.drawHtml() - termGoto(0, buffer.selectedlink.y - buffer.fromy) - stdout.eraseLine() - for i in buffer.selectedlink.y..buffer.selectedlink.ey: - if i < buffer.fromy + buffer.height - 1: - let line = buffer.fmttext[i] - print(line) - print('\n') - print("".ansiReset()) - - if prevlink != nil: - buffer.clearText() - buffer.drawHtml() - termGoto(0, prevlink.y - buffer.fromy) - for i in prevlink.y..prevlink.ey: - if i < buffer.fromy + buffer.height - 1: - let line = buffer.fmttext[i] - stdout.eraseLine() - print(line) - print('\n') - print("".ansiReset()) - if buffer.refreshTermAttrs(): redraw = true reshape = true + if buffer.redraw: + redraw = true + if reshape: - buffer.clearText() - buffer.drawHtml() + buffer.reshape() if redraw: + buffer.refreshDisplay() buffer.displayBuffer() if not nostatus: diff --git a/src/utils/termattrs.nim b/src/io/term.nim index d49800ae..5103c153 100644 --- a/src/utils/termattrs.nim +++ b/src/io/term.nim @@ -9,3 +9,6 @@ proc getTermAttributes*(): TermAttributes = let attrs = TermAttributes(termWidth: terminalWidth(), termHeight: terminalHeight()) return attrs + +proc termGoto*(x: int, y: int) = + setCursorPos(stdout, x, y) diff --git a/src/io/twtio.nim b/src/io/twtio.nim index 4e2789b0..8a8bef1c 100644 --- a/src/io/twtio.nim +++ b/src/io/twtio.nim @@ -1,4 +1,4 @@ -import terminal +import std/terminal import tables import unicode import strutils @@ -10,6 +10,8 @@ import ../utils/radixtree import ../config +import ./terminal + template print*(s: varargs[string, `$`]) = for x in s: stdout.write(x) @@ -22,19 +24,6 @@ template printesc*(s: string) = else: stdout.write($r) -template eprint*(s: varargs[string, `$`]) = {.cast(noSideEffect).}: - var a = false - for x in s: - if not a: - a = true - else: - stderr.write(' ') - stderr.write(x) - stderr.write('\n') - -proc termGoto*(x: int, y: int) = - setCursorPos(stdout, x, y) - proc getNormalAction*(s: string): TwtAction = if normalActionRemap.hasKey(s): return normalActionRemap[s] diff --git a/src/main.nim b/src/main.nim index 7912a431..78d24e8d 100644 --- a/src/main.nim +++ b/src/main.nim @@ -3,14 +3,14 @@ import uri import os import streams -import utils/termattrs +import utils/eprint import html/dom import html/htmlparser import io/display -import io/twtio import io/buffer +import io/term import config @@ -43,19 +43,21 @@ proc main*() = eprint "Invalid parameters. Usage:\ntwt <url>" quit(1) if not readConfig("res/config"): - eprint "Failed to read keymap, fallback to default" + #eprint "Failed to read keymap, fallback to default" + discard let attrs = getTermAttributes() let buffer = newBuffer(attrs) let uri = parseUri(paramStr(1)) buffers.add(buffer) - buffer.document = parseHtml(getPageUri(uri)) - buffer.document.applyDefaultStylesheet() - buffer.setLocation(uri) - buffer.renderHtml() + buffer.renderPlainText(getPageUri(uri).readAll()) + #buffer.document = parseHtml(getPageUri(uri)) + #buffer.setLocation(uri) + #buffer.document.applyDefaultStylesheet() + #buffer.renderHtml() var lastUri = uri while displayPage(attrs, buffer): statusMsg("Loading...", buffer.height) - var newUri = buffer.document.location + var newUri = buffer.location lastUri.anchor = "" newUri.anchor = "" if $lastUri != $newUri: @@ -63,7 +65,7 @@ proc main*() = if uri.scheme == "" and uri.path == "" and uri.anchor != "": discard else: - buffer.document = parseHtml(getPageUri(buffer.document.location)) + buffer.document = parseHtml(getPageUri(buffer.location)) buffer.renderHtml() lastUri = newUri main() diff --git a/src/utils/eprint.nim b/src/utils/eprint.nim new file mode 100644 index 00000000..23248363 --- /dev/null +++ b/src/utils/eprint.nim @@ -0,0 +1,11 @@ +{.used.} + +template eprint*(s: varargs[string, `$`]) = {.cast(noSideEffect).}: + var a = false + for x in s: + if not a: + a = true + else: + stderr.write(' ') + stderr.write(x) + stderr.write('\n') diff --git a/src/utils/radixtree.nim b/src/utils/radixtree.nim index dd0a6a1d..b361d367 100644 --- a/src/utils/radixtree.nim +++ b/src/utils/radixtree.nim @@ -1,6 +1,5 @@ # Radix tree implementation. It isn't that much faster than a hash table, -# however it *is* faster. Use StaticRadixTree for saving trees in the -# executable and RadixNode otherwise (which needs less bounds checking). +# however it *is* faster. import json import tables diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim index 730f8b32..22207e2c 100644 --- a/src/utils/twtstr.nim +++ b/src/utils/twtstr.nim @@ -211,8 +211,7 @@ iterator split*(s: seq[Rune], sep: Rune): seq[Rune] = # # - The null character (U+0000) has a column width of 0. # -# - Other C0/C1 control characters and DEL will lead to a return value of 2 -# (changed from 0 b/c we normally display control chars like ^H - TODO?). +# - Other C0/C1 control characters and DEL will lead to a return value of 0 # # - Non-spacing and enclosing combining characters (general category code Mn # or Me in the Unicode database) have a column width of 0. @@ -305,7 +304,7 @@ func is_dwidth(r: Rune): bool = func makewidthtable(): array[0..0x10FFFF, byte] = for r in low(char)..high(char): if r.isControlChar(): - result[int(r)] = 2 + result[int(r)] = 2 #TODO this should be 0 else: result[int(r)] = 1 @@ -545,6 +544,3 @@ proc fullwidth*(s: seq[Rune]): seq[Rune] = proc fullwidth*(s: string): string = return $fullwidth(s.toRunes()) - -echo (halfwidth("とうギょう")) -echo "東京" |