« get me outta code hell

tui-lib - Pure Node.js library for making visual command-line programs (ala vim, ncdu)
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--util/ansi.js161
1 files changed, 82 insertions, 79 deletions
diff --git a/util/ansi.js b/util/ansi.js
index 9645f0d..5902b70 100644
--- a/util/ansi.js
+++ b/util/ansi.js
@@ -118,9 +118,8 @@ const ansi = {
     )
   },
 
-
   interpret(text, scrRows, scrCols, {
-    oldChars = null, oldLastChar = null,
+    oldChars = null, oldLastCharAttributes = null,
     oldCursorRow = 0, oldCursorCol = 0, oldShowCursor = true
   } = {}) {
     // Interprets the given ansi code, more or less.
@@ -130,22 +129,26 @@ const ansi = {
       char: ' '
     }
 
-    const chars = new Array(scrRows * scrCols).fill(blank)
+    const numChars = scrRows * scrCols
+    const chars = new Array(numChars * 2)
+
+    const fillBlank = () => {
+      for (let i = 0; i < numChars; i++) {
+        chars[i * 2] = ' '
+        chars[i * 2 + 1] = []
+      }
+    }
+
+    fillBlank()
 
     let showCursor = true
     let cursorRow = 1
     let cursorCol = 1
     const attributes = []
-    const getCursorIndex = () => (cursorRow - 1) * scrCols + (cursorCol - 1)
 
     for (let charI = 0; charI < text.length; charI++) {
+      const cursorIndex = (cursorRow - 1) * scrCols + (cursorCol - 1)
       if (text[charI] === ESC) {
-        if (false) {
-          chars[getCursorIndex()] = {char: '%', attributes: []}
-          cursorCol++
-          continue
-        }
-
         charI++
 
         if (text[charI] !== '[') {
@@ -192,7 +195,7 @@ const ansi = {
         if (text[charI] === 'J') {
           // ESC[2J - erase whole display
           if (args[0] === '2') {
-            chars.fill(blank)
+            fillBlank()
             charI += 3
             cursorCol = 1
             cursorRow = 1
@@ -200,15 +203,17 @@ const ansi = {
 
           // ESC[1J - erase to beginning
           else if (args[0] === '1') {
-            for (let i = 0; i < getCursorIndex(); i++) {
-              chars[i] = blank
+            for (let i = 0; i < cursorIndex; i++) {
+              chars[i * 2] = ' '
+              chars[i * 2 + 1] = []
             }
           }
 
           // ESC[0J - erase to end
           else if (args.length === 0 || args[0] === '0') {
-            for (let i = getCursorIndex(); i < chars.length; i++) {
-              chars[i] = blank
+            for (let i = cursorIndex; i < numChars; i++) {
+              chars[i * 2] = ' '
+              chars[i * 2 + 1] = []
             }
           }
         }
@@ -264,21 +269,8 @@ const ansi = {
         continue
       }
 
-      // debug
-      /*
-      if (text[charI] === '.') {
-        console.log(
-          `#1-char "${text[charI]}" at ` +
-          `(${cursorRow},${cursorCol}):${getCursorIndex()} ` +
-          ` attr:[${attributes.join(';')}]`
-        )
-      }
-      */
-
-      chars[getCursorIndex()] = {
-        char: text[charI],
-        attributes: attributes.slice()
-      }
+      chars[cursorIndex * 2] = text[charI]
+      chars[cursorIndex * 2 + 1] = attributes.slice()
 
       cursorCol++
 
@@ -301,87 +293,98 @@ const ansi = {
     const differences = []
 
     if (oldChars === null) {
-      differences.push({i: 0, chars: [...newChars]})
+      differences.push(0)
+      differences.push(newChars.slice())
     } else {
-      const charsEqual = (oldChar, newChar) => {
-        // TODO: Check attributes.
+      let curChars = null
 
-        if (oldChar.char !== newChar.char) {
-          return false
-        }
+      for (let i = 0; i < numChars; i++) {
+        const oldChar = oldChars[i * 2]
+        const newChar = newChars[i * 2]
 
-        let oldAttrs = oldChar.attributes.slice()
-        let newAttrs = newChar.attributes.slice()
+        let charsEqual = true
 
-        while (newAttrs.length) {
-          const attr = newAttrs.shift()
-          if (oldAttrs.includes(attr)) {
-            oldAttrs.splice(oldAttrs.indexOf(attr), 1)
-          } else {
-            return false
-          }
-        }
+        if (oldChars[i * 2] !== newChars[i * 2]) {
+          charsEqual = false
+        } else {
+          let oldAttrs = oldChars[i * 2 + 1].slice()
+          let newAttrs = newChars[i * 2 + 1].slice()
 
-        oldAttrs = oldChar.attributes.slice()
-        newAttrs = newChar.attributes.slice()
+          while (newAttrs.length) {
+            const attr = newAttrs.shift()
+            if (oldAttrs.includes(attr)) {
+              oldAttrs.splice(oldAttrs.indexOf(attr), 1)
+            } else {
+              charsEqual = false
+              break
+            }
+          }
 
-        while (oldAttrs.length) {
-          const attr = oldAttrs.shift()
-          if (newAttrs.includes(attr)) {
-            newAttrs.splice(newAttrs.indexOf(attr), 1)
-          } else {
-            return false
+          if (charsEqual) {
+            oldAttrs = oldChars[i * 2 + 1].slice()
+            newAttrs = newChars[i * 2 + 1].slice()
+
+            while (oldAttrs.length) {
+              const attr = oldAttrs.shift()
+              if (newAttrs.includes(attr)) {
+                newAttrs.splice(newAttrs.indexOf(attr), 1)
+              } else {
+                charsEqual = false
+                break
+              }
+            }
           }
         }
 
-        return true
-      }
-
-      let curDiff = null
-
-      for (let i = 0; i < chars.length; i++) {
-        const oldChar = oldChars[i]
-        const newChar = newChars[i]
-
         // TODO: Some sort of "distance" before we should clear curDiff?
         // It may take *less* characters if this diff and the next are merged
         // (entering a single character is smaller than the length of the code
         // used to move past that character). Probably not very significant of
         // an impact, though.
-        if (charsEqual(oldChar, newChar)) {
-          curDiff = null
+        if (charsEqual) {
+          curChars = null
         } else {
-          if (curDiff === null) {
-            curDiff = {i, chars: []}
-            differences.push(curDiff)
+          if (curChars === null) {
+            curChars = []
+            differences.push(i, curChars)
           }
 
-          curDiff.chars.push(newChar)
+          const curAttrs = newChars[i * 2 + 1]
+          curChars.push(newChar, curAttrs)
         }
       }
     }
 
     // Character concatenation -----------
 
+    /*
     let lastChar = oldLastChar || {
       char: '',
       attributes: []
     }
+    */
+
+    let lastCharAttributes = []
 
     const result = []
 
-    for (const diff of differences) {
-      const col = diff.i % scrCols
-      const row = (diff.i - col) / scrCols
+    for (let parse = 0; parse < differences.length; parse += 2) {
+      const i = differences[parse]
+      const chars = differences[parse + 1]
+
+      const col = i % scrCols
+      const row = (i - col) / scrCols
       result.push(ansi.moveCursor(row, col))
 
-      for (const char of diff.chars) {
+      for (let charI = 0; charI < chars.length / 2; charI++) {
+        const charAttributes = chars[charI * 2 + 1]
+
         const newAttributes = (
-          char.attributes.filter(attr => !(lastChar.attributes.includes(attr)))
+          charAttributes.filter(attr => !(lastCharAttributes.includes(attr)))
         )
 
         const removedAttributes = (
-          lastChar.attributes.filter(attr => !(char.attributes.includes(attr)))
+          lastCharAttributes.filter(attr => !(charAttributes.includes(attr)))
         )
 
         // The only way to practically remove any character attribute is to
@@ -389,14 +392,14 @@ const ansi = {
         // If we do that, there's no need to add new attributes.
         if (removedAttributes.length) {
           result.push(ansi.resetAttributes())
-          result.push(`${ESC}[${char.attributes.join(';')}m`)
+          result.push(`${ESC}[${charAttributes.join(';')}m`)
         } else if (newAttributes.length) {
           result.push(`${ESC}[${newAttributes.join(';')}m`)
         }
 
-        result.push(char.char)
+        result.push(chars[charI * 2])
 
-        lastChar = char
+        lastCharAttributes = charAttributes
       }
     }
 
@@ -419,7 +422,7 @@ const ansi = {
       oldCursorRow: cursorRow,
       oldCursorCol: cursorCol,
       oldShowCursor: showCursor,
-      oldLastChar: Object.assign({}, lastChar),
+      oldLastCharAttributes: lastCharAttributes.slice(),
       screen: result.join('')
     }
   }