about summary refs log tree commit diff stats
path: root/buffer.nim
diff options
context:
space:
mode:
Diffstat (limited to 'buffer.nim')
-rw-r--r--buffer.nim404
1 files changed, 203 insertions, 201 deletions
diff --git a/buffer.nim b/buffer.nim
index fcc24b76..06459d11 100644
--- a/buffer.nim
+++ b/buffer.nim
@@ -1,3 +1,5 @@
+#beware, awful code ahead
+
 import options
 import uri
 import tables
@@ -15,20 +17,19 @@ import twtstr
 type
   Buffer* = ref BufferObj
   BufferObj = object
-    text*: string
-    rawtext*: string
-    lines*: seq[int]
-    rawlines*: seq[int]
+    fmttext*: seq[string]
+    rawtext*: seq[string]
     title*: string
     hovertext*: string
-    htmlSource*: XmlNode
+    htmlsource*: XmlNode
     width*: int
     height*: int
-    cursorX*: int
-    cursorY*: int
+    cursorx*: int
+    cursory*: int
+    cursorchar*: int
     xend*: int
-    fromX*: int
-    fromY*: int
+    fromx*: int
+    fromy*: int
     nodes*: seq[HtmlNode]
     links*: seq[HtmlNode]
     clickables*: seq[HtmlNode]
@@ -40,73 +41,50 @@ type
     document*: Document
 
 proc newBuffer*(attrs: TermAttributes): Buffer =
-  return Buffer(lines: @[0],
-                rawlines: @[0],
-                width: attrs.termWidth,
+  return Buffer(width: attrs.termWidth,
                 height: attrs.termHeight,
-                cursorY: 1,
-                document: newDocument())
+                attrs: attrs)
 
 func lastLine*(buffer: Buffer): int =
-  assert(buffer.rawlines.len == buffer.lines.len)
-  return buffer.lines.len - 1
+  assert(buffer.fmttext.len == buffer.rawtext.len)
+  return buffer.fmttext.len - 1
 
 func lastVisibleLine*(buffer: Buffer): int =
-  return min(buffer.fromY + buffer.height - 1, buffer.lastLine())
-
-func currentLine*(buffer: Buffer): int =
-  return buffer.cursorY - 1
+  return min(buffer.fromy + buffer.height - 1, buffer.lastLine())
 
-func textBetween*(buffer: Buffer, s: int, e: int): string =
-  return buffer.text.runeSubstr(s, e - s)
-
-#doesn't include newline
-func lineLength*(buffer: Buffer, line: int): int =
-  assert buffer.lines.len > line
-  let str = buffer.textBetween(buffer.lines[line - 1], buffer.lines[line])
-  let len = mk_wcswidth_cjk(str)
-  if len >= 0:
-    return len
-  else:
-    return 0
-
-func rawLineLength*(buffer: Buffer, line: int): int =
-  assert buffer.rawlines.len > line
-  let str = buffer.textBetween(buffer.rawlines[line - 1], buffer.rawlines[line])
-  let len = mk_wcswidth_cjk(str)
-  if len >= 0:
-    return len
-  else:
-    return 0
+func realCurrentLineLength*(buffer: Buffer): int =
+  return mk_wcswidth_cjk(buffer.rawtext[buffer.cursory])
 
 func currentLineLength*(buffer: Buffer): int =
-  return buffer.lineLength(buffer.cursorY)
-
-func currentRawLineLength*(buffer: Buffer): int =
-  return buffer.rawLineLength(buffer.cursorY)
-
-func cursorAtLineEnd*(buffer: Buffer): bool =
-  return buffer.cursorX == buffer.currentRawLineLength()
+  return buffer.rawtext[buffer.cursory].runeLen()
 
 func atPercentOf*(buffer: Buffer): int =
-  return (100 * buffer.cursorY) div buffer.lastLine()
-
+  return (100 * buffer.cursory) div buffer.lastLine()
+
+func fmtBetween*(buffer: Buffer, sx: int, sy: int, ex: int, ey: int): string =
+  if sy < ey:
+    result &= buffer.rawtext[sy].runeSubstr(sx)
+    var i = sy + 1
+    while i < ey - 1:
+      result &= buffer.rawtext[i]
+      inc i
+    result &= buffer.rawtext[i].runeSubstr(0, ex - sx)
+  else:
+    result &= buffer.rawtext[sy].runeSubstr(sx, ex - sx)
 
 func visibleText*(buffer: Buffer): string = 
