diff options
-rw-r--r-- | src/buffer/container.nim | 67 | ||||
-rw-r--r-- | src/display/pager.nim | 8 | ||||
-rw-r--r-- | src/display/term.nim | 18 | ||||
-rw-r--r-- | src/layout/box.nim | 1 | ||||
-rw-r--r-- | src/layout/engine.nim | 13 | ||||
-rw-r--r-- | src/render/renderdocument.nim | 8 | ||||
-rw-r--r-- | src/render/rendertext.nim | 16 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 20 |
8 files changed, 78 insertions, 73 deletions
diff --git a/src/buffer/container.nim b/src/buffer/container.nim index 065177a7..8383485b 100644 --- a/src/buffer/container.nim +++ b/src/buffer/container.nim @@ -138,12 +138,6 @@ func fromy*(container: Container): int {.inline.} = container.pos.fromy func xend(container: Container): int {.inline.} = container.pos.xend func lastVisibleLine(container: Container): int = min(container.fromy + container.height, container.numLines) - 1 -func acursorx*(container: Container): int = - max(0, container.cursorx - container.fromx) - -func acursory*(container: Container): int = - container.cursory - container.fromy - func currentLine(container: Container): string = return container.getLine(container.cursory).str @@ -154,41 +148,47 @@ func cursorBytes(container: Container, y: int, cc = container.cursorx): int = while i < line.len and w < cc: var r: Rune fastRuneAt(line, i, r) - w += r.width() + w += r.twidth(w) return i func currentCursorBytes(container: Container, cc = container.cursorx): int = return container.cursorBytes(container.cursory, cc) -func prevWidth(container: Container): int = +# Returns the X position of the first cell occupied by the character the cursor +# currently points to. +func cursorFirstX(container: Container): int = if container.numLines == 0: return 0 let line = container.currentLine - if line.len == 0: return 0 var w = 0 var i = 0 - let cc = container.pos.fromx + container.pos.cursorx - var pr: Rune var r: Rune - fastRuneAt(line, i, r) - while i < line.len and w < cc: - pr = r + let cc = container.cursorx + while i < line.len: fastRuneAt(line, i, r) - w += r.width() - return pr.width() - -func currentWidth(container: Container): int = + let tw = r.twidth(w) + if w + tw > cc: + return w + w += tw + +# Returns the X position of the last cell occupied by the character the cursor +# currently points to. +func cursorLastX(container: Container): int = if container.numLines == 0: return 0 let line = container.currentLine - if line.len == 0: return 0 var w = 0 var i = 0 - let cc = container.cursorx var r: Rune - fastRuneAt(line, i, r) - while i < line.len and w < cc: + let cc = container.cursorx + while i < line.len and w <= cc: fastRuneAt(line, i, r) - w += r.width() - return r.width() + w += r.twidth(w) + return max(w - 1, 0) + +func acursorx*(container: Container): int = + max(0, container.cursorLastX() - container.fromx) + +func acursory*(container: Container): int = + container.cursory - container.fromy func maxScreenWidth(container: Container): int = for line in container.ilines(container.fromy..container.lastVisibleLine): @@ -387,13 +387,10 @@ proc cursorUp(container: Container) {.jsfunc.} = container.setCursorY(container.cursory - 1) proc cursorLeft(container: Container) {.jsfunc.} = - var w = container.prevWidth() - if w == 0: - w = 1 - container.setCursorX(container.cursorx - w) + container.setCursorX(container.cursorFirstX() - 1) proc cursorRight(container: Container) {.jsfunc.} = - container.setCursorX(container.cursorx + container.currentWidth()) + container.setCursorX(container.cursorLastX() + 1) proc cursorLineBegin(container: Container) {.jsfunc.} = container.setCursorX(0) @@ -412,7 +409,7 @@ proc cursorNextWord(container: Container) {.jsfunc.} = if r.breaksWord(): b = pb break - x += r.width() + x += r.twidth(x) while b < container.currentLine.len: let pb = b @@ -420,7 +417,7 @@ proc cursorNextWord(container: Container) {.jsfunc.} = if not r.breaksWord(): b = pb break - x += r.width() + x += r.twidth(x) if b < container.currentLine.len: container.setCursorX(x) @@ -442,14 +439,14 @@ proc cursorPrevWord(container: Container) {.jsfunc.} = if r.breaksWord(): break b -= o - x -= r.width() + x -= r.twidth(x) while b >= 0: let (r, o) = lastRune(container.currentLine, b) if not r.breaksWord(): break b -= o - x -= r.width() + x -= r.twidth(x) else: b = -1 @@ -611,7 +608,7 @@ proc cursorNextMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.} container.setCursorXY(res.x, res.y) if container.hlon: container.clearSearchHighlights() - let ex = res.x + res.str.width() - 1 + let ex = res.x + res.str.twidth(res.x) - 1 let hl = Highlight(x: res.x, y: res.y, endx: ex, endy: res.y, clear: true) container.highlights.add(hl) container.triggerEvent(UPDATE) @@ -630,7 +627,7 @@ proc cursorPrevMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.} container.setCursorXY(res.x, res.y) if container.hlon: container.clearSearchHighlights() - let ex = res.x + res.str.width() - 1 + let ex = res.x + res.str.twidth(res.x) - 1 let hl = Highlight(x: res.x, y: res.y, endx: ex, endy: res.y, clear: true) container.highlights.add(hl) container.hlon = false) diff --git a/src/display/pager.nim b/src/display/pager.nim index 49ff2227..057f9238 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -201,7 +201,7 @@ proc refreshDisplay(pager: Pager, container = pager.container) = # Skip cells till fromx. while w < container.fromx and i < line.str.len: fastRuneAt(line.str, i, r) - w += r.width() + w += r.twidth(w) let dls = by * pager.display.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.) @@ -218,7 +218,7 @@ proc refreshDisplay(pager: Pager, container = pager.container) = while i < line.str.len: let pw = w fastRuneAt(line.str, i, r) - w += r.width() + w += r.twidth(w) if w > container.fromx + pager.display.width: break # die on exceeding the width limit if nf.pos != -1 and nf.pos <= pw: @@ -228,7 +228,7 @@ proc refreshDisplay(pager: Pager, container = pager.container) = lan &= r if cf.pos != -1: pager.display[dls + k].format = cf.format - let tk = k + r.width() + let tk = k + r.twidth(k) while k < tk and k < pager.display.width - 1: inc k # Finally, override cell formatting for highlighted cells. @@ -258,7 +258,7 @@ proc writeStatusMessage(pager: Pager, str: string, format: Format = newFormat()) else: pager.statusgrid[i].str &= r pager.statusgrid[i].format = format - i += r.width() + i += r.twidth(i) proc refreshStatusMsg*(pager: Pager) = let container = pager.container diff --git a/src/display/term.nim b/src/display/term.nim index aca20637..1a3b8b9e 100644 --- a/src/display/term.nim +++ b/src/display/term.nim @@ -320,14 +320,20 @@ proc windowChange*(term: Terminal, attrs: WindowAttributes) = term.canvas = newFixedGrid(attrs.width, attrs.height) term.cleared = false -proc processOutputString(term: Terminal, str: string): string = +proc processOutputString(term: Terminal, str: string, w: var int): string = if str.validateUtf8() != -1: return "?" for r in str.runes(): - if r.isControlChar(): + let tw = r.twidth(w) + if r == Rune('\t'): + # Needs to be replaced with spaces, otherwise bgcolor isn't displayed. + for i in 0 ..< tw: + result &= ' ' + elif r.isControlChar(): result &= "^" & getControlLetter(char(r)) - elif r.width() != 0: + elif tw != 0: result &= r + w += tw proc generateFullOutput(term: Terminal, grid: FixedGrid): string = var format = newFormat() @@ -342,8 +348,7 @@ proc generateFullOutput(term: Terminal, grid: FixedGrid): string = inc w let cell = grid[y * grid.width + x] result &= term.processFormat(format, cell.format) - result &= term.processOutputString(cell.str) - w += cell.width() + result &= term.processOutputString(cell.str, w) if y != grid.height - 1: result &= "\r\n" @@ -371,8 +376,7 @@ proc generateSwapOutput(term: Terminal, grid: FixedGrid, prev: FixedGrid): strin line = "" lr = lr or (grid[i] != prev[i]) line &= term.processFormat(format, cell.format) - line &= term.processOutputString(cell.str) - w += cell.width() + line &= term.processOutputString(cell.str, w) inc x if lr: result &= term.cursorGoto(0, grid.height - 1) diff --git a/src/layout/box.nim b/src/layout/box.nim index 9ce95fb8..a436cfae 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -93,6 +93,7 @@ type height*: int lines*: seq[LineBox] currentLine*: LineBox + charwidth*: int whitespacenum*: int minwidth*: int diff --git a/src/layout/engine.nim b/src/layout/engine.nim index c5536517..b26e0ebe 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -212,6 +212,7 @@ proc flushWhitespace(ictx: InlineContext, computed: CSSComputedValues) = proc finishLine(ictx: InlineContext, computed: CSSComputedValues, maxwidth: int, force = false) = if ictx.currentLine.atoms.len != 0 or force: ictx.whitespacenum = 0 + ictx.charwidth = 0 ictx.flushWhitespace(computed) ictx.verticalAlignLine() @@ -255,6 +256,7 @@ proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, pcomputed: CS if atom of InlineWord: ictx.format = InlineWord(atom).format else: + ictx.charwidth = 0 ictx.format = nil ictx.currentLine.atoms.add(atom) @@ -315,13 +317,14 @@ proc checkWrap(state: var InlineState, r: Rune) = proc processWhitespace(state: var InlineState, c: char) = state.addWord() case state.computed{"white-space"} - of WHITESPACE_NORMAL, WHITESPACE_NOWRAP: + of WHITESPACE_NORMAL, WHITESPACE_NOWRAP, WHITESPACE_PRE_LINE: state.ictx.whitespacenum = max(state.ictx.whitespacenum, 1) - of WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP: + of WHITESPACE_PRE, WHITESPACE_PRE_WRAP: if c == '\n': state.ictx.flushLine(state.computed, state.maxwidth) elif c == '\t': - state.ictx.whitespacenum = (state.ictx.whitespacenum div 8 + 1) * 8 + state.ictx.charwidth = ((state.ictx.charwidth div 8) + 1) * 8 + state.word.str &= c else: inc state.ictx.whitespacenum @@ -348,7 +351,9 @@ proc renderText*(ictx: InlineContext, str: string, maxwidth: int, computed: CSSC state.hasshy = true else: state.word.str &= r - state.word.width += r.width() * state.ictx.cellwidth + let w = r.width() + state.word.width += w * state.ictx.cellwidth + state.ictx.charwidth += w if r == Rune('-'): # ascii dash state.wrappos = state.word.str.len state.hasshy = false diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim index bdcf4df8..a988cca7 100644 --- a/src/render/renderdocument.nim +++ b/src/render/renderdocument.nim @@ -40,7 +40,7 @@ proc setText(lines: var FlexibleGrid, linestr: string, cformat: ComputedFormat, while cx < x and i < lines[y].str.len: let pi = i fastRuneAt(lines[y].str, i, r) - let w = r.width() + let w = r.twidth(cx) # we must ensure x is max(cx, x), otherwise our assumption of cx <= x # breaks down if cx + w > x: @@ -55,13 +55,13 @@ proc setText(lines: var FlexibleGrid, linestr: string, cformat: ComputedFormat, lines[y].str &= ' '.repeat(padwidth) lines[y].str &= linestr - let linestrwidth = linestr.width() + let linestrwidth = linestr.twidth(x) - x i = 0 var nx = x # last x of new string while nx < x + linestrwidth and i < ostr.len: fastRuneAt(ostr, i, r) - nx += r.width() + nx += r.twidth(nx) if i < ostr.len: lines[y].str &= ostr.substr(i) @@ -153,7 +153,7 @@ proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, window: Wi var i = 0 while x < 0 and i < word.str.len: fastRuneAt(word.str, i, r) - x += r.width() + x += r.twidth(x) if x < 0: return # highest x is outside the canvas, no need to draw let linestr = word.str.substr(i) diff --git a/src/render/rendertext.nim b/src/render/rendertext.nim index e3d01638..24d7039e 100644 --- a/src/render/rendertext.nim +++ b/src/render/rendertext.nim @@ -6,9 +6,7 @@ import data/charset import encoding/decoderstream import utils/twtstr -const tabwidth = 8 type StreamRenderer* = object - w: int ansiparser: AnsiCodeParser format: Format af: bool @@ -35,7 +33,6 @@ proc renderStream*(grid: var FlexibleGrid, renderer: var StreamRenderer, len: in # avoid newline at end of stream grid.addLine() renderer.newline = false - renderer.w = 0 let r = buf[i] if r.isAscii(): let c = cast[char](r) @@ -44,27 +41,16 @@ proc renderStream*(grid: var FlexibleGrid, renderer: var StreamRenderer, len: in if not cancel: if renderer.ansiparser.state == PARSE_DONE: renderer.af = true - continue case c of '\n': add_format renderer.newline = true - continue - of '\r': continue - of '\t': - add_format - let w = ((renderer.w div tabwidth) + 1) * tabwidth - while renderer.w < w: - grid[^1].str &= ' ' - inc renderer.w - continue + of '\r': discard of '\e': renderer.ansiparser.reset() - continue else: add_format grid[^1].str &= c else: add_format grid[^1].str &= r - renderer.w += r.width() diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim index 91633734..96d4bf2f 100644 --- a/src/utils/twtstr.nim +++ b/src/utils/twtstr.nim @@ -993,10 +993,6 @@ func width*(r: Rune): int = return int(width_table[int(r)]) {.pop.} -func width*(s: string): int = - for r in s.runes(): - result += width(r) - func width*(s: string, len: int): int = var i = 0 var m = len @@ -1023,6 +1019,22 @@ func width*(s: seq[Rune], min: int): int = result += width(s[i]) inc i +# Width, but also works with tabs. +# Needs the column width of the text so far. +func twidth*(r: Rune, w: int): int = + if r != Rune('\t'): + return r.width() + return ((w div 8) + 1) * 8 - w + +func width*(s: string): int = + for r in s.runes(): + result += twidth(r, result) + +func twidth*(s: string, w: int): int = + result = w + for r in s.runes(): + result += twidth(r, result) + func breaksWord*(r: Rune): bool = return not (r.isDigitAscii() or r.width() == 0 or r.isAlpha()) |