hyper-snippet
Version:
Help you create code snippet for your hyper terminal
420 lines (347 loc) • 9.38 kB
JavaScript
const MOVE_LEFT = "\x1b[D"
const MOVE_RIGHT = "\x1b[C"
const MOVE_UP = "\x1b[A"
const MOVE_DOWN = "\x1b[B"
const MOVE_LINE_START = "\x1bOH"
const MOVE_LINE_START2 = "\x01"
const MOVE_LINE_END = "\x1bOF"
const MOVE_LINE_END2 = "\x05"
const MOVE_WORD_BEFORE = "\x1bb"
const MOVE_WORD_AFTER = "\x1bf"
const PASTE_START = "\x1b[200~"
const PASTE_END = "\x1b[201~"
const NEWLINE = "\r"
const NEWLINE2 = "\n"
const ERASE_TO_END = "\x0b"
const ERASE_UNITIL_PREVIOUS_WHITESPACE = "\x17"
const ERASE_LINE = "\x15"
const CLEAR_SCREEN = "\x1b[2J"
const DELETE_BEFORE = "\x7f"
const DELETE_BEFORE2 = "\b"
const DELETE_AFTER = "\x04"
const TAB = "\t"
const EXIST_C = "\x03"
const STOP_CHARS = new Set([":", " ", "|", "\\", "@", "`"])
const RESET_MOVE_PATTEN = /([\x1b]\[(\d+)?[CD])|[\b]/g
const REMOVE_USELESS_PTY_PATTERN = /([\x1b](\[(\d+)?[Pm])|\x07)/g
function findNextBreakPoint(chars, startCurosr) {
if (startCurosr == chars.length) {
return startCurosr
}
let isFindNormal = STOP_CHARS.has(chars[startCurosr])
for (let i = startCurosr; i < chars.length; i++) {
const currentChar = chars[i]
if (isFindNormal) {
if (!STOP_CHARS.has(currentChar)) {
return i
}
} else {
if (STOP_CHARS.has(currentChar)) {
isFindNormal = true
continue
}
}
}
return chars.length
}
function findPreviousBreakPoint(chars, startCurosr) {
if (startCurosr == 0) {
return startCurosr
}
if (!STOP_CHARS.has(chars[startCurosr]) && STOP_CHARS.has(chars[startCurosr - 1])) {
startCurosr--
}
let isFindNormal = !STOP_CHARS.has(chars[startCurosr])
for (let i = startCurosr; i > -1; i--) {
const currentChar = chars[i]
if (isFindNormal) {
if (STOP_CHARS.has(currentChar)) {
return i + 1
}
} else {
if (!STOP_CHARS.has(currentChar)) {
isFindNormal = true
continue
}
}
}
return 0
}
const PTY_SKIP_CHARS = new Set(["\r"])
class TerminalSimulator {
constructor() {
this.cursor = 0
this.cache = []
this.isFirstTab = false
this.isDisableTabCheck = false
this.isUpAndDown = false
}
writePTY(_data) {
// console.log(_data);
let data = _data.replace(REMOVE_USELESS_PTY_PATTERN,"")
if (this.isFirstTab && data.indexOf("\n") == -1) {
if(data !== ' '){
this.write(data)
}
this.isFirstTab = false
} else if (this.isUpAndDown) {
// this.reset()
const sequences = this.splitSequence(data)
if(sequences[sequences.length - 1] != ERASE_TO_END){
sequences.push(ERASE_TO_END)
}
// console.log(sequences);
for(let item of sequences){
this.write(item)
}
this.isUpAndDown = false
}
}
splitSequence(text){
let cursor = 0
let result = []
while(cursor < text.length){
const char = text[cursor]
if(char === "\x1b"){
// move 1 step
if(cursor + 2 < text.length && text[cursor+1] === "[" && (text[cursor+2] === "C" || text[cursor+2] === "D")){
result.push(text.slice(cursor,cursor+3))
cursor += 3
}
// move multi step
else if(cursor + 3 < text.length && text[cursor+1] === "[" && (text[cursor+3] === "C" || text[cursor+3] === "D")){
const count = Number(text[cursor+2])
const MOVE_LITERAL = "\x1b[" + text[cursor+3]
for (let i = 0; i < count; i++) {
result.push(MOVE_LITERAL)
}
cursor += 4
}
// ignore up and down
else if(cursor + 2 < text.length && text[cursor+1] === "[" && (text[cursor+2] === "A" || text[cursor+2] === "B")){
cursor += 3
}
// erase to end
else if(cursor + 2 < text.length && text[cursor+1] === "[" && (text[cursor+2] === "K")){
result.push(ERASE_TO_END)
cursor += 3
}
else {
cursor += 1
}
}
else {
if(!PTY_SKIP_CHARS.has(char)){
result.push(char)
}
cursor+=1
}
}
return result
}
write(data) {
if (data.length === 0) {
return
}
switch (data) {
case MOVE_UP:
case MOVE_DOWN:
this.isUpAndDown = true
break
case MOVE_LEFT:
if (this.cursor > 0) {
this.cursor -= 1
}
break
case MOVE_RIGHT:
if (this.cursor < this.cache.length) {
this.cursor += 1
}
break
case ERASE_UNITIL_PREVIOUS_WHITESPACE:
let toDeleteLength = 0
while (this.cursor > 0) {
this.cursor--
if(this.cache[this.cursor] === ' '){
toDeleteLength++
}
else {
break
}
}
while (this.cursor >= 0) {
if(this.cache[this.cursor] !== ' '){
toDeleteLength++
this.cursor--
}
else {
break
}
}
this.cursor++
this.cache.splice(this.cursor,toDeleteLength)
break
case ERASE_TO_END:
this.cache.splice(this.cursor, this.cache.length - this.cursor)
break
case MOVE_LINE_START:
case MOVE_LINE_START2:
if (this.cursor > 0) {
this.cursor = 0
}
break
case MOVE_LINE_END:
case MOVE_LINE_END2:
if (this.cursor < this.cache.length) {
this.cursor = this.cache.length
}
break
case NEWLINE:
case NEWLINE2:
case ERASE_LINE:
this.reset()
break
case DELETE_BEFORE:
case DELETE_BEFORE2:
if (this.cursor > 0) {
this.cursor--
this.cache.splice(this.cursor, 1)
}
break
case DELETE_AFTER:
if (this.cursor < this.cache.length) {
this.cache.splice(this.cursor, 1)
}
break
case MOVE_WORD_BEFORE:
this.cursor = findPreviousBreakPoint(this.cache, this.cursor)
break
case MOVE_WORD_AFTER:
this.cursor = findNextBreakPoint(this.cache, this.cursor)
break
case EXIST_C:
this.reset()
break
default:
if (data === TAB) {
if(this.isDisableTabCheck){
console.log(this.isDisableTabCheck);
return
}
if (this.isFirstTab) {
this.isFirstTab = false
} else {
this.isFirstTab = true
}
return
}
if (data.indexOf(PASTE_START) !== -1) {
data = data.substr(6, data.length - 12)
} else if (data.indexOf(MOVE_RIGHT) !== -1) {
const segments = data.split(MOVE_RIGHT)
for (let i = 0; i < segments.length; i++) {
this.write(segments[i])
if (i !== 0) {
this.write(MOVE_RIGHT)
}
}
return
} else if (data.indexOf(MOVE_LEFT) !== -1) {
const segments = data.split(MOVE_LEFT)
// console.log(segments);
for (let i = 0; i < segments.length; i++) {
this.write(segments[i])
if (i !== 0) {
this.write(MOVE_LEFT)
}
}
return
}
for (let char of data) {
this.cache.splice(this.cursor, 0, char)
this.cursor++
}
break
}
}
reset() {
this.cache = []
this.cursor = 0
}
get text() {
return this.cache.join("")
}
}
const MATCH_COMMAND_PATTERN = /^[^ ]+/
const MATCH_SUBCOMMAND_PATTERN = /^[^ ]+\s+([^ ]+)/
const MATCH_LASTWORD_PATTERN = /[^ ]+$/
const TEMPLATE_END_TOKEN = "#END#"
class CommandLineWrapper{
constructor(terminalSimulator,sendUserData) {
this.terminalSimulator = terminalSimulator
this.sendUserData = sendUserData
}
get cmd(){
const match = MATCH_COMMAND_PATTERN.exec(this.terminalSimulator.text)
if(match){
return match[0]
}
else {
return ""
}
}
get subcmd(){
const match = MATCH_SUBCOMMAND_PATTERN.exec(this.terminalSimulator.text)
if(match){
return match[1]
}
else {
return ""
}
}
get lastWord(){
const match = MATCH_LASTWORD_PATTERN.exec(this.terminalSimulator.text.slice(this.cmd.length))
if(match){
return match[0]
}
else {
return ""
}
}
get cursorLastWord(){
const match = MATCH_LASTWORD_PATTERN.exec(this.terminalSimulator.text.slice(this.cmd.length,this.terminalSimulator.cursor))
if(match){
return match[0]
}
else {
return ""
}
}
replaceCursorLastWord(text){
const toDeleteLength = this.cursorLastWord.length
for(let i=0;i < toDeleteLength;i++){
this.append(DELETE_BEFORE)
}
this.append(text)
}
append(text) {
const texts = text.split(TEMPLATE_END_TOKEN)
if(texts.length === 1){
this.sendUserData(text)
}
else {
const result = [texts[0],texts[1]]
for (let i = 0; i < texts[1].length; i++) {
result.push(`\x1b[D`)
}
this.sendUserData(result.join(""))
}
}
clear(){
this.sendUserData(ERASE_LINE)
}
}
exports.TerminalSimulator = TerminalSimulator
exports.CommandLineWrapper = CommandLineWrapper
// console.log(findNextBreakPoint("012:\\567",2))
// console.log(findPreviousBreakPoint("012:\\567",2))