-  return buffer.textBetween(buffer.lines[buffer.fromY], buffer.lines[buffer.lastVisibleLine()])
+  return buffer.fmttext[buffer.fromy..buffer.lastVisibleLine()].join("\n")
 
 func lastNode*(buffer: Buffer): HtmlNode =
   return buffer.nodes[^1]
 
-func onNewLine*(buffer: Buffer): bool =
-  return buffer.text.len == 0 or buffer.text[^1] == '\n'
-
-func onSpace*(buffer: Buffer): bool =
-  return buffer.text.len > 0 and buffer.text[^1] == ' '
-
 func cursorOnNode*(buffer: Buffer, node: HtmlNode): bool =
-  return buffer.rawlines[buffer.cursorY - 1] + buffer.cursorX >= node.rawchar and
-         buffer.rawlines[buffer.cursorY - 1] + buffer.cursorX < node.rawend
+  if node.y == node.ey and node.y == buffer.cursory:
+    return buffer.cursorx >= node.x and buffer.cursorx < node.ex
+  else:
+    return (buffer.cursory == node.y and buffer.cursorx >= node.x) or
+           (buffer.cursory > node.y and buffer.cursory < node.ey) or
+           (buffer.cursory == node.ey and buffer.cursorx < node.ex)
 
 func findSelectedElement*(buffer: Buffer): Option[HtmlElement] =
   if buffer.selectedlink != nil and buffer.selectedLink.parentNode of HtmlElement:
@@ -117,9 +95,6 @@ func findSelectedElement*(buffer: Buffer): Option[HtmlElement] =
         if buffer.cursorOnNode(node): return some(HtmlElement(node))
   return none(HtmlElement)
 
-func cursorAt*(buffer: Buffer): int =
-  return buffer.rawlines[buffer.currentLine()] + buffer.cursorX
-
 func canScroll*(buffer: Buffer): bool =
   return buffer.lastLine() > buffer.height
 
@@ -131,7 +106,7 @@ func getElementById*(buffer: Buffer, id: string): HtmlElement =
 proc findSelectedNode*(buffer: Buffer): Option[HtmlNode] =
   for node in buffer.nodes:
     if node.getFmtLen() > 0 and node.displayed():
-      if buffer.cursorY >= node.y and buffer.cursorY <= node.y + node.height and buffer.cursorX >= node.x and buffer.cursorX <= node.x + node.width:
+      if buffer.cursory >= node.y and buffer.cursory <= node.y + node.height and buffer.cursorx >= node.x and buffer.cursorx <= node.x + node.width:
         return some(node)
   return none(HtmlNode)
 
@@ -160,12 +135,12 @@ proc addNode*(buffer: Buffer, htmlNode: HtmlNode) =
       buffer.idelements[elem.id] = elem
 
 proc writefmt*(buffer: Buffer, str: string) =
-  buffer.text &= str
+  buffer.fmttext &= str
   if buffer.printwrite:
     stdout.write(str)
 
 proc writefmt*(buffer: Buffer, c: char) =
-  buffer.text &= c
+  buffer.rawtext &= $c
   if buffer.printwrite:
     stdout.write(c)
 
@@ -173,7 +148,7 @@ proc writeraw*(buffer: Buffer, str: string) =
   buffer.rawtext &= str
 
 proc writeraw*(buffer: Buffer, c: char) =
-  buffer.rawtext &= c
+  buffer.rawtext &= $c
 
 proc write*(buffer: Buffer, str: string) =
   buffer.writefmt(str)
@@ -184,10 +159,8 @@ proc write*(buffer: Buffer, c: char) =
   buffer.writeraw(c)
 
 proc clearText*(buffer: Buffer) =
-  buffer.text = ""
-  buffer.rawtext = ""
-  buffer.lines = @[0]
-  buffer.rawlines = @[0]
+  buffer.fmttext.setLen(0)
+  buffer.rawtext.setLen(0)
 
 proc clearNodes*(buffer: Buffer) =
   buffer.nodes.setLen(0)
