diff options
author | bptato <nincsnevem662@gmail.com> | 2021-01-22 15:35:56 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-01-22 15:35:56 +0100 |
commit | 94d681b3935a3f9105dc60320230fa9657cbd7b5 (patch) | |
tree | 3eb9189d01f3627927bc25b53fca1da20ae9b1e2 | |
parent | 5d6af7f57a89239554a9cd51fe60f8227d7ce549 (diff) | |
download | chawan-94d681b3935a3f9105dc60320230fa9657cbd7b5.tar.gz |
something broke again
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | buffer.nim | 17 | ||||
-rw-r--r-- | config.nim | 9 | ||||
-rw-r--r-- | display.nim | 62 | ||||
-rw-r--r-- | fmttext.nim | 8 | ||||
-rw-r--r-- | htmlelement.nim | 58 | ||||
-rw-r--r-- | keymap | 1 | ||||
-rw-r--r-- | twtio.nim | 111 | ||||
-rw-r--r-- | twtstr.nim | 79 |
9 files changed, 182 insertions, 165 deletions
diff --git a/Makefile b/Makefile index 5f71b0e1..9d231e4a 100644 --- a/Makefile +++ b/Makefile @@ -4,4 +4,6 @@ release: nim compile -d:release -d:ssl -o:twt main.nim release_opt: nim compile -d:danger -d:ssl -o:twt_opt main.nim +clean: + rm ./twt ./dtwt ./twt_opt all: debug release release_opt diff --git a/buffer.nim b/buffer.nim index df1f0612..788f8aae 100644 --- a/buffer.nim +++ b/buffer.nim @@ -14,7 +14,7 @@ type Buffer* = ref BufferObj BufferObj = object text*: string - rawText*: string + rawtext*: string lines*: seq[int] rawlines*: seq[int] title*: string @@ -45,14 +45,12 @@ proc newBuffer*(attrs: TermAttributes): Buffer = cursorY: 1, document: newDocument()) - - func lastLine*(buffer: Buffer): int = - assert buffer.rawlines.len == buffer.lines.len + assert(buffer.rawlines.len == buffer.lines.len) return buffer.lines.len - 1 func lastVisibleLine*(buffer: Buffer): int = - return min(buffer.fromY + buffer.height - 2, buffer.lastLine()) + return min(buffer.fromY + buffer.height - 1, buffer.lastLine()) #doesn't include newline func lineLength*(buffer: Buffer, line: int): int = @@ -147,7 +145,7 @@ proc addNode*(buffer: Buffer, htmlNode: HtmlNode) = else: discard elif htmlNode.isTextNode(): if htmlNode.parentElement != nil and htmlNode.parentElement.islink: - let anchor = htmlNode.getParent(TAG_A) + let anchor = htmlNode.ancestor(TAG_A) assert(anchor != nil) buffer.clickables.add(anchor) @@ -434,14 +432,14 @@ proc checkLinkSelection*(buffer: Buffer): bool = if buffer.cursorOnNode(buffer.selectedlink): return false else: - let anchor = buffer.selectedlink.getParent(TAG_A) + let anchor = buffer.selectedlink.ancestor(TAG_A) anchor.selected = false buffer.selectedlink = nil buffer.hovertext = "" for node in buffer.links: if buffer.cursorOnNode(node): buffer.selectedlink = node - let anchor = node.getParent(TAG_A) + let anchor = node.ancestor(TAG_A) assert(anchor != nil) anchor.selected = true buffer.hovertext = HtmlAnchorElement(anchor).href @@ -456,4 +454,7 @@ proc gotoAnchor*(buffer: Buffer): bool = return false proc setLocation*(buffer: Buffer, uri: Uri) = + buffer.document.location = uri + +proc gotoLocation*(buffer: Buffer, uri: Uri) = buffer.document.location = buffer.document.location.combine(uri) diff --git a/config.nim b/config.nim index df3d16d6..f13d172b 100644 --- a/config.nim +++ b/config.nim @@ -2,6 +2,8 @@ import tables import strutils import macros +import twtstr + type TwtAction* = enum @@ -30,13 +32,6 @@ type var normalActionRemap*: Table[string, TwtAction] var linedActionRemap*: Table[string, TwtAction] -func getControlChar(c: char): char = - if int(c) >= int('a'): - return char(int(c) - int('a') + 1) - elif c == '?': - return char(127) - assert(false) - proc getRealKey(key: string): string = var realk: string var currchar: char diff --git a/display.nim b/display.nim index 5ebf78f3..19876ab5 100644 --- a/display.nim +++ b/display.nim @@ -64,28 +64,18 @@ proc addSpaces(buffer: Buffer, state: var RenderState, n: int) = state.atchar += n state.atrawchar += n +const runeSpace = " ".toRunes()[0] proc writeWrappedText(buffer: Buffer, state: var RenderState, node: HtmlNode) = state.lastwidth = 0 var n = 0 - var fmtword: ustring = @[] - var rawword: ustring = @[] + var fmtword = "" + var rawword = "" var prevl = false - for r in node.fmttext: - fmtword &= r - - if n >= node.rawtext.len or r != node.rawtext[n]: - continue - + for r in node.rawtext.runes: rawword &= r state.x += 1 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) prevl = true @@ -93,21 +83,21 @@ proc writeWrappedText(buffer: Buffer, state: var RenderState, node: HtmlNode) = state.lastwidth = max(state.lastwidth, state.x) if r == runeSpace: - buffer.writefmt($fmtword) - buffer.writeraw($rawword) - state.atchar += ($fmtword).len - state.atrawchar += ($rawword).len + 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.len + state.x += rawword.runeLen prevl = false - fmtword = @[] - rawword = @[] - n += 1 - - buffer.writefmt($fmtword) - buffer.writeraw($rawword) - state.atchar += ($fmtword).len - state.atrawchar += ($rawword).len + fmtword = "" + rawword = "" + + buffer.writefmt(fmtword) + buffer.writeraw(rawword) + state.atchar += fmtword.len + state.atrawchar += rawword.len state.lastwidth = max(state.lastwidth, state.x) proc preAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = @@ -173,8 +163,6 @@ proc postAlignNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = buffer.flushLine(state) proc renderNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = - if not node.visibleNode(): - return let elem = node.nodeAttr() if elem.tagType == TAG_TITLE: if node.isTextNode(): @@ -205,9 +193,9 @@ proc renderNode(buffer: Buffer, node: HtmlNode, state: var RenderState) = node.x = state.x node.y = state.y 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 + #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.height = state.y - node.y + 1 @@ -228,8 +216,8 @@ type proc setLastHtmlLine(buffer: Buffer, state: var RenderState) = if buffer.text.len != buffer.lines[^1]: - state.atchar = buffer.text.len + 1 - state.atrawchar = buffer.rawtext.len + 1 + state.atchar = buffer.text.len + state.atrawchar = buffer.rawtext.len buffer.flushLine(state) proc renderHtml*(buffer: Buffer) = @@ -250,7 +238,7 @@ proc renderHtml*(buffer: Buffer) = html: getHtmlNode(item, currElem.html)) stack.add(child) currElem.html.childNodes.add(child.html) - if not last and child.html.visibleNode(): + if not last and not child.html.hidden: last = true if HtmlElement(currElem.html).display == DISPLAY_BLOCK: stack[^1].html.closeblock = true @@ -339,8 +327,8 @@ proc inputLoop(attrs: TermAttributes, buffer: Buffer): bool = redraw = true else: discard if selectedElem.get().islink: - let anchor = HtmlAnchorElement(buffer.selectedlink.getParent(TAG_A)).href - buffer.setLocation(parseUri(anchor)) + let anchor = HtmlAnchorElement(buffer.selectedlink.ancestor(TAG_A)).href + buffer.gotoLocation(parseUri(anchor)) return true of ACTION_CHANGE_LOCATION: var url = $buffer.document.location diff --git a/fmttext.nim b/fmttext.nim new file mode 100644 index 00000000..148ac626 --- /dev/null +++ b/fmttext.nim @@ -0,0 +1,8 @@ +type + FmtText* = object + str*: string + beginStyle*: string + endStyle*: string + +func `$`*(stt: FmtText): string = + return stt.beginStyle & stt.str & stt.endStyle diff --git a/htmlelement.nim b/htmlelement.nim index c37b294e..e6dc0ca8 100644 --- a/htmlelement.nim +++ b/htmlelement.nim @@ -24,8 +24,8 @@ type parentNode*: HtmlNode parentElement*: HtmlElement - rawtext*: ustring - fmttext*: ustring + rawtext*: string + fmttext*: string x*: int y*: int width*: int @@ -130,12 +130,6 @@ func getFmtLen*(htmlNode: HtmlNode): int = func getRawLen*(htmlNode: HtmlNode): int = return htmlNode.rawtext.len -func visibleNode*(node: HtmlNode): bool = - case node.nodeType - of NODE_TEXT: return true - of NODE_ELEMENT: return true - else: return false - func toInputType*(str: string): InputType = case str of "button": INPUT_BUTTON @@ -170,63 +164,61 @@ func toInputSize*(str: string): int = return 20 return str.parseInt() -func getFmtInput(inputElement: HtmlInputElement): ustring = +func getFmtInput(inputElement: HtmlInputElement): string = case inputElement.itype of INPUT_TEXT, INPUT_SEARCH: - let valueFit = fitValueToSize(inputElement.value.toRunes(), inputElement.size) + let valueFit = fitValueToSize(inputElement.value, inputElement.size) return valueFit.ansiStyle(styleUnderscore).ansiReset().buttonFmt() of INPUT_SUBMIT: - return inputElement.value.toRunes().buttonFmt() + return inputElement.value.buttonFmt() else: discard -func getRawInput(inputElement: HtmlInputElement): ustring = +func getRawInput(inputElement: HtmlInputElement): string = case inputElement.itype of INPUT_TEXT, INPUT_SEARCH: - return inputElement.value.toRunes().fitValueToSize(inputElement.size).buttonFmt() + return inputElement.value.fitValueToSize(inputElement.size).buttonRaw() of INPUT_SUBMIT: - return inputElement.value.toRunes().buttonFmt() + return inputElement.value.buttonRaw() else: discard -func getParent*(htmlNode: HtmlNode, tagType: TagType): HtmlElement = - var pnode = htmlNode.parentElement - while pnode != nil and pnode.tagType != tagType: - pnode = pnode.parentElement - - return pnode +func ancestor*(htmlNode: HtmlNode, tagType: TagType): HtmlElement = + result = htmlNode.parentElement + while result != nil and result.tagType != tagType: + result = result.parentElement -proc getRawText*(htmlNode: HtmlNode): ustring = +proc getRawText*(htmlNode: HtmlNode): string = if htmlNode.isElemNode(): case HtmlElement(htmlNode).tagType of TAG_INPUT: return HtmlInputElement(htmlNode).getRawInput() - else: return @[] + else: return "" elif htmlNode.isTextNode(): if htmlNode.parentElement != nil and htmlNode.parentElement.tagType != TAG_PRE: - result = htmlNode.rawtext.remove(runeNewline) + result = htmlNode.rawtext.remove("\n") if unicode.strip($result).toRunes().len > 0: if htmlNode.nodeAttr().display != DISPLAY_INLINE: if htmlNode.previousSibling == nil or htmlNode.previousSibling.nodeAttr().display != DISPLAY_INLINE: - result = unicode.strip($result, true, false).toRunes() + result = unicode.strip(result, true, false) if htmlNode.nextSibling == nil or htmlNode.nextSibling.nodeAttr().display != DISPLAY_INLINE: - result = unicode.strip($result, false, true).toRunes() + result = unicode.strip(result, false, true) else: - result = @[] + result = "" else: - result = unicode.strip($htmlNode.rawtext).toRunes() + result = unicode.strip(htmlNode.rawtext) if htmlNode.parentElement != nil and htmlNode.parentElement.tagType == TAG_OPTION: result = result.buttonRaw() else: assert(false) -func getFmtText*(htmlNode: HtmlNode): ustring = +func getFmtText*(htmlNode: HtmlNode): 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).getParent(TAG_A) + let parent = HtmlElement(htmlNode.parentNode).ancestor(TAG_A) if parent != nil and parent.selected: result = result.ansiStyle(styleUnderscore).ansiReset() @@ -334,15 +326,15 @@ proc getHtmlNode*(xmlElement: XmlNode, parent: HtmlNode): HtmlNode = of xnText: new(result) result.nodeType = NODE_TEXT - result.rawtext = xmlElement.text.toRunes() + result.rawtext = xmlElement.text of xnComment: new(result) result.nodeType = NODE_COMMENT - result.rawtext = xmlElement.text.toRunes() + result.rawtext = xmlElement.text of xnCData: new(result) result.nodeType = NODE_CDATA - result.rawtext = xmlElement.text.toRunes() + result.rawtext = xmlElement.text else: assert(false) result.parentNode = parent diff --git a/keymap b/keymap index b63600f7..51708c8e 100644 --- a/keymap +++ b/keymap @@ -53,3 +53,4 @@ 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/twtio.nim b/twtio.nim index 4c02cfb0..32e4d55c 100644 --- a/twtio.nim +++ b/twtio.nim @@ -1,6 +1,7 @@ import terminal import tables import strutils +import unicode import twtstr import config @@ -9,6 +10,13 @@ template print*(s: varargs[string, `$`]) = for x in s: stdout.write(x) +template printesc*(s: string) = + for ruby in s: + if ($ruby)[0].isControlChar(): + stdout.write(($($ruby)[0].getControlLetter()).ansiFgColor(fgBlue).ansiStyle(styleBright).ansiReset()) + else: + stdout.write($ruby) + template eprint*(s: varargs[string, `$`]) = {.cast(noSideEffect).}: var a = false for x in s: @@ -36,10 +44,11 @@ proc readLine*(prompt: string, current: var string): bool = var new = current print(prompt) print(' ') - print(new) + printesc(new) var s = "" var feedNext = false - var cursor = new.len + var escNext = false + var cursor = new.runeLen while true: if not feedNext: s = "" @@ -47,7 +56,10 @@ proc readLine*(prompt: string, current: var string): bool = feedNext = false let c = getch() s &= c - let action = getLinedAction(s) + var rl = new.runeLen() + var action = getLinedAction(s) + if escNext: + action = NO_ACTION case action of ACTION_LINED_CANCEL: return false @@ -56,70 +68,95 @@ proc readLine*(prompt: string, current: var string): bool = return true of ACTION_LINED_BACKSPACE: if cursor > 0: - print(' '.repeat(new.len - cursor + 1)) - print('\b'.repeat(new.len - cursor + 1)) + print(' '.repeat(rl - cursor + 1)) + print('\b'.repeat(rl - cursor + 1)) print("\b \b") - new = new.substr(0, cursor - 2) & new.substr(cursor, new.len) + new = new.runeSubstr(0, cursor - 1) & new.runeSubstr(cursor, rl) + rl = new.runeLen() cursor -= 1 - print(new.substr(cursor, new.len)) - print('\b'.repeat(new.len - cursor)) + printesc(new.runeSubstr(cursor, rl)) + print('\b'.repeat(rl - cursor)) of ACTION_LINED_ESC: - new &= c - print("^[".ansiFgColor(fgBlue).ansiStyle(styleBright).ansiReset()) + escNext = true of ACTION_LINED_CLEAR: - print(' '.repeat(new.len - cursor + 1)) - print('\b'.repeat(new.len - cursor + 1)) + print(' '.repeat(rl - cursor + 1)) + print('\b'.repeat(rl - cursor + 1)) print('\b'.repeat(cursor)) print(' '.repeat(cursor)) print('\b'.repeat(cursor)) - new = new.substr(cursor, new.len) - print(new) - print('\b'.repeat(new.len)) + new = new.runeSubstr(cursor, rl) + rl = new.runeLen() + printesc(new) + print('\b'.repeat(rl)) cursor = 0 of ACTION_LINED_KILL: - print(' '.repeat(new.len - cursor + 1)) - print('\b'.repeat(new.len - cursor + 1)) - new = new.substr(0, cursor - 1) + print(' '.repeat(rl - cursor + 1)) + print('\b'.repeat(rl - cursor + 1)) + new = new.runeSubstr(0, cursor) of ACTION_LINED_BACK: if cursor > 0: cursor -= 1 print("\b") of ACTION_LINED_FORWARD: - if cursor < new.len: - print(new[cursor]) + if cursor < rl: + var rune: Rune + new.fastRuneAt(cursor, rune, false) + printesc($rune) cursor += 1 of ACTION_LINED_PREV_WORD: while cursor > 0: print('\b') cursor -= 1 - if new[cursor] == ' ': + var rune: Rune + new.fastRuneAt(cursor, rune, false) + if rune == runeSpace: break of ACTION_LINED_NEXT_WORD: - while cursor < new.len: - print(new[cursor]) + while cursor < rl: + var rune: Rune + new.fastRuneAt(cursor, rune, false) + printesc($rune) cursor += 1 - if cursor < new.len and new[cursor] == ' ': - break + if cursor < rl: + new.fastRuneAt(cursor, rune, false) + if rune == runeSpace: + break of ACTION_LINED_KILL_WORD: var chars = 0 while cursor > chars: chars += 1 - if new[cursor - chars] == ' ': + var rune: Rune + new.fastRuneAt(cursor - chars, rune, false) + if rune == runeSpace: break if chars > 0: - print(' '.repeat(new.len - cursor + 1)) - print('\b'.repeat(new.len - cursor + 1)) + print(' '.repeat(rl - cursor + 1)) + print('\b'.repeat(rl - cursor + 1)) print("\b \b".repeat(chars)) - new = new.substr(0, cursor - 1 - chars) & new.substr(cursor, new.len) + new = new.runeSubstr(0, cursor - chars) & new.runeSubstr(cursor, rl) + rl = new.runeLen() cursor -= chars - print(new.substr(cursor, new.len)) - print('\b'.repeat(new.len - cursor)) + printesc(new.runeSubstr(cursor, rl)) + print('\b'.repeat(rl - cursor)) of ACTION_FEED_NEXT: feedNext = true - else: - print(' '.repeat(new.len - cursor + 1)) - print('\b'.repeat(new.len - cursor + 1)) - new = new.substr(0, cursor - 1) & c & new.substr(cursor, new.len) - print(new.substr(cursor, new.len)) - print('\b'.repeat(new.len - cursor - 1)) + elif validateUtf8(s) == -1: + var cs = "" + for c in s: + if not c.isControlChar(): + cs &= c + elif escNext: + cs &= c + escNext = false + escNext = false + if cs.len == 0: + continue + print(' '.repeat(rl - cursor + 1)) + print('\b'.repeat(rl - cursor + 1)) + new = new.runeSubstr(0, cursor) & cs & new.runeSubstr(cursor, rl) + rl = new.runeLen() + printesc(new.runeSubstr(cursor, rl)) + print('\b'.repeat(rl - cursor - 1)) cursor += 1 + else: + feedNext = true diff --git a/twtstr.nim b/twtstr.nim index 50f0e4da..85035a61 100644 --- a/twtstr.nim +++ b/twtstr.nim @@ -2,59 +2,52 @@ import terminal import strutils import unicode -type ustring* = seq[Rune] - -const runeSpace*: Rune = " ".toRunes()[0] -const runeNewline*: Rune = "\n".toRunes()[0] -const runeReturn*: Rune = "\r".toRunes()[0] - -func isWhitespace(r: Rune): bool = - case r - of runeSpace, runeNewline, runeReturn: return true - else: return false +const runeSpace* = " ".runeAt(0) func ansiStyle*(str: string, style: Style): string = - return ansiStyleCode(style) & str + return ansiStyleCode(style) & str & "\e[0m" func ansiFgColor*(str: string, color: ForegroundColor): string = - return ansiForegroundColorCode(color) & str + return ansiForegroundColorCode(color) & str & ansiResetCode func ansiReset*(str: string): string = return str & ansiResetCode -func ansiStyle*(str: ustring, style: Style): ustring = - return ansiStyleCode(style).toRunes() & str - -func ansiFgColor*(str: ustring, color: ForegroundColor): ustring = - return ansiForegroundColorCode(color).toRunes & str - -func ansiReset*(str: ustring): ustring = - return str & ansiResetCode.toRunes() - -func maxString*(str: ustring, max: int): ustring = - result = str - if max < str.len: - result.setLen(max - 2) - result &= "$".toRunes() - func maxString*(str: string, max: int): string = - result = str if max < str.len: - result.setLen(max - 1) - result[max - 2] = '$' + return str.substr(0, max - 2) & "$" + return str -func fitValueToSize*(str: ustring, size: int): ustring = - if str.len < size: - return str & ' '.repeat(size - str.len).toRunes() +func fitValueToSize*(str: string, size: int): string = + if str.runeLen < size: + return str & ' '.repeat(size - str.runeLen) return str.maxString(size) -func buttonFmt*(str: ustring): ustring = - return "[".toRunes().ansiFgColor(fgRed).ansiReset() & str.ansiFgColor(fgRed).ansiReset() & "]".ansiFgColor(fgRed).toRunes().ansiReset() - -func buttonRaw*(str: ustring): ustring = - return "[".toRunes() & str & "]".toRunes() - -func remove*(s: ustring, r: Rune): ustring = - for c in s: - if c != r: - result.add(c) +func buttonFmt*(str: string): string = + return "[".ansiFgColor(fgRed) & str.ansiFgColor(fgRed).ansiReset() & "]".ansiFgColor(fgRed).ansiReset() + +func buttonRaw*(str: string): string = + return "[" & str & "]" + +func remove*(str: string, c: string): string = + let rem = c.toRunes()[0] + for rune in str.runes: + if rem != rune: + result &= $rune + +func isControlChar*(c: char): bool = + return int(c) <= 0x1F or int(c) == 0x7F + +func getControlChar*(c: char): char = + if int(c) >= int('a'): + return char(int(c) - int('a') + 1) + elif c == '?': + return char(127) + assert(false) + +func getControlLetter*(c: char): char = + if int(c) <= 0x1F: + return char(int(c) + int('A') - 1) + elif c == '\x7F': + return '?' + assert(false) |