diff options
author | bptato <nincsnevem662@gmail.com> | 2022-11-15 23:42:20 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2022-11-19 14:32:54 +0100 |
commit | a6bbcd0dd3f77b0e98527c1fa9e510a40acd954e (patch) | |
tree | ca55cc9079afbe788a61986e42d1a8d9d0bc7c2f /src/io/buffer.nim | |
parent | e75f62b34f7c7f3127bcde0c4a12cbb785342dd9 (diff) | |
download | chawan-a6bbcd0dd3f77b0e98527c1fa9e510a40acd954e.tar.gz |
Rewrite buffer/pager for multi-processing
Diffstat (limited to 'src/io/buffer.nim')
-rw-r--r-- | src/io/buffer.nim | 1285 |
1 files changed, 0 insertions, 1285 deletions
diff --git a/src/io/buffer.nim b/src/io/buffer.nim deleted file mode 100644 index f4cce8c4..00000000 --- a/src/io/buffer.nim +++ /dev/null @@ -1,1285 +0,0 @@ -import algorithm -import options -import os -import streams -import tables -import terminal -import unicode - -import css/cascade -import css/cssparser -import css/mediaquery -import css/sheet -import css/stylednode -import config/config -import html/dom -import html/tags -import html/htmlparser -import io/cell -import io/lineedit -import io/loader -import io/request -import io/term -import js/regex -import layout/box -import render/renderdocument -import render/rendertext -import types/color -import types/url -import utils/twtstr - -type - CursorPosition* = object - cursorx*: int - cursory*: int - xend*: int - fromx*: int - fromy*: int - - BufferMatch* = object - success*: bool - x*: int - y*: int - str*: string - - Buffer* = ref object - contenttype*: string - title*: string - lines*: FlexibleGrid - display*: FixedGrid - prevdisplay*: FixedGrid - statusmsg*: FixedGrid - hovertext*: string - width*: int - height*: int - cpos*: CursorPosition - attrs*: TermAttributes - document*: Document - viewport*: Viewport - prevstyled*: StyledNode - redraw*: bool - reshape*: bool - nostatus*: bool - location*: Url - ispipe*: bool - istream*: Stream - streamclosed*: bool - source*: string - prevnode*: StyledNode - userstyle*: CSSStylesheet - loader*: FileLoader - marks*: seq[Mark] - config*: Config - tty: File - -proc newBuffer*(config: Config, tty: File): Buffer = - new(result) - result.attrs = getTermAttributes(stdout) - result.width = result.attrs.width - result.height = result.attrs.height - 1 - result.config = config - result.loader = newFileLoader() - - result.display = newFixedGrid(result.width, result.height) - result.prevdisplay = newFixedGrid(result.width, result.height) - result.statusmsg = newFixedGrid(result.width) - -func cursorx*(buffer: Buffer): int {.inline.} = buffer.cpos.cursorx -func cursory*(buffer: Buffer): int {.inline.} = buffer.cpos.cursory -func fromx*(buffer: Buffer): int {.inline.} = buffer.cpos.fromx -func fromy*(buffer: Buffer): int {.inline.} = buffer.cpos.fromy -func xend*(buffer: Buffer): int {.inline.} = buffer.cpos.xend - -func generateFullOutput*(buffer: Buffer): string = - var x = 0 - var w = 0 - var format = newFormat() - result &= HVP(1, 1) - - for cell in buffer.display: - if x >= buffer.width: - result &= EL() - result &= "\r\n" - x = 0 - w = 0 - - result &= format.processFormat(cell.format) - result &= cell.str - - w += cell.width() - inc x - - result &= EL() - result &= "\r\n" - -# generate a sequence of instructions to replace the previous frame with the -# current one. ideally should be used when small changes are made (e.g. hover -# changes underlining) -func generateSwapOutput(buffer: Buffer): string = - var format = newFormat() - let curr = buffer.display - let prev = buffer.prevdisplay - var i = 0 - var x = 0 - var y = 0 - var line = "" - var lr = false - while i < curr.len: - if x >= buffer.width: - if lr: - result &= HVP(y + 1, 1) - result &= EL() - result &= line - lr = false - x = 0 - inc y - line = "" - lr = lr or (curr[i] != prev[i]) - line &= format.processFormat(curr[i].format) - line &= curr[i].str - inc i - inc x - if lr: - result &= HVP(y + 1, 1) - result &= EL() - result &= line - lr = false - - #TODO maybe fix this - #var x = 0 - #var y = 0 - #var cx = -1 - #var cy = -1 - #var i = 0 - #var text = "" - #while i < max: - # if x >= buffer.width: - # x = 0 - # inc y - - # if curr[i] != prev[i]: - # let currwidth = curr[i].runes.width() - # let prevwidth = prev[i].runes.width() - # if (curr[i].runes.len > 0 or currwidth < prevwidth) and (x != cx or y != cy): - # if text.len > 0: - # result &= text - # text = "" - # result &= HVP(y + 1, x + 1) - # cx = x - # cy = y - - # text &= format.processFormat(curr[i].format) - - # text &= $curr[i].runes - # if currwidth < prevwidth: - # var j = 0 - # while j < prevwidth - currwidth: - # text &= ' ' - # inc j - # if text.len > 0: - # inc cx - - # inc x - # inc i - #if text.len > 0: - # result &= $text - -func generateStatusMessage*(buffer: Buffer): string = - var format = newFormat() - var w = 0 - for cell in buffer.statusmsg: - result &= format.processFormat(cell.format) - result &= cell.str - w += cell.width() - if w < buffer.width: - result &= EL() - -func numLines(buffer: Buffer): int = buffer.lines.len - -func lastVisibleLine(buffer: Buffer): int = min(buffer.fromy + buffer.height, buffer.numLines) - -func currentLineWidth(buffer: Buffer): int = - return buffer.lines[buffer.cursory].width() - -func maxfromy(buffer: Buffer): int = max(buffer.numLines - buffer.height, 0) - -func maxfromx(buffer: Buffer): int = max(buffer.currentLineWidth() - buffer.width, 0) - -func acursorx(buffer: Buffer): int = - return max(0, buffer.cursorx - buffer.fromx) - -func acursory(buffer: Buffer): int = - return buffer.cursory - buffer.fromy - -func cellOrigin(buffer: Buffer, x, y: int): int = - let row = y * buffer.width - var ox = x - while ox > 0 and buffer.display[row + ox].str.len == 0: - dec ox - return ox - -func currentCellOrigin(buffer: Buffer): int = - return buffer.cellOrigin(buffer.acursorx, buffer.acursory) - -func currentDisplayCell(buffer: Buffer): FixedCell = - let row = (buffer.cursory - buffer.fromy) * buffer.width - return buffer.display[row + buffer.currentCellOrigin()] - -func getLink(node: StyledNode): HTMLAnchorElement = - if node == nil: - return nil - if node.t == STYLED_ELEMENT and node.node != nil and Element(node.node).tagType == TAG_A: - return HTMLAnchorElement(node.node) - if node.node != nil: - return HTMLAnchorElement(node.node.findAncestor({TAG_A})) - #TODO ::before links? - -const ClickableElements = { - TAG_A, TAG_INPUT, TAG_OPTION -} - -func getClickable(styledNode: StyledNode): Element = - if styledNode == nil or styledNode.node == nil: - return nil - if styledNode.t == STYLED_ELEMENT: - let element = Element(styledNode.node) - if element.tagType in ClickableElements: - return element - styledNode.node.findAncestor(ClickableElements) - -func getCursorClickable(buffer: Buffer): Element = - return buffer.currentDisplayCell().node.getClickable() - -func currentLine(buffer: Buffer): string = - return buffer.lines[buffer.cursory].str - -func cursorBytes(buffer: Buffer, y: int, cc = buffer.fromx + buffer.cursorx): int = - assert y < buffer.lines.len - let line = buffer.lines[y].str - var w = 0 - var i = 0 - while i < line.len and w < cc: - var r: Rune - fastRuneAt(line, i, r) - w += r.width() - return i - -func currentCursorBytes(buffer: Buffer, cc = buffer.fromx + buffer.cursorx): int = - return buffer.cursorBytes(buffer.cursory, cc) - -func currentWidth(buffer: Buffer): int = - let line = buffer.currentLine - if line.len == 0: return 0 - var w = 0 - var i = 0 - let cc = buffer.fromx + buffer.cursorx - var r: Rune - fastRuneAt(line, i, r) - while i < line.len and w < cc: - fastRuneAt(line, i, r) - w += r.width() - return r.width() - -func prevWidth(buffer: Buffer): int = - let line = buffer.currentLine - if line.len == 0: return 0 - var w = 0 - var i = 0 - let cc = buffer.fromx + buffer.cursorx - var pr: Rune - var r: Rune - fastRuneAt(line, i, r) - while i < line.len and w < cc: - pr = r - fastRuneAt(line, i, r) - w += r.width() - return pr.width() - -func maxScreenWidth(buffer: Buffer): int = - for line in buffer.lines[buffer.fromy..buffer.lastVisibleLine - 1]: - result = max(line.width(), result) - -func atPercentOf(buffer: Buffer): int = - if buffer.lines.len == 0: return 100 - return (100 * (buffer.cursory + 1)) div buffer.numLines - -func hasAnchor*(buffer: Buffer, anchor: string): bool = - return buffer.document.getElementById(anchor) != nil - -func getTitle(buffer: Buffer): string = - if buffer.document != nil: - result = buffer.document.title - if result != "": return result - if buffer.ispipe: - return "*pipe*" - return buffer.location.serialize(excludepassword = true) - -proc clearDisplay(buffer: Buffer) = - buffer.prevdisplay = buffer.display - buffer.display = newFixedGrid(buffer.width, buffer.height) - -proc refreshDisplay(buffer: Buffer) = - var r: Rune - var y = 0 # y position on screen - buffer.clearDisplay() - - for by in buffer.fromy..buffer.lastVisibleLine - 1: - let line = buffer.lines[by] # by: y position in lines - var w = 0 # width of the row so far - var i = 0 # byte in line.str - - # Skip cells till buffer.fromx. - while w < buffer.fromx and i < line.str.len: - fastRuneAt(line.str, i, r) - w += r.width() - - let dls = y * buffer.width # starting position of row in display - - # Fill in the gap in case we skipped more cells than fromx mandates (i.e. - # we encountered a double-width character.) - var k = 0 - if w > buffer.fromx: - while k < w - buffer.fromx: - buffer.display[dls + k].str &= ' ' - inc k - - var cf = line.findFormat(w) - var nf = line.findNextFormat(w) - - let startw = w # save this for later - - # Now fill in the visible part of the row. - while i < line.str.len: - let pw = w - fastRuneAt(line.str, i, r) - w += r.width() - if w > buffer.fromx + buffer.width: - break # die on exceeding the width limit - if nf.pos != -1 and nf.pos <= pw: - cf = nf - nf = line.findNextFormat(pw) - buffer.display[dls + k].str &= r - if cf.pos != -1: - buffer.display[dls + k].format = cf.format - buffer.display[dls + k].node = cf.node - let tk = k + r.width() - while k < tk and k < buffer.width - 1: - inc k - - # Then, for each cell that has a mark, override its formatting with that - # specified by the mark. - var l = 0 - while l < buffer.marks.len and buffer.marks[l].y < by: - inc l # linear search to find the first applicable mark - let aw = buffer.width - (startw - buffer.fromx) # actual width - while l < buffer.marks.len and buffer.marks[l].y == by: - let mark = buffer.marks[l] - inc l - if mark.x >= startw + aw or mark.x + mark.width < startw: continue - for i in max(mark.x, startw)..<min(mark.x + mark.width, startw + aw): - buffer.display[dls + i - startw].format = mark.format - - inc y - -proc setCursorX(buffer: Buffer, x: int, refresh = true, save = true) = - if (not refresh) or (buffer.fromx <= x and x < buffer.fromx + buffer.width): - buffer.cpos.cursorx = x - else: - if refresh and buffer.fromx > buffer.cursorx: - buffer.cpos.fromx = max(buffer.currentLineWidth() - 1, 0) - buffer.cpos.cursorx = buffer.fromx - elif x > buffer.cursorx: - buffer.cpos.fromx = max(x - buffer.width + 1, 0) - buffer.cpos.cursorx = x - elif x < buffer.cursorx: - buffer.cpos.fromx = x - buffer.cpos.cursorx = x - buffer.redraw = true - if save: - buffer.cpos.xend = buffer.cursorx - -proc restoreCursorX(buffer: Buffer) = - buffer.setCursorX(max(min(buffer.currentLineWidth() - 1, buffer.xend), 0), false, false) - -proc setCursorY(buffer: Buffer, y: int) = - if buffer.cursory == y: - return - if y - buffer.fromy >= 0 and y - buffer.height < buffer.fromy: - buffer.cpos.cursory = y - else: - if y > buffer.cursory: - buffer.cpos.fromy = max(y - buffer.height + 1, 0) - else: - buffer.cpos.fromy = min(y, buffer.maxfromy) - buffer.cpos.cursory = y - buffer.redraw = true - buffer.restoreCursorX() - -proc centerLine*(buffer: Buffer) = - let ny = max(min(buffer.cursory - buffer.height div 2, buffer.numLines - buffer.height), 0) - if ny != buffer.fromy: - buffer.cpos.fromy = ny - buffer.redraw = true - -proc setCursorXY*(buffer: Buffer, x, y: int) = - let fy = buffer.fromy - buffer.setCursorY(max(min(y, buffer.numLines - 1), 0)) - buffer.setCursorX(max(min(buffer.currentLineWidth(), x), 0)) - if fy != buffer.fromy: - buffer.centerLine() - -proc setFromXY*(buffer: Buffer, x, y: int) = - buffer.cpos.fromy = max(min(y, buffer.maxfromy), 0) - buffer.cpos.fromx = max(min(x, buffer.maxfromx), 0) - -proc cursorDown*(buffer: Buffer) = - if buffer.cursory < buffer.numLines - 1: - buffer.setCursorY(buffer.cursory + 1) - -proc cursorUp*(buffer: Buffer) = - if buffer.cursory > 0: - buffer.setCursorY(buffer.cursory - 1) - -proc cursorRight*(buffer: Buffer) = - let cellwidth = buffer.currentWidth() - if buffer.cursorx + cellwidth < buffer.currentLineWidth(): - buffer.setCursorX(buffer.cursorx + cellwidth) - -proc cursorLeft*(buffer: Buffer) = - buffer.setCursorX(max(buffer.cursorx - buffer.prevWidth(), 0)) - -proc cursorLineBegin*(buffer: Buffer) = - buffer.setCursorX(0) - -proc cursorLineEnd*(buffer: Buffer) = - buffer.setCursorX(max(buffer.currentLineWidth() - 1, 0)) - -proc cursorNextWord*(buffer: Buffer) = - var r: Rune - var b = buffer.currentCursorBytes() - var x = buffer.cursorx - while b < buffer.currentLine.len: - let pb = b - fastRuneAt(buffer.currentLine, b, r) - if r.breaksWord(): - b = pb - break - x += r.width() - - while b < buffer.currentLine.len: - let pb = b - fastRuneAt(buffer.currentLine, b, r) - if not r.breaksWord(): - b = pb - break - x += r.width() - - if b < buffer.currentLine.len: - buffer.setCursorX(x) - else: - if buffer.cursory < buffer.numLines - 1: - buffer.cursorDown() - buffer.cursorLineBegin() - else: - buffer.cursorLineEnd() - -proc cursorPrevWord*(buffer: Buffer) = - var b = buffer.currentCursorBytes() - var x = buffer.cursorx - if buffer.currentLine.len > 0: - b = min(b, buffer.currentLine.len - 1) - while b >= 0: - let (r, o) = lastRune(buffer.currentLine, b) - if r.breaksWord(): - break - b -= o - x -= r.width() - - while b >= 0: - let (r, o) = lastRune(buffer.currentLine, b) - if not r.breaksWord(): - break - b -= o - x -= r.width() - else: - b = -1 - - if b >= 0: - buffer.setCursorX(x) - else: - if buffer.cursory > 0: - buffer.cursorUp() - buffer.cursorLineEnd() - else: - buffer.cursorLineBegin() - -proc cursorNextLink*(buffer: Buffer) = - let line = buffer.lines[buffer.cursory] - var i = line.findFormatN(buffer.cursorx) - 1 - var link: Element = nil - if i >= 0: - link = line.formats[i].node.getClickable() - inc i - - while i < line.formats.len: - let format = line.formats[i] - let fl = format.node.getClickable() - if fl != nil and fl != link: - buffer.setCursorX(format.pos) - return - inc i - - for y in (buffer.cursory + 1)..(buffer.numLines - 1): - let line = buffer.lines[y] - i = 0 - while i < line.formats.len: - let format = line.formats[i] - let fl = format.node.getClickable() - if fl != nil and fl != link: - buffer.setCursorXY(format.pos, y) - return - inc i - -proc cursorPrevLink*(buffer: Buffer) = - let line = buffer.lines[buffer.cursory] - var i = line.findFormatN(buffer.cursorx) - 1 - var link: Element = nil - if i >= 0: - link = line.formats[i].node.getClickable() - dec i - - var ly = 0 #last y - var lx = 0 #last x - template link_beginning() = - #go to beginning of link - ly = y #last y - lx = format.pos #last x - - #on the current line - let line = buffer.lines[y] - while i >= 0: - let format = line.formats[i] - let nl = format.node.getClickable() - if nl == fl: - lx = format.pos - dec i - - #on previous lines - for iy in countdown(ly - 1, 0): - let line = buffer.lines[iy] - i = line.formats.len - 1 - while i >= 0: - let format = line.formats[i] - let nl = format.node.getClickable() - if nl == fl: - ly = iy - lx = format.pos - dec i - - while i >= 0: - let format = line.formats[i] - let fl = format.node.getClickable() - if fl != nil and fl != link: - let y = buffer.cursory - link_beginning - buffer.setCursorXY(lx, ly) - return - dec i - - for y in countdown(buffer.cursory - 1, 0): - let line = buffer.lines[y] - i = line.formats.len - 1 - while i >= 0: - let format = line.formats[i] - let fl = format.node.getClickable() - if fl != nil and fl != link: - link_beginning - buffer.setCursorXY(lx, ly) - return - dec i - -proc cursorFirstLine*(buffer: Buffer) = - buffer.setCursorY(0) - -proc cursorLastLine*(buffer: Buffer) = - buffer.setCursorY(buffer.numLines - 1) - -proc cursorTop*(buffer: Buffer) = - buffer.setCursorY(buffer.fromy) - -proc cursorMiddle*(buffer: Buffer) = - buffer.setCursorY(min(buffer.fromy + (buffer.height - 2) div 2, buffer.numLines - 1)) - -proc cursorBottom*(buffer: Buffer) = - buffer.setCursorY(min(buffer.fromy + buffer.height - 1, buffer.numLines - 1)) - -proc cursorLeftEdge*(buffer: Buffer) = - buffer.setCursorX(buffer.fromx) - -proc cursorVertMiddle*(buffer: Buffer) = - buffer.setCursorX(min(buffer.fromx + (buffer.width - 2) div 2, buffer.currentLineWidth)) - -proc cursorRightEdge*(buffer: Buffer) = - buffer.setCursorX(min(buffer.fromx + buffer.width - 1, buffer.currentLineWidth)) - -proc halfPageUp*(buffer: Buffer) = - buffer.cpos.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.cpos.fromy = nfy - buffer.redraw = true - buffer.restoreCursorX() - -proc halfPageDown*(buffer: Buffer) = - buffer.cpos.cursory = min(buffer.cursory + buffer.height div 2 - 1, buffer.numLines - 1) - let nfy = min(max(buffer.numLines - buffer.height, 0), buffer.fromy + buffer.height div 2 - 1) - if nfy != buffer.fromy: - buffer.cpos.fromy = nfy - buffer.redraw = true - buffer.restoreCursorX() - -proc pageUp*(buffer: Buffer) = - buffer.cpos.cursory = max(buffer.cursory - buffer.height, 0) - let nfy = max(0, buffer.fromy - buffer.height) - if nfy != buffer.fromy: - buffer.cpos.fromy = nfy - buffer.redraw = true - buffer.restoreCursorX() - -proc pageDown*(buffer: Buffer) = - buffer.cpos.cursory = min(buffer.cursory + buffer.height, buffer.numLines - 1) - let nfy = min(buffer.fromy + buffer.height, max(buffer.numLines - buffer.height, 0)) - if nfy != buffer.fromy: - buffer.cpos.fromy = nfy - buffer.redraw = true - buffer.restoreCursorX() - -proc pageLeft*(buffer: Buffer) = - buffer.cpos.cursorx = max(buffer.cursorx - buffer.width, 0) - let nfx = max(0, buffer.fromx - buffer.width) - if nfx != buffer.fromx: - buffer.cpos.fromx = nfx - buffer.redraw = true - -proc pageRight*(buffer: Buffer) = - buffer.cpos.cursorx = min(buffer.fromx, buffer.currentLineWidth()) - let nfx = min(max(buffer.maxScreenWidth() - buffer.width, 0), buffer.fromx + buffer.width) - if nfx != buffer.fromx: - buffer.cpos.fromx = nfx - buffer.redraw = true - -proc scrollDown*(buffer: Buffer) = - if buffer.fromy + buffer.height < buffer.numLines: - inc buffer.cpos.fromy - if buffer.fromy > buffer.cursory: - buffer.cursorDown() - buffer.redraw = true - else: - buffer.cursorDown() - -proc scrollUp*(buffer: Buffer) = - if buffer.fromy > 0: - dec buffer.cpos.fromy - if buffer.fromy + buffer.height <= buffer.cursory: - buffer.cursorUp() - buffer.redraw = true - else: - buffer.cursorUp() - -proc scrollRight*(buffer: Buffer) = - if buffer.fromx + buffer.width < buffer.maxScreenWidth(): - inc buffer.cpos.fromx - buffer.redraw = true - -proc scrollLeft*(buffer: Buffer) = - if buffer.fromx > 0: - dec buffer.cpos.fromx - if buffer.cursorx < buffer.fromx: - buffer.setCursorX(max(buffer.currentLineWidth() - 1, 0)) - buffer.redraw = true - -proc gotoAnchor*(buffer: Buffer) = - if buffer.document == nil: return - let anchor = buffer.document.getElementById(buffer.location.anchor) - if anchor == nil: return - for y in 0..<buffer.numLines: - let line = buffer.lines[y] - var i = 0 - while i < line.formats.len: - let format = line.formats[i] - if format.node != nil and anchor in format.node.node: - buffer.setCursorY(y) - buffer.centerLine() - buffer.setCursorX(format.pos) - return - inc i - -proc addMark*(buffer: Buffer, x, y, width: int): Mark = - assert y < buffer.lines.len - var format = newFormat() - format.bgcolor = buffer.config.markcolor - result = Mark(x: x, y: y, width: width, format: format) - let previ = upperBound(buffer.marks, y, (proc(a: Mark, b: int): int = cmp(a.y, b))) - buffer.marks.insert(result, previ) - -proc removeMark*(buffer: Buffer, mark: Mark) = - let i = buffer.marks.find(mark) - if i != -1: - buffer.marks.delete(i) - -proc cursorNextMatch(buffer: Buffer, regex: Regex, sy, ey: int, wrap = false): BufferMatch = - for y in sy..ey: - let s = if y == buffer.cursory and not wrap: - buffer.currentCursorBytes(buffer.fromx + buffer.cursorx + 1) - else: - 0 - let res = regex.exec(buffer.lines[y].str, s) - if res.success and res.captures.len > 0: - let cap = res.captures[0] - let x = buffer.lines[y].str.width(cap.s) - buffer.setCursorXY(x, y) - result.success = true - result.y = y - result.x = x - result.str = buffer.lines[y].str.substr(cap.s, cap.e - 1) - return - -proc cursorNextMatch*(buffer: Buffer, regex: Regex, wrap = true): BufferMatch = - let s = buffer.currentCursorBytes(buffer.fromx + buffer.cursorx + 1) - var low = buffer.cursory - if s == buffer.lines.len: - low += 1 - if low > buffer.lines.high: - low = 0 - let ret = buffer.cursorNextMatch(regex, low, buffer.lines.high) - if ret.success: - return ret - if wrap: - return buffer.cursorNextMatch(regex, 0, low, true) - -proc cursorPrevMatch*(buffer: Buffer, regex: Regex, sy, ey: int, wrap = false): BufferMatch = - for y in countdown(sy, ey): - let e = if y == buffer.cursory and not wrap: - buffer.currentCursorBytes() - else: - buffer.lines[y].str.len + 1 - let res = regex.exec(buffer.lines[y].str) - if res.success: - for i in countdown(res.captures.high, 0): - let cap = res.captures[i] - if cap.s < e: - let x = buffer.lines[y].str.width(cap.s) - buffer.setCursorXY(x, y) - result.success = true - result.y = y - result.x = x - result.str = buffer.lines[y].str.substr(cap.s, cap.e - 1) - return - -proc cursorPrevMatch*(buffer: Buffer, regex: Regex, wrap = true): BufferMatch = - var high = buffer.cursory - if buffer.fromx + buffer.cursorx - 1 < 0: - high -= 1 - if high < 0: - high = buffer.lines.high - let ret = buffer.cursorPrevMatch(regex, high, 0) - if ret.success: - return ret - if wrap: - return buffer.cursorPrevMatch(regex, buffer.lines.high, high) - -proc refreshTermAttrs*(buffer: Buffer): bool = - let newAttrs = getTermAttributes(stdout) - if newAttrs != buffer.attrs: - buffer.attrs = newAttrs - buffer.width = newAttrs.width - buffer.height = newAttrs.height - 1 - return true - return false - -proc updateCursor(buffer: Buffer) = - if buffer.fromy > buffer.lastVisibleLine - 1: - buffer.cpos.fromy = 0 - buffer.cpos.cursory = buffer.lastVisibleLine - 1 - - if buffer.cursory >= buffer.lines.len: - buffer.cpos.cursory = max(0, buffer.lines.len - 1) - - if buffer.lines.len == 0: - buffer.cpos.cursory = 0 - -proc updateHover(buffer: Buffer) = - let thisnode = buffer.currentDisplayCell().node - let prevnode = buffer.prevnode - - if thisnode != prevnode and (thisnode == nil or prevnode == nil or thisnode.node != prevnode.node): - for styledNode in thisnode.branch: - if styledNode.t == STYLED_ELEMENT and styledNode.node != nil: - let elem = Element(styledNode.node) - if not elem.hover: - elem.hover = true - buffer.reshape = true - - let link = thisnode.getLink() - if link != nil: - buffer.hovertext = link.href - else: - buffer.hovertext = "" - - for styledNode in prevnode.branch: - if styledNode.t == STYLED_ELEMENT and styledNode.node != nil: - let elem = Element(styledNode.node) - if elem.hover: - elem.hover = false - buffer.reshape = true - - buffer.prevnode = thisnode - -proc loadResource(buffer: Buffer, document: Document, elem: HTMLLinkElement) = - let url = parseUrl(elem.href, document.location.some) - if url.isSome: - let url = url.get - if url.scheme == buffer.location.scheme: - let media = elem.media - if media != "": - let media = parseMediaQueryList(parseListOfComponentValues(newStringStream(media))) - if not media.applies(): return - let fs = buffer.loader.doRequest(newRequest(url)) - if fs.body != nil and fs.contenttype == "text/css": - elem.sheet = parseStylesheet(fs.body) - -proc loadResources(buffer: Buffer, document: Document) = - var stack: seq[Element] - if document.html != nil: - stack.add(document.html) - while stack.len > 0: - let elem = stack.pop() - - if elem.tagType == TAG_LINK: - let elem = HTMLLinkElement(elem) - if elem.rel == "stylesheet": - buffer.loadResource(document, elem) - - for child in elem.children_rev: - stack.add(child) - -proc load*(buffer: Buffer) = - case buffer.contenttype - of "text/html": - if not buffer.streamclosed: - buffer.source = buffer.istream.readAll() - buffer.istream.close() - buffer.istream = newStringStream(buffer.source) - buffer.document = parseHTML5(buffer.istream) - buffer.streamclosed = true - else: - buffer.document = parseHTML5(newStringStream(buffer.source)) - buffer.document.location = buffer.location - buffer.loadResources(buffer.document) - else: - if not buffer.streamclosed: - buffer.source = buffer.istream.readAll() - buffer.istream.close() - buffer.streamclosed = true - buffer.lines = renderPlainText(buffer.source) - -proc render*(buffer: Buffer) = - case buffer.contenttype - of "text/html": - if buffer.viewport == nil: - buffer.viewport = Viewport(term: buffer.attrs) - if buffer.userstyle == nil: - buffer.userstyle = buffer.config.stylesheet.parseStylesheet() - let ret = renderDocument(buffer.document, buffer.attrs, buffer.userstyle, buffer.viewport, buffer.prevstyled) - buffer.lines = ret[0] - buffer.prevstyled = ret[1] - else: discard - buffer.updateCursor() - -proc cursorBufferPos(buffer: Buffer) = - let x = buffer.acursorx - let y = buffer.acursory - print(HVP(y + 1, x + 1)) - -proc clearStatusMessage(buffer: Buffer) = - buffer.statusmsg = newFixedGrid(buffer.width) - -proc writeStatusMessage(buffer: Buffer, str: string, format: Format = Format()) = - buffer.clearStatusMessage() - var i = 0 - for r in str.runes: - i += r.width() - if i >= buffer.statusmsg.len: - buffer.statusmsg[^1].str = "$" - break - buffer.statusmsg[i].str &= r - buffer.statusmsg[i].format = format - -proc statusMsgForBuffer(buffer: Buffer) = - var msg = $(buffer.cursory + 1) & "/" & $buffer.numLines & " (" & - $buffer.atPercentOf() & "%) " & "<" & buffer.title & ">" - if buffer.hovertext.len > 0: - msg &= " " & buffer.hovertext - var format: Format - format.reverse = true - buffer.writeStatusMessage(msg, format) - -proc setStatusMessage*(buffer: Buffer, str: string) = - buffer.writeStatusMessage(str) - buffer.nostatus = true - -proc lineInfo*(buffer: Buffer) = - buffer.setStatusMessage("line " & $(buffer.cursory + 1) & "/" & $buffer.numLines & " col " & $(buffer.cursorx + 1) & "/" & $buffer.currentLineWidth() & " x: " & $buffer.currentCursorBytes()) - -proc displayBufferSwapOutput(buffer: Buffer) = - print(buffer.generateSwapOutput()) - -proc displayBuffer(buffer: Buffer) = - print(buffer.generateFullOutput()) - -proc displayStatusMessage*(buffer: Buffer) = - print(HVP(buffer.height + 1, 1)) - print(SGR()) - print(buffer.generateStatusMessage()) - print(SGR()) - -# https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set -proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encoding: string = ""): seq[tuple[name, value: string]] = - if form.constructingentrylist: - return - form.constructingentrylist = true - - var entrylist: seq[tuple[name, value: string]] - for field in form.controls: - if field.findAncestor({TAG_DATALIST}) != nil or - field.attrb("disabled") or - field.isButton() and Element(field) != submitter: - continue - - if field.tagType == TAG_INPUT: - let field = HTMLInputElement(field) - if field.inputType == INPUT_IMAGE: - let name = if field.attr("name") != "": - field.attr("name") & '.' - else: - "" - entrylist.add((name & 'x', $field.xcoord)) - entrylist.add((name & 'y', $field.ycoord)) - continue - - #TODO custom elements - - let name = field.attr("name") - - if name == "": - continue - - if field.tagType == TAG_SELECT: - let field = HTMLSelectElement(field) - for option in field.options: - if option.selected or option.disabled: - entrylist.add((name, option.value)) - elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType in {INPUT_CHECKBOX, INPUT_RADIO}: - let value = if field.attr("value") != "": - field.attr("value") - else: - "on" - entrylist.add((name, value)) - elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType == INPUT_FILE: - #TODO file - discard - elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType == INPUT_HIDDEN and name.equalsIgnoreCase("_charset_"): - let charset = if encoding != "": - encoding - else: - "UTF-8" - entrylist.add((name, charset)) - else: - if field.tagType == TAG_INPUT: - entrylist.add((name, HTMLInputElement(field).value)) - else: - assert false - if field.tagType == TAG_TEXTAREA or - field.tagType == TAG_INPUT and HTMLInputElement(field).inputType in {INPUT_TEXT, INPUT_SEARCH}: - if field.attr("dirname") != "": - let dirname = field.attr("dirname") - let dir = "ltr" #TODO bidi - entrylist.add((dirname, dir)) - - form.constructingentrylist = false - return entrylist - -#https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm -proc makeCRLF(s: string): string = - result = newStringOfCap(s.len) - var i = 0 - while i < s.len - 1: - if s[i] == '\r' and s[i + 1] != '\n': - result &= '\r' - result &= '\n' - elif s[i] != '\r' and s[i + 1] == '\n': - result &= s[i] - result &= '\r' - result &= '\n' - inc i - else: - result &= s[i] - inc i - -proc serializeMultipartFormData(kvs: seq[(string, string)]): MimeData = - for it in kvs: - let name = makeCRLF(it[0]) - let value = makeCRLF(it[1]) - result[name] = value - -proc serializePlainTextFormData(kvs: seq[(string, string)]): string = - for it in kvs: - let (name, value) = it - result &= name - result &= '=' - result &= value - result &= "\r\n" - -proc submitForm(form: HTMLFormElement, submitter: Element): Option[Request] = - let entrylist = form.constructEntryList(submitter) - - let action = if submitter.action() == "": - $form.document.location - else: - submitter.action() - - let url = parseUrl(action, submitter.document.baseUrl.some) - if url.isnone: - return none(Request) - - var parsedaction = url.get - let scheme = parsedaction.scheme - let enctype = submitter.enctype() - let formmethod = submitter.formmethod() - if formmethod == FORM_METHOD_DIALOG: - #TODO - return none(Request) - let httpmethod = if formmethod == FORM_METHOD_GET: - HTTP_GET - else: - assert formmethod == FORM_METHOD_POST - HTTP_POST - - #let target = if submitter.isSubmitButton() and submitter.attrb("formtarget"): - # submitter.attr("formtarget") - #else: - # submitter.target() - #let noopener = true #TODO - - template mutateActionUrl() = - let query = serializeApplicationXWWWFormUrlEncoded(entrylist) - parsedaction.query = query.some - return newRequest(parsedaction, httpmethod).some - - template submitAsEntityBody() = - var mimetype: string - var body = none(string) - var multipart = none(MimeData) - case enctype - of FORM_ENCODING_TYPE_URLENCODED: - body = serializeApplicationXWWWFormUrlEncoded(entrylist).some - mimeType = $enctype - of FORM_ENCODING_TYPE_MULTIPART: - multipart = serializeMultipartFormData(entrylist).some - mimetype = $enctype - of FORM_ENCODING_TYPE_TEXT_PLAIN: - body = serializePlainTextFormData(entrylist).some - mimetype = $enctype - return newRequest(parsedaction, httpmethod, {"Content-Type": mimetype}, body, multipart).some - - template getActionUrl() = - return newRequest(parsedaction).some - - case scheme - of "http", "https": - if formmethod == FORM_METHOD_GET: - mutateActionUrl - else: - assert formmethod == FORM_METHOD_POST - submitAsEntityBody - of "ftp": - getActionUrl - of "data": - if formmethod == FORM_METHOD_GET: - mutateActionUrl - else: - assert formmethod == FORM_METHOD_POST - getActionUrl - -proc click*(buffer: Buffer): Option[Request] = - let clickable = buffer.getCursorClickable() - if clickable != nil: - template set_focus(e: Element) = - if buffer.document.focus != e: - buffer.document.focus = e - buffer.reshape = true - template restore_focus = - if buffer.document.focus != nil: - buffer.document.focus = nil - buffer.reshape = true - case clickable.tagType - of TAG_SELECT: - set_focus clickable - of TAG_A: - restore_focus - let url = parseUrl(HTMLAnchorElement(clickable).href, clickable.document.baseUrl.some) - if url.issome: - return newRequest(url.get, HTTP_GET).some - of TAG_OPTION: - let option = HTMLOptionElement(clickable) - let select = option.select - if select != nil: - if buffer.document.focus == select: - # select option - if not select.attrb("multiple"): - for option in select.options: - option.selected = false - option.selected = true - restore_focus - else: - # focus on select - set_focus select - of TAG_INPUT: - restore_focus - let input = HTMLInputElement(clickable) - case input.inputType - of INPUT_SEARCH: - var value = input.value - print(HVP(buffer.height + 1, 1)) - print(EL()) - let status = readLine("SEARCH: ", value, buffer.width, {'\r', '\n'}, config = buffer.config, tty = buffer.tty) - if status: - input.value = value - input.invalid = true - buffer.reshape = true - if input.form != nil: - let submitaction = submitForm(input.form, input) - return submitaction - of INPUT_TEXT, INPUT_PASSWORD: - var value = input.value - print(HVP(buffer.height + 1, 1)) - print(EL()) - let status = readLine("TEXT: ", value, buffer.width, {'\r', '\n'}, input.inputType == INPUT_PASSWORD, config = buffer.config, tty = buffer.tty) - if status: - input.value = value - input.invalid = true - buffer.reshape = true - of INPUT_FILE: - var path = if input.file.issome: - input.file.get.path.serialize_unicode() - else: - "" - print(HVP(buffer.height + 1, 1)) - print(EL()) - let status = readLine("Filename: ", path, buffer.width, {'\r', '\n'}, config = buffer.config, tty = buffer.tty) - if status: - let cdir = parseUrl("file://" & getCurrentDir() & DirSep) - let path = parseUrl(path, cdir) - if path.issome: - input.file = path - input.invalid = true - buffer.reshape = true - of INPUT_CHECKBOX: - input.checked = not input.checked - input.invalid = true - buffer.reshape = true - of INPUT_RADIO: - for radio in input.radiogroup: - radio.checked = false - radio.invalid = true - input.checked = true - input.invalid = true - buffer.reshape = true - of INPUT_RESET: - if input.form != nil: - input.form.reset() - buffer.reshape = true - of INPUT_SUBMIT, INPUT_BUTTON: - if input.form != nil: - let submitaction = submitForm(input.form, input) - return submitaction - else: - restore_focus - else: - restore_focus - -proc setupBuffer*(buffer: Buffer) = - buffer.load() - buffer.render() - buffer.gotoAnchor() - buffer.redraw = true - -proc dupeBuffer*(buffer: Buffer, location = none(URL)): Buffer = - let clone = newBuffer(buffer.config, buffer.tty) - clone.contenttype = buffer.contenttype - clone.ispipe = buffer.ispipe - if location.isSome: - clone.location = location.get - else: - clone.location = buffer.location - clone.istream = newStringStream(buffer.source) - clone.setupBuffer() - return clone - -proc drawBuffer*(buffer: Buffer) = - var format = newFormat() - for line in buffer.lines: - if line.formats.len == 0: - print(line.str & "\n") - else: - var x = 0 - var i = 0 - for f in line.formats: - var outstr = "" - #assert f.pos < line.str.width(), "fpos " & $f.pos & "\nstr" & line.str & "\n" - while x < f.pos: - var r: Rune - fastRuneAt(line.str, i, r) - outstr &= r - x += r.width() - print(outstr) - print(format.processFormat(f.format)) - print(line.str.substr(i)) - print(format.processFormat(newFormat())) - print("\n") - -proc refreshTitle*(buffer: Buffer) = - buffer.title = buffer.getTitle() - -proc refreshBuffer*(buffer: Buffer, peek = false): bool {.discardable.} = - buffer.refreshTitle() - stdout.hideCursor() - - if buffer.refreshTermAttrs(): - buffer.redraw = true - buffer.reshape = true - - if buffer.redraw: - buffer.refreshDisplay() - buffer.displayBuffer() - #result = true - buffer.redraw = false - - if not peek: - buffer.updateHover() - - if buffer.reshape: - buffer.render() - buffer.reshape = false - buffer.refreshDisplay() - buffer.displayBufferSwapOutput() - #result = true - - if not peek: - if not buffer.nostatus: - buffer.statusMsgForBuffer() - else: - buffer.nostatus = false - buffer.displayStatusMessage() - buffer.cursorBufferPos() - stdout.showCursor() |