@@ -199,229 +172,236 @@ proc clearNodes*(buffer: Buffer) =
 proc clearBuffer*(buffer: Buffer) =
   buffer.clearText()
   buffer.clearNodes()
-  buffer.cursorX = 0
-  buffer.cursorY = 1
-  buffer.fromX = 0
-  buffer.fromY = 0
+  buffer.cursorx = 0
+  buffer.cursory = 0
+  buffer.fromx = 0
+  buffer.fromy = 0
   buffer.hovertext = ""
   buffer.selectedlink = nil
 
 proc scrollTo*(buffer: Buffer, y: int): bool =
-  if y == buffer.fromY:
+  if y == buffer.fromy:
     return false
-  buffer.fromY = min(max(buffer.lastLine() - buffer.height + 1, 0), y)
-  buffer.cursorY = min(max(buffer.fromY, buffer.cursorY), buffer.fromY + buffer.height)
+  buffer.fromy = min(max(buffer.lastLine() - buffer.height + 1, 0), y)
+  buffer.cursory = min(max(buffer.fromy, buffer.cursory), buffer.fromy + buffer.height)
   return true
 
 proc cursorTo*(buffer: Buffer, x: int, y: int): bool =
   result = false
-  buffer.cursorY = min(max(y, 1), buffer.lastLine())
-  if buffer.fromY >= buffer.cursorY:
-    buffer.fromY = max(buffer.cursorY - 1, 0)
+  buffer.cursory = min(max(y, 0), buffer.lastLine())
+  if buffer.fromy > buffer.cursory:
+    buffer.fromy = max(buffer.cursory, 0)
     result = true
-  elif buffer.fromY + buffer.height <= buffer.cursorY:
-    buffer.fromY = max(buffer.cursorY - buffer.height + 1, 0)
+  elif buffer.fromy + buffer.height - 1 <= buffer.cursory:
+    buffer.fromy = max(buffer.cursory - buffer.height + 2, 0)
     result = true
-  buffer.cursorX = min(max(x, 0), buffer.currentRawLineLength())
-  buffer.fromX = min(max(buffer.currentRawLineLength() - buffer.width + 1, 0), 0) #TODO
+  buffer.cursorx = min(max(x, 0), buffer.currentLineLength())
+  #buffer.fromX = min(max(buffer.currentLineLength() - buffer.width + 1, 0), 0) #TODO
 
 proc cursorDown*(buffer: Buffer): bool =
-  if buffer.cursorY < buffer.lastLine():
-    buffer.cursorY += 1
-    if buffer.cursorX > buffer.currentRawLineLength():
-      if buffer.xend == 0:
-        buffer.xend = buffer.cursorX
-      buffer.cursorX = buffer.currentRawLineLength()
+  if buffer.cursory < buffer.lastLine():
+    inc buffer.cursory
+    if buffer.cursorx >= buffer.currentLineLength():
+      buffer.cursorx = max(buffer.currentLineLength() - 1, 0)
     elif buffer.xend > 0:
-      buffer.cursorX = min(buffer.currentRawLineLength(), buffer.xend)
-    if buffer.cursorY > buffer.lastVisibleLine():
-      buffer.fromY += 1
+      buffer.cursorx = min(buffer.currentLineLength() - 1, buffer.xend)
+    if buffer.cursory >= buffer.lastVisibleLine() and buffer.lastVisibleLine() != buffer.lastLine():
+      inc buffer.fromy
       return true
   return false
 
 proc cursorUp*(buffer: Buffer): bool =
-  if buffer.cursorY > 1:
-    buffer.cursorY -= 1
-    if buffer.cursorX > buffer.currentRawLineLength():
-      if buffer.xend == 0:
-        buffer.xend = buffer.cursorX
-      buffer.cursorX = buffer.currentRawLineLength()
+  if buffer.cursory > 0:
+    dec buffer.cursory
+    if buffer.cursorx > buffer.currentLineLength():
+      if buffer.cursorx == 0:
+        buffer.xend = buffer.cursorx
+      buffer.cursorx = buffer.currentLineLength()
     elif buffer.xend > 0:
