diff options
author | bptato <nincsnevem662@gmail.com> | 2021-01-23 14:20:41 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-01-23 14:20:41 +0100 |
commit | ed3886ab51a76f184d023d79e085f928427b8328 (patch) | |
tree | 1e95754cb83677fa278be4206f4876e3af835a84 | |
parent | 94d681b3935a3f9105dc60320230fa9657cbd7b5 (diff) | |
download | chawan-ed3886ab51a76f184d023d79e085f928427b8328.tar.gz |
unicode kinda broken otherwise better than ever
-rw-r--r-- | buffer.nim | 23 | ||||
-rw-r--r-- | config | 58 | ||||
-rw-r--r-- | config.nim | 7 | ||||
-rw-r--r-- | display.nim | 149 | ||||
-rw-r--r-- | htmlelement.nim | 59 | ||||
-rw-r--r-- | main.nim | 2 | ||||
-rw-r--r-- | readme.md | 4 | ||||
-rw-r--r-- | twtstr.nim | 35 |
8 files changed, 245 insertions, 92 deletions
diff --git a/buffer.nim b/buffer.nim index 788f8aae..f7cac95b 100644 --- a/buffer.nim +++ b/buffer.nim @@ -98,8 +98,8 @@ func onSpace*(buffer: Buffer): bool = return buffer.text.len > 0 and buffer.text[^1] == ' ' func cursorOnNode*(buffer: Buffer, node: HtmlNode): bool = - return buffer.cursorY >= node.y and buffer.cursorY < node.y + node.height and - buffer.cursorX >= node.x and buffer.cursorX <= node.x + node.width + return buffer.rawlines[buffer.cursorY - 1] + buffer.cursorX >= node.rawchar and + buffer.rawlines[buffer.cursorY - 1] + buffer.cursorX < node.rawend func findSelectedElement*(buffer: Buffer): Option[HtmlElement] = if buffer.selectedlink != nil and buffer.selectedLink.parentNode of HtmlElement: @@ -200,6 +200,7 @@ proc clearBuffer*(buffer: Buffer) = buffer.fromX = 0 buffer.fromY = 0 buffer.hovertext = "" + buffer.selectedlink = nil proc scrollTo*(buffer: Buffer, y: int): bool = if y == buffer.fromY: @@ -209,14 +210,16 @@ proc scrollTo*(buffer: Buffer, y: int): bool = return true proc cursorTo*(buffer: Buffer, x: int, y: int): bool = + result = false buffer.cursorY = min(max(y, 0), buffer.lastLine()) if buffer.fromY > buffer.cursorY: - buffer.fromY = min(buffer.cursorY, buffer.lastLine() - buffer.height) - elif buffer.fromY + buffer.height < buffer.cursorY: - buffer.fromY = max(buffer.cursorY - buffer.height, 0) + buffer.fromY = min(buffer.cursorY - 1, buffer.lastLine() - buffer.height) + result = true + elif buffer.fromY + buffer.height <= buffer.cursorY: + buffer.fromY = max(buffer.cursorY - buffer.height + 1, 0) + result = true buffer.cursorX = min(max(x, 0), buffer.currentRawLineLength()) buffer.fromX = min(max(buffer.currentRawLineLength() - buffer.width + 1, 0), 0) #TODO - return true proc cursorDown*(buffer: Buffer): bool = if buffer.cursorY < buffer.lastLine(): @@ -409,6 +412,12 @@ 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()) + return true + proc scrollDown*(buffer: Buffer): bool = if buffer.fromY + buffer.height <= buffer.lastLine(): buffer.fromY += 1 @@ -434,6 +443,7 @@ proc checkLinkSelection*(buffer: Buffer): bool = else: let anchor = buffer.selectedlink.ancestor(TAG_A) anchor.selected = false + buffer.selectedlink.fmttext = buffer.selectedlink.getFmtText() buffer.selectedlink = nil buffer.hovertext = "" for node in buffer.links: @@ -443,6 +453,7 @@ proc checkLinkSelection*(buffer: Buffer): bool = assert(anchor != nil) anchor.selected = true buffer.hovertext = HtmlAnchorElement(anchor).href + node.fmttext = node.getFmtText() return true return false diff --git a/config b/config new file mode 100644 index 00000000..48e81660 --- /dev/null +++ b/config @@ -0,0 +1,58 @@ +#normal mode keybindings +nmap q ACTION_QUIT +nmap h ACTION_CURSOR_LEFT +nmap j ACTION_CURSOR_DOWN +nmap k ACTION_CURSOR_UP +nmap l ACTION_CURSOR_RIGHT +nmap \e[D ACTION_CURSOR_LEFT +nmap \e[B ACTION_CURSOR_DOWN +nmap \e[A ACTION_CURSOR_UP +nmap \e[C ACTION_CURSOR_RIGHT +nmap ^ ACTION_CURSOR_LINEBEGIN +nmap $ ACTION_CURSOR_LINEEND +nmap b ACTION_CURSOR_PREV_WORD +nmap B ACTION_CURSOR_PREV_NODE +nmap w ACTION_CURSOR_NEXT_WORD +nmap W ACTION_CURSOR_NEXT_NODE +nmap [ ACTION_CURSOR_PREV_LINK +nmap ] ACTION_CURSOR_NEXT_LINK +nmap H ACTION_CURSOR_TOP +nmap M ACTION_CURSOR_MIDDLE +nmap L ACTION_CURSOR_BOTTOM +nmap C-d ACTION_HALF_PAGE_DOWN +nmap C-u ACTION_HALF_PAGE_UP +nmap C-f ACTION_PAGE_DOWN +nmap C-b ACTION_PAGE_UP +nmap \e[6~ ACTION_PAGE_DOWN +nmap \e[5~ ACTION_PAGE_UP +nmap C-e ACTION_SCROLL_DOWN +nmap C-y ACTION_SCROLL_UP +nmap J ACTION_SCROLL_DOWN +nmap K ACTION_SCROLL_UP +nmap C-m ACTION_CLICK +nmap C-j ACTION_CLICK +nmap C-l ACTION_CHANGE_LOCATION +nmap U ACTION_RELOAD +nmap r ACTION_RESHAPE +nmap R ACTION_REDRAW +nmap gg ACTION_CURSOR_FIRST_LINE +nmap G ACTION_CURSOR_LAST_LINE +nmap \e[H ACTION_CURSOR_FIRST_LINE +nmap \e[F ACTION_CURSOR_LAST_LINE +nmap z ACTION_CENTER_LINE +nmap C-g ACTION_LINE_INFO + +#line editing keybindings +lemap C-m ACTION_LINED_SUBMIT +lemap C-j ACTION_LINED_SUBMIT +lemap C-h ACTION_LINED_BACKSPACE +lemap C-? ACTION_LINED_BACKSPACE +lemap C-c ACTION_LINED_CANCEL +lemap \eb ACTION_LINED_PREV_WORD +lemap \ef ACTION_LINED_NEXT_WORD +lemap C-b ACTION_LINED_BACK +lemap C-f ACTION_LINED_FORWARD +lemap C-u ACTION_LINED_CLEAR +lemap C-k ACTION_LINED_KILL +lemap C-w ACTION_LINED_KILL_WORD +lemap C-v ACTION_LINED_ESC diff --git a/config.nim b/config.nim index f13d172b..8df30caf 100644 --- a/config.nim +++ b/config.nim @@ -23,6 +23,7 @@ type ACTION_RELOAD, ACTION_RESHAPE, ACTION_REDRAW, ACTION_CURSOR_FIRST_LINE, ACTION_CURSOR_LAST_LINE, ACTION_CURSOR_TOP, ACTION_CURSOR_MIDDLE, ACTION_CURSOR_BOTTOM, + ACTION_CENTER_LINE, ACTION_LINE_INFO, ACTION_LINED_SUBMIT, ACTION_LINED_CANCEL, ACTION_LINED_BACKSPACE, ACTION_LINED_CLEAR, ACTION_LINED_KILL, ACTION_LINED_KILL_WORD, ACTION_LINED_BACK, ACTION_LINED_FORWARD, @@ -84,10 +85,10 @@ proc constructActionTable*(origTable: var Table[string, TwtAction]): Table[strin return newTable macro staticReadKeymap(): untyped = - var keymap = staticRead"keymap" + var config = staticRead"config" var normalActionMap: Table[string, TwtAction] var linedActionMap: Table[string, TwtAction] - for line in keymap.split('\n'): + for line in config.split('\n'): if line.len == 0 or line[0] == '#': continue let cmd = line.split(' ') @@ -126,7 +127,7 @@ macro staticReadKeymap(): untyped = staticReadKeymap() -proc readKeymap*(filename: string): bool = +proc readConfig*(filename: string): bool = var f: File let status = f.open(filename, fmRead) var normalActionMap: Table[string, TwtAction] diff --git a/display.nim b/display.nim index 19876ab5..ec4552de 100644 --- a/display.nim +++ b/display.nim @@ -29,7 +29,7 @@ type lastwidth: int atchar: int atrawchar: int - centerqueue: int + centerqueue: seq[HtmlNode] centerlen: int blanklines: int blankspaces: int @@ -71,33 +71,49 @@ proc writeWrappedText(buffer: Buffer, state: var RenderState, node: HtmlNode) = var fmtword = "" var rawword = "" var prevl = false - for r in node.rawtext.runes: - rawword &= r - state.x += 1 + for w in node.fmttext: + if w.len > 0 and w[0] == '\e': + fmtword &= w + continue + + for r in w.runes: + fmtword &= r + rawword &= r + + state.x += 1 - if state.x > buffer.width: - state.lastwidth = max(state.lastwidth, state.x) - buffer.flushLine(state) - prevl = true - else: - state.lastwidth = max(state.lastwidth, state.x) - - if r == runeSpace: - eprint "x at", rawword, "is", state.x, "." - buffer.writefmt(fmtword) - buffer.writeraw(rawword) - state.atchar += fmtword.len - state.atrawchar += rawword.len if prevl: state.x += rawword.runeLen prevl = false - fmtword = "" - rawword = "" + + if r == runeSpace: + buffer.writefmt(fmtword) + buffer.writeraw(rawword) + state.atchar += fmtword.len + state.atrawchar += rawword.runeLen() + fmtword = "" + rawword = "" + + if state.x > buffer.width: + if buffer.rawtext.len > 0 and buffer.rawtext[^1] == ' ': + buffer.rawtext = buffer.rawtext.substr(0, buffer.rawtext.len - 2) + buffer.text = buffer.text.substr(0, buffer.text.len - 2) + state.atchar -= 1 + state.atrawchar -= 1 + state.x -= 1 + state.lastwidth = max(state.lastwidth, state.x) + buffer.flushLine(state) + state.x = -1 + prevl = true + else: + state.lastwidth = max(state.lastwidth, state.x) + + n += 1 buffer.writefmt(fmtword) buffer.writeraw(rawword) state.atchar += fmtword.len - state.atrawchar += rawword.len + state.atrawchar += rawword.runeLen() state.lastwidth = max(state.lastwidth, state.x) proc preAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = @@ -108,8 +124,7 @@ proc preAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = if node.openblock: while state.blanklines < max(elem.margin, elem.margintop): buffer.flushLine(state) - if elem.display == DISPLAY_LIST_ITEM: - state.indent += 1 + state.indent += elem.indent if not buffer.onNewLine() and state.blanklines == 0 and node.displayed(): buffer.addSpaces(state, state.nextspaces) @@ -121,11 +136,12 @@ proc preAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = buffer.addSpaces(state, max(buffer.width div 2 - state.centerlen div 2, 0)) state.centerlen = 0 - if elem.display == DISPLAY_LIST_ITEM and state.indent > 0: + if node.isElemNode() and elem.display == DISPLAY_LIST_ITEM and state.indent > 0: + buffer.flushLine(state) var listchar = "" case elem.parentElement.tagType of TAG_UL: - listchar = "*" + listchar = "•" of TAG_OL: state.listval += 1 listchar = $state.listval & ")" @@ -133,9 +149,9 @@ proc preAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = return buffer.addSpaces(state, state.indent) buffer.write(listchar) - state.x += 1 - state.atchar += 1 - state.atrawchar += 1 + state.x += listchar.runeLen() + state.atchar += listchar.len + state.atrawchar += listchar.runeLen() buffer.addSpaces(state, 1) proc postAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = @@ -147,56 +163,61 @@ proc postAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = if not buffer.onNewLine() and state.blanklines == 0: state.nextspaces += max(elem.margin, elem.marginright) - if node.closeblock: + if node.closeblock and (node.isTextNode() or elem.numChildNodes == 0): buffer.flushLine(state) if node.closeblock: while state.blanklines < max(elem.margin, elem.marginbottom): buffer.flushLine(state) - if elem.display == DISPLAY_LIST_ITEM and node.isTextNode(): - state.indent -= 1 + state.indent -= elem.indent if elem.tagType == TAG_BR and not node.openblock: buffer.flushLine(state) - if elem.display == DISPLAY_LIST_ITEM and node.isElemNode(): - buffer.flushLine(state) - proc renderNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = let elem = node.nodeAttr() if elem.tagType == TAG_TITLE: if node.isTextNode(): - buffer.title = $node.rawtext + buffer.title = node.rawtext return else: discard if elem.hidden: return if not state.docenter: if elem.centered: - if not node.closeblock and elem.tagType != TAG_BR: - state.centerqueue += 1 + state.centerqueue.add(node) + if node.closeblock or elem.tagType == TAG_BR: + state.docenter = true + state.centerlen = 0 + for node in state.centerqueue: + state.centerlen += node.getRawLen() + for node in state.centerqueue: + buffer.renderNode(node, state) + state.centerqueue.setLen(0) + state.docenter = false return - if state.centerqueue > 0: + else: + return + if state.centerqueue.len > 0: state.docenter = true state.centerlen = 0 - var i = state.centerqueue - while i > 0: - state.centerlen += buffer.nodes[^i].getRawLen() - i -= 1 - while state.centerqueue > 0: - buffer.renderNode(buffer.nodes[^state.centerqueue], state) - state.centerqueue -= 1 + for node in state.centerqueue: + state.centerlen += node.getRawLen() + for node in state.centerqueue: + buffer.renderNode(node, state) + state.centerqueue.setLen(0) state.docenter = false buffer.preAlignNode(node, state) node.x = state.x node.y = state.y + node.fmtchar = state.atchar + node.rawchar = state.atrawchar buffer.writeWrappedText(state, node) - #if state.x != node.x: - # eprint node.x, node.y, state.x, state.y, node.nodeAttr().tagType - # eprint "len", state.atrawchar - node.width = state.lastwidth - node.x + node.fmtend = state.atchar + node.rawend = state.atrawchar + node.width = state.lastwidth - node.x - 1 node.height = state.y - node.y + 1 buffer.postAlignNode(node, state) @@ -229,8 +250,8 @@ proc renderHtml*(buffer: Buffer) = var state = newRenderState() while stack.len > 0: let currElem = stack.pop() - buffer.renderNode(currElem.html, state) buffer.addNode(currElem.html) + buffer.renderNode(currElem.html, state) if currElem.xml.len > 0: var last = false for item in currElem.xml.revItems: @@ -272,7 +293,7 @@ proc displayBuffer(buffer: Buffer) = eraseScreen() termGoto(0, 0) - print(buffer.visibleText()) + print(buffer.visibleText().ansiReset()) proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = var s = "" @@ -288,6 +309,7 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = let action = getNormalAction(s) var redraw = false var reshape = false + var nostatus = false case action of ACTION_QUIT: eraseScreen() @@ -313,6 +335,7 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = 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_CLICK: @@ -332,11 +355,15 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = return true of ACTION_CHANGE_LOCATION: var url = $buffer.document.location + clearStatusMsg(buffer.height) let status = readLine("URL:", url) if status: buffer.setLocation(parseUri(url)) return true + of ACTION_LINE_INFO: + statusMsg("line " & $buffer.cursorY & "/" & $buffer.lastLine() & " col " & $buffer.cursorX & "/" & $buffer.currentLineLength(), buffer.width) + nostatus = true of ACTION_FEED_NEXT: feedNext = true of ACTION_RELOAD: return true @@ -345,13 +372,31 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = redraw = true of ACTION_REDRAW: redraw = true else: discard - redraw = redraw or buffer.checkLinkSelection() + if reshape: buffer.clearText() buffer.drawHtml() if redraw: buffer.displayBuffer() - buffer.statusMsgForBuffer() + + let prevlink = buffer.selectedlink + let sel = buffer.checkLinkSelection() + if prevlink != nil and prevlink != buffer.selectedlink: + termGoto(prevlink.x - buffer.fromX, prevlink.y - buffer.fromY - 1) + print(buffer.text.substr(prevlink.fmtchar, prevlink.fmtend)) + if sel: + termGoto(buffer.selectedlink.x - buffer.fromX, buffer.selectedlink.y - buffer.fromY - 1) + let str = buffer.text.substr(buffer.selectedlink.fmtchar, buffer.selectedlink.fmtend) + var i = str.findChar('\n') + while i != -1: + print("".ansiStyle(styleUnderscore)) + i = str.findChar('\n', i + 1) + print(str.ansiStyle(styleUnderscore).ansiReset()) + + if not nostatus: + buffer.statusMsgForBuffer() + else: + nostatus = false proc displayPage*(attrs: TermAttributes, buffer: Buffer): bool = #buffer.printwrite = true diff --git a/htmlelement.nim b/htmlelement.nim index e6dc0ca8..8a61acb7 100644 --- a/htmlelement.nim +++ b/htmlelement.nim @@ -25,9 +25,13 @@ type parentElement*: HtmlElement rawtext*: string - fmttext*: string + fmttext*: seq[string] x*: int y*: int + fmtchar*: int + rawchar*: int + fmtend*: int + rawend*: int width*: int height*: int openblock*: bool @@ -56,6 +60,8 @@ type underscore*: bool islink*: bool selected*: bool + numChildNodes*: int + indent*: int HtmlInputElement* = ref HtmlInputElementObj HtmlInputElementObj = object of HtmlElementObj @@ -125,10 +131,10 @@ func isDocument*(node: HtmlNode): bool = return node.nodeType == NODE_DOCUMENT func getFmtLen*(htmlNode: HtmlNode): int = - return htmlNode.fmttext.len + return htmlNode.fmttext.join().runeLen() func getRawLen*(htmlNode: HtmlNode): int = - return htmlNode.rawtext.len + return htmlNode.rawtext.runeLen() func toInputType*(str: string): InputType = case str @@ -164,7 +170,7 @@ func toInputSize*(str: string): int = return 20 return str.parseInt() -func getFmtInput(inputElement: HtmlInputElement): string = +func getFmtInput(inputElement: HtmlInputElement): seq[string] = case inputElement.itype of INPUT_TEXT, INPUT_SEARCH: let valueFit = fitValueToSize(inputElement.value, inputElement.size) @@ -194,7 +200,7 @@ proc getRawText*(htmlNode: HtmlNode): string = elif htmlNode.isTextNode(): if htmlNode.parentElement != nil and htmlNode.parentElement.tagType != TAG_PRE: result = htmlNode.rawtext.remove("\n") - if unicode.strip($result).toRunes().len > 0: + if unicode.strip(result).runeLen() > 0: if htmlNode.nodeAttr().display != DISPLAY_INLINE: if htmlNode.previousSibling == nil or htmlNode.previousSibling.nodeAttr().display != DISPLAY_INLINE: result = unicode.strip(result, true, false) @@ -209,28 +215,31 @@ proc getRawText*(htmlNode: HtmlNode): string = else: assert(false) -func getFmtText*(htmlNode: HtmlNode): string = +func getFmtText*(htmlNode: HtmlNode): seq[string] = if htmlNode.isElemNode(): case HtmlElement(htmlNode).tagType of TAG_INPUT: return HtmlInputElement(htmlNode).getFmtInput() - else: return "" + else: return @[] elif htmlNode.isTextNode(): - result = htmlNode.rawtext - if htmlNode.parentElement != nil and htmlNode.parentElement.islink: - result = result.ansiFgColor(fgBlue) - let parent = HtmlElement(htmlNode.parentNode).ancestor(TAG_A) - if parent != nil and parent.selected: + result &= htmlNode.rawtext + if htmlNode.parentElement != nil: + if htmlNode.parentElement.islink: + result = result.ansiFgColor(fgBlue).ansiReset() + let anchor = htmlNode.ancestor(TAG_A) + if anchor != nil and anchor.selected: + result = result.ansiStyle(styleUnderscore).ansiReset() + + if htmlNode.parentElement.tagType == TAG_OPTION: + result = result.ansiFgColor(fgRed).ansiReset() + + if htmlNode.parentElement.bold: + result = result.ansiStyle(styleBright).ansiReset() + if htmlNode.parentElement.italic: + result = result.ansiStyle(styleItalic).ansiReset() + if htmlNode.parentElement.underscore: result = result.ansiStyle(styleUnderscore).ansiReset() - - if HtmlElement(htmlNode.parentNode).tagType == TAG_OPTION: - result = result.ansiFgColor(fgRed).ansiReset() - - if HtmlElement(htmlNode.parentNode).bold: - result = result.ansiStyle(styleBright).ansiReset() - if HtmlElement(htmlNode.parentNode).italic: - result = result.ansiStyle(styleItalic).ansiReset() - if HtmlElement(htmlNode.parentNode).underscore: - result = result.ansiStyle(styleUnderscore).ansiReset() + else: + assert(false, "Uhhhh I'm pretty sure we should have parent elements for text nodes?" & htmlNode.rawtext) else: assert(false) @@ -306,6 +315,11 @@ proc getHtmlElement*(xmlElement: XmlNode, parentNode: HtmlNode): HtmlElement = result = optionElement of TAG_PRE, TAG_TD, TAG_TH: result.margin = 1 + of TAG_UL, TAG_OL: + result.indent = 1 + of TAG_H1, TAG_H2, TAG_H3, TAG_H4, TAG_H5, TAG_H6: + result.bold = true + result.marginbottom = 1 else: discard if parentNode.isElemNode(): @@ -317,6 +331,7 @@ proc getHtmlElement*(xmlElement: XmlNode, parentNode: HtmlNode): HtmlElement = result.hidden = result.hidden or parent.hidden result.islink = result.islink or parent.islink + result.numChildNodes = xmlElement.len proc getHtmlNode*(xmlElement: XmlNode, parent: HtmlNode): HtmlNode = case kind(xmlElement) diff --git a/main.nim b/main.nim index c98f0db5..db4b0f6d 100644 --- a/main.nim +++ b/main.nim @@ -33,7 +33,7 @@ proc main*() = if paramCount() != 1: eprint "Invalid parameters. Usage:\ntwt <url>" quit(1) - if not readKeymap("keymap"): + if not readConfig("config"): eprint "Failed to read keymap, falling back to default" let attrs = getTermAttributes() let buffer = newBuffer(attrs) diff --git a/readme.md b/readme.md index fe58b84d..32c6a7dc 100644 --- a/readme.md +++ b/readme.md @@ -5,8 +5,8 @@ A terminal web browser. It displays websites in your terminal and allows you to ## Why make another web browser? I've found other terminal web browsers insufficient for my needs, so I thought it'd be a fun excercise to write one myself. -I don't really want a standard-compliant browser, or one that displays pages perfectly - the only way you could do that in a terminal is to work like browsh, which kinda defeats the point of a terminal web browser. I want one that is good enough for daily use - something like lynx or w3m, but better. -So the aim is to implement HTML rendering, some degree of JS support, and a very limited subset of CSS. Plus some other things I'd add to w3m if it weren't 50k lines of incomprehensible ancient C code. +I don't really want a standard-compliant browser, or one that displays pages perfectly - the only way you could do that in a terminal is to work like browsh, which kinda defeats the point of a terminal web browser. I want one that is good enough for daily use, something like lynx or w3m. +So the aim is to implement HTML rendering, some degree of JS support, and a very limited subset of CSS. Plus some other things. ## So what can this do? Currently implemented features are: diff --git a/twtstr.nim b/twtstr.nim index 85035a61..5f762780 100644 --- a/twtstr.nim +++ b/twtstr.nim @@ -4,13 +4,25 @@ import unicode const runeSpace* = " ".runeAt(0) -func ansiStyle*(str: string, style: Style): string = - return ansiStyleCode(style) & str & "\e[0m" +func ansiStyle*(str: string, style: Style): seq[string] = + result &= ansiStyleCode(style) + result &= str -func ansiFgColor*(str: string, color: ForegroundColor): string = - return ansiForegroundColorCode(color) & str & ansiResetCode +func ansiFgColor*(str: string, color: ForegroundColor): seq[string] = + result &= ansiForegroundColorCode(color) + result &= str -func ansiReset*(str: string): string = +func ansiReset*(str: string): seq[string] = + result &= str + result &= ansiResetCode + +func ansiStyle*(str: seq[string], style: Style): seq[string] = + return ansiStyleCode(style) & str + +func ansiFgColor*(str: seq[string], color: ForegroundColor): seq[string] = + return ansiForegroundColorCode(color) & str + +func ansiReset*(str: seq[string]): seq[string] = return str & ansiResetCode func maxString*(str: string, max: int): string = @@ -23,7 +35,10 @@ func fitValueToSize*(str: string, size: int): string = return str & ' '.repeat(size - str.runeLen) return str.maxString(size) -func buttonFmt*(str: string): string = +func buttonFmt*(str: string): seq[string] = + return "[".ansiFgColor(fgRed) & str.ansiFgColor(fgRed).ansiReset() & "]".ansiFgColor(fgRed).ansiReset() + +func buttonFmt*(str: seq[string]): seq[string] = return "[".ansiFgColor(fgRed) & str.ansiFgColor(fgRed).ansiReset() & "]".ansiFgColor(fgRed).ansiReset() func buttonRaw*(str: string): string = @@ -51,3 +66,11 @@ func getControlLetter*(c: char): char = elif c == '\x7F': return '?' assert(false) + +func findChar*(str: string, c: char, start: int = 0): int = + var i = start + while i < str.len: + if str[i] == c: + return i + i += 1 + return -1 |