linenoise
這個 function 是整個程式主要的 API,一開始會檢查是否是 tty
,然後在看終端機是否有支援,有支援了話就會呼叫 linenoiseRaw
。
linenoiseRaw
這個函式會先呼叫 enableRawMode
,然後呼叫 linenoiseEdit
進入編輯模式,最後會呼叫 disableRawMode
。
enableRawMode
這邊要先了解到 Terminal mode,不同的模式下面會決定終端機要如何轉換(interpreted)字元。
一般的終端機是 line-based system,輸入的字元會被放到 buffer 直到收到 carriage return (Enter or Return),這個叫做 cooked,會允許特殊字元被先處理,可以看 stty(1) 像是 Ctrl+D, Ctrl+S, Backspace 這些都是,終端機的驅動會先 "cooks" 處理這些字元。
再來看看 termios
這個結構,是用來設定終端機的屬性,這邊會先用 tcgetattr
拿到原本終端機的屬性並且保存到 orig_termios
,最後在 disableRawMode
的時候透過 tcsetattr
把 orig_termios
設定回去。
linenoiseEdit
這邊會用到一個結構 linenoiseState
用來記錄下一些在命令列編輯時的特定資訊,一開始會先對這些結構初始化。
這個主要就是用在編輯命令列上的文字,可以看到第 5 行這邊是先把 prompt 的文字寫到終端機,也就是 hello>
。接著進入迴圈,每次迴圈第 10 行都會去 read
1 個字元放到變數 c
,下面就會對這個字元做對應的動作。
第 12 行先判斷 c == 9
在 ASCII 當中 9 代表 tab
,所以這邊如果是使用者輸入 tab,如果有註冊 autocomplete 的 callback function 就會執行 auto complete,呼叫 completeLine
。
completeLine
做完後會回傳最後一個收到的字元,再往下看到 switch
,會對應不同的輸入進行不同的行為,如果是一般的輸入字元的話會呼叫 linenoiseEditInsert
,插入一個字元到游標目前位置,其他對應的動作如下。
linenoiseEditBackspace
linenoiseEditDelete
linenoiseEditMoveLeft
linenoiseEditMoveRight
linenoiseEditMoveHome
linenoiseEditMoveEnd
linenoiseEditHistoryNext
linenoiseClearScreen
completeLine
第 6 行會呼叫 completionCallback
也就是一開始註冊的 callback function,可以先看到 example.c
第 45 行,有先透過 linenoiseSetCompletionCallback
先註冊好 callback function completion
,所以在這邊呼叫 completionCallback
就是呼叫 completion
。
第 7 行開始判斷 lc
是否為 0,如果是 0 代表現在輸入的這個 buffer 沒有 match 到任何 completion 的規則,就會呼叫 linenoiseBeep
。
如果有的話就會進入到 while
迴圈當中,這邊就會根據輸入的字元做對應的動作,按一次 tab
就會去 lc.cvec
裡面的字串做 auto complete,如果還有就會繼續補下去,lc.cvec
裡面的全部做完會回到 i
會回到 0,就會是原本的輸入。
如果輸入是 escape
或是其他字元,while
迴圈就會停止,並且把最後收到的字元 return
。
linenoiseAddCompletion
這個函式式用來建立 auto complete 的內容,會透過剛剛在 completion
當中呼叫給定的字串來建立。這邊用到一個結構 linenoiseCompletions
,變數 cvec
是指標的指標,用來把拿到的字串存起來,所以這邊開頭為 h
的輸入就會有 "hello"、"hello there" 兩個 auto complete。
refreshLine
這個是用來更新目前的 buffer 內容、游標位置到正確的位置。
有兩種模式 MultiLine
、SingleLine
,可以在一開始的時候指定,如果沒有指定了話會是 SingleLine mode,所以這邊就會進到 refreshSingleLine
。
這邊有用到一個結構 abuf
是用來 append buffer,目的是要一次更新完到終端機上,避免文字在螢幕上閃爍。
這邊還要先了解到 ANSI escape code 是一種制定的標準,還可以搭配 VT100、ANSI 碼說明。用來控制終端機的游標位置、顏色等,大多數已 ESC
跳脫字元和 [
字元開始,後面跟著參數,可以看 wiki 的說明如下。
The general format for an ANSI-compliant escape sequence is defined by ANSI X3.41 (equivalent to ECMA-35 or ISO/IEC 2022). The ESC (27 / hex 0x1B / oct 033) is followed by zero or more intermediate "I" bytes between hex 0x20 and 0x2F inclusive, followed by a final "F" byte between 0x30 and 0x7E inclusive
一般的格式是由 ESC
(二進位 27 十六進為 0x1B) 開頭,第二個位元則是 0x40–0x5F (ASCII @A–Z[\]^_
) 範圍內的字元,最後一個則是範圍 0x30-0x7E。
這邊一開始先用 \r
carriage return 把游標位置回到最左邊。
這邊是把 prompt 和原本的 buffer 內容寫回去。
這邊的 0K
對應到的動作是 EL (Erase in Line),n 為 0 的時候會從游標的位置開始清除到最後行尾。
這邊則是先把游標先移到最左,這邊的動作是 CUF (Cursor Forward),是把游標向右移動 n 個,然後 %d
這邊是吃 pos+plen
,也就是從最左邊移動到原本的游標位置。
最後一併 write 到終端機輸出。
refreshShowHints
這邊會呼叫到 hintsCallback
,這個 callback function 也是在一開始就指定的,所以對應 example.c
的 function 就是 hints
,會設定好顏色、粗細,回傳 hint 的字串。
linenoiseHistory
相關這是為了記錄下每次出入的指令,作法是用指標的指標 history
記錄下來每次輸入的字串。
最一開始啟動的時候會呼叫 linenoiseHistoryLoad
,去從實體檔案當中將內容讀出來到記憶體,
之後在每次輸入完指令都會呼叫 linenoiseHistoryAdd
新增一筆,然後用 linenoiseHistorySave
存到實體檔案中。
然後透過 linenoiseEditHistoryNext
把當前的輸入改成上一個或下一個 history
的內容。