-      buffer.cursorX = min(buffer.currentRawLineLength(), buffer.xend)
-    if buffer.cursorY <= buffer.fromY:
-      buffer.fromY -= 1
+      buffer.cursorx = min(buffer.currentLineLength(), buffer.xend)
+    if buffer.cursory < buffer.fromy:
+      dec buffer.fromy
       return true
   return false
 
 proc cursorRight*(buffer: Buffer): bool =
-  if buffer.cursorX < buffer.currentRawLineLength():
-    buffer.cursorX += mk_wcwidth_cjk(buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX))
+  if buffer.cursorx < buffer.currentLineLength() - 1:
+    inc buffer.cursorx
     buffer.xend = 0
   else:
-    buffer.xend = buffer.cursorX
+    buffer.xend = buffer.cursorx
   return false
 
 proc cursorLeft*(buffer: Buffer): bool =
-  if buffer.cursorX > 0:
-    buffer.cursorX -= mk_wcwidth_cjk(buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX))
-    buffer.cursorX -= 1
+  if buffer.cursorx > 0:
+    dec buffer.cursorx
   buffer.xend = 0
   return false
 
 proc cursorLineBegin*(buffer: Buffer) =
-  buffer.cursorX = 0
+  buffer.cursorx = 0
   buffer.xend = 0
 
 proc cursorLineEnd*(buffer: Buffer) =
-  buffer.cursorX = buffer.currentRawLineLength()
-  buffer.xend = buffer.cursorX
+  buffer.cursorx = buffer.currentLineLength() - 1
+  buffer.xend = buffer.cursorx
 
 iterator revnodes*(buffer: Buffer): HtmlNode {.inline.} =
   var i = buffer.nodes.len - 1
   while i >= 0:
     yield buffer.nodes[i]
-    i -= 1
-
-proc cursorNextNode*(buffer: Buffer): bool =
-  for node in buffer.nodes:
-    if node.displayed():
-      if node.y > buffer.cursorY or (node.y == buffer.cursorY and node.x > buffer.cursorX):
-        return buffer.cursorTo(node.x, node.y)
-  buffer.cursorLineEnd()
-  return false
-
-proc cursorPrevNode*(buffer: Buffer): bool =
-  var prevnode: HtmlNode
-  for node in buffer.nodes:
-    if node.displayed():
-      if node.y >= buffer.cursorY and node.x >= buffer.cursorX and prevnode != nil:
-        return buffer.cursorTo(prevnode.x, prevnode.y)
-      prevnode = node
-  buffer.cursorLineBegin()
-  return false
+    dec i
 
 proc cursorNextWord*(buffer: Buffer): bool =
-  if buffer.cursorAtLineEnd():
-    if buffer.cursorY < buffer.lastLine():
-      let ret = buffer.cursorDown()
-      buffer.cursorLineBegin()
-      return ret
-    else:
-      buffer.cursorLineEnd()
-      return false
-
-  var res = buffer.cursorRight()
-  while buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX) != Rune(' '):
-    if buffer.cursorAtLineEnd():
-      return res
-    res = res or buffer.cursorRight()
+  let llen = buffer.currentLineLength() - 1
+  var r: Rune
+  var x = buffer.cursorx
+  var y = buffer.cursory
+  fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  while r != Rune(' '):
+    if x >= llen:
+      break
+    inc x
+    fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  while r == Rune(' '):
+    if x >= llen:
+      break
+    inc x
+    fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  if x >= llen:
+    if y < buffer.lastLine():
+      inc y
+      x = 0
+  return buffer.cursorTo(x, y)
 
 proc cursorPrevWord*(buffer: Buffer): bool =
-  if buffer.cursorX <= 1:
-    if buffer.cursorY > 1:
-      let ret = buffer.cursorUp()
-      buffer.cursorLineEnd()
-      return ret
-    else:
-      buffer.cursorLineBegin()
-      return false
-
-  discard buffer.cursorLeft()
-  while buffer.rawtext.runeAt(buffer.rawlines[buffer.currentLine()] + buffer.cursorX) != Rune(' '):
-    if buffer.cursorX == 0:
-      return false
-    discard buffer.cursorLeft()
+  var r: Rune
+  var x = buffer.cursorx
+  var y = buffer.cursory
+  fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  while r != Rune(' '):
+    if x == 0:
+      break
+    dec x
+    fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  while r == Rune(' '):
+    if x == 0:
+      break
+    dec x
+    fastRuneAt(buffer.rawtext[y], x, r, false)
+
+  if x == 0:
+    if y < buffer.lastLine():
+      dec y
+      x = buffer.rawtext[y].runeLen() - 1
+  return buffer.cursorTo(x, y)
 
 iterator revclickables*(buffer: Buffer): HtmlNode {.inline.} =
   var i = buffer.clickables.len - 1
   while i >= 0:
     yield buffer.clickables[i]
-    i -= 1
+    dec i
 
 proc cursorNextLink*(buffer: Buffer): bool =
   for node in buffer.clickables:
-    if node.y > buffer.cursorY or (node.y == buffer.cursorY and node.x > buffer.cursorX):
-      return buffer.cursorTo(node.x, node.y)
+    if node.y > buffer.cursory or (node.y == buffer.cursorY and node.x > buffer.cursorx):
+      result = buffer.cursorTo(node.x, node.y)
+      if buffer.cursorx < buffer.currentLineLength():
+        var r: Rune
+        fastRuneAt(buffer.rawtext[buffer.cursory], buffer.cursorx, r, false)
+        if r == Rune(' '):
+          return result or buffer.cursorNextWord()
+      return result
   return false
 
 proc cursorPrevLink*(buffer: Buffer): bool =
   for node in buffer.revclickables:
-    if node.y < buffer.cursorY or (node.y == buffer.cursorY and node.x < buffer.cursorX):
+    if node.y < buffer.cursorY or (node.y == buffer.cursorY and node.x < buffer.cursorx):
       return buffer.cursorTo(node.x, node.y)
   return false
 
 proc cursorFirstLine*(buffer: Buffer): bool =
-  if buffer.fromY > 0:
-    buffer.fromY = 0
+  if buffer.fromy > 0:
+    buffer.fromy = 0
     result = true
   else:
     result = false
 
-  buffer.cursorY = 1
+  buffer.cursorY = 0
   buffer.cursorLineBegin()
 
 proc cursorLastLine*(buffer: Buffer): bool =
-  if buffer.fromY < buffer.lastLine() - buffer.height:
-    buffer.fromY = buffer.lastLine() - (buffer.height - 1)
+  if buffer.fromy < buffer.lastLine() - buffer.height:
+    buffer.fromy = buffer.lastLine() - (buffer.height - 2)
     result = true
   else:
     result = false
-  buffer.cursorY = buffer.lastLine()
+  buffer.cursory = buffer.lastLine()
   buffer.cursorLineBegin()
 
+proc cursorTop*(buffer: Buffer): bool =
+  buffer.cursorY = buffer.fromy
+  return false
+
+proc cursorMiddle*(buffer: Buffer): bool =
+  buffer.cursorY = min(buffer.fromy + (buffer.height - 2) div 2, buffer.lastLine())
+  return false
+
+proc cursorBottom*(buffer: Buffer): bool =
+  buffer.cursorY = min(buffer.fromy + buffer.height - 2, buffer.lastLine())
+  return false
+
+proc centerLine*(buffer: Buffer): bool =
+  let ny = max(min(buffer.cursory - buffer.height div 2, buffer.lastLine() - buffer.height + 2), 0)
+  if ny != buffer.fromy:
+    buffer.fromy = ny
+    return true
+  return false
+
 proc halfPageUp*(buffer: Buffer): bool =
-  buffer.cursorY = max(buffer.cursorY - buffer.height div 2 + 1, 1)
-  if buffer.fromY - 1 > buffer.cursorY or true:
-    buffer.fromY = max(0, buffer.fromY - buffer.height div 2 + 1)
+  buffer.cursory = max(buffer.cursorY - buffer.height div 2 + 1, 0)
+  let nfy = max(0, buffer.fromy - buffer.height div 2 + 1)
+  if nfy != buffer.fromy:
+    buffer.fromy = nfy
     return true
   return false
 
 proc halfPageDown*(buffer: Buffer): bool =
-  buffer.cursorY = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine())
-  buffer.fromY = min(max(buffer.lastLine() - buffer.height + 1, 0), buffer.fromY + buffer.height div 2 - 1)
-  return true
+  buffer.cursory = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine())
+  let nfy = min(max(buffer.lastLine() - buffer.height + 2, 0), buffer.fromy + buffer.height div 2 - 1)
+  if nfy != buffer.fromy:
+    buffer.fromy = nfy
+    return true
+  return false
 
 proc pageUp*(buffer: Buffer): bool =
   buffer.cursorY = max(buffer.cursorY - buffer.height + 1, 1)
-  buffer.fromY = max(0, buffer.fromY - buffer.height)
+  buffer.fromy = max(0, buffer.fromy - buffer.height)
   return true
 
 proc pageDown*(buffer: Buffer): bool =
   buffer.cursorY = min(buffer.cursorY + buffer.height div 2 - 1, buffer.lastLine())
-  buffer.fromY = min(max(buffer.lastLine() - buffer.height + 1, 0), buffer.fromY + buffer.height div 2)
-  return true
-
-proc cursorTop*(buffer: Buffer): bool =
-  buffer.cursorY = buffer.fromY + 1
-  return false
-
-proc cursorMiddle*(buffer: Buffer): bool =
-  buffer.cursorY = min(buffer.fromY + buffer.height div 2, buffer.lastLine())
-  return false
-
-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())
+  buffer.fromy = min(max(buffer.lastLine() - buffer.height + 1, 0), buffer.fromy + buffer.height div 2)
   return true
 
 proc scrollDown*(buffer: Buffer): bool =
-  if buffer.fromY + buffer.height <= buffer.lastLine():
-    buffer.fromY += 1
-    if buffer.fromY >= buffer.cursorY:
+  if buffer.fromy + buffer.height - 1 <= buffer.lastLine():
+    inc buffer.fromy
+    if buffer.fromy >= buffer.cursory:
       discard buffer.cursorDown()
     return true
   discard buffer.cursorDown()
   return false
 
 proc scrollUp*(buffer: Buffer): bool =
-  if buffer.fromY > 0:
-    buffer.fromY -= 1
-    if buffer.fromY + buffer.height <= buffer.cursorY:
+  if buffer.fromy > 0:
+    dec buffer.fromy
+    if buffer.fromy + buffer.height - 1 <= buffer.cursorY:
       discard buffer.cursorUp()
     return true
   discard buffer.cursorUp()
@@ -437,6 +417,13 @@ proc checkLinkSelection*(buffer: Buffer): bool =
       buffer.selectedlink.fmttext = buffer.selectedlink.getFmtText()
       buffer.selectedlink = nil
       buffer.hovertext = ""
+      var stack: seq[HtmlNode]
+      stack.add(anchor)
+      while stack.len > 0:
+        let elem = stack.pop()
+        elem.fmttext = elem.getFmtText()
+        for child in elem.childNodes:
+          stack.add(child)
   for node in buffer.links:
     if buffer.cursorOnNode(node):
       buffer.selectedlink = node
@@ -444,7 +431,13 @@ proc checkLinkSelection*(buffer: Buffer): bool =
       assert(anchor != nil)
       anchor.selected = true
       buffer.hovertext = HtmlAnchorElement(anchor).href
-      node.fmttext = node.getFmtText()
+      var stack: seq[HtmlNode]
+      stack.add(anchor)
+      while stack.len > 0:
+        let elem = stack.pop()
+        elem.fmttext = elem.getFmtText()
+        for child in elem.childNodes:
+          stack.add(child)
       return true
   return false
 
@@ -460,3 +453,12 @@ proc setLocation*(buffer: Buffer, uri: Uri) =
 
 proc gotoLocation*(buffer: Buffer, uri: Uri) =
   buffer.document.location = buffer.document.location.combine(uri)
+
+proc refreshTermAttrs*(buffer: Buffer): bool =
+  let newAttrs = getTermAttributes()
+  if newAttrs != buffer.attrs:
+    buffer.attrs = newAttrs
+    buffer.width = newAttrs.termWidth
+    buffer.height = newAttrs.termHeight
+    return true
+  return false