Node 實現 REPL 自動補全功能

之前沒看過 Node 的 repl 模塊, 嘗試了一下
http://nodejs.org/api/repl.html
repl 模塊主要是封裝了一個執行 Node 的 context 從命令行傳入數據訪問
我看中的是自動補全的功能, 好比 CoffeeScript 命令行工具藉此實現了 Tab 補全html

按文檔的代碼例子跑了之後, 發現補全功能其實只是 Node 默認的 JS 變量補全
若是是要自定義補全的內容, 就不能經過這個 API
網上給出的訪方案是經過重寫 .complete 方法來達到自定義的目的
http://stackoverflow.com/a/7096913/883571node

因而我寫了一個例子, 測試在每次 Tab 時是否能本身打印環境當中的數據git

repl = require 'repl'
util = require 'util'

shell = repl.start('cirru> ')

y = 0

shell.complete = (x, callback) ->
  y += 1
  console.log y
  util.print 'cirru> '
  callback([])

shell.context.shell = shell

網上再找了之後, 我意識到 readline 模塊能夠直接完成這樣的事情
因而有了下面的例子, 主要是 completer 方法的寫法:github

util = require 'util'
readline = require 'readline'

candidates = ['acd', 'abc', 'abd']

completer = (line) ->
  matchLastWord = line.match(/[\w-:\/]+$/)
  return [candidates, ''] unless matchLastWord?
  lastWord = matchLastWord[0]
  wordsBefore = line[...(-lastWord.length)]
  hits = candidates
  .filter (word) ->
    (word.indexOf lastWord) is 0
  if hits.length > 0
    [hits, lastWord]
  else
    [[], null]

shell = readline.createInterface
  input: process.stdin
  output: process.stdout
  completer: completer

do repl = ->
  shell.question 'cirru> ', (anwser) ->
    repl()

完整的代碼我用在了 Cirru-Shell 的實現當中:
https://github.com/Cirru/cirru-shell/blob/master/coffee/shell.coffeeshell

completer 的返回值的格式是 [[substr1, substr2, ...], originalsubstring]
舉一個例子, 命令行裏內容是 c a b d, 光標前面的內容是 c a
好比 completer 的返回內容是 [['abc', 'acd'], a], 命令行裏是
那麼就會根據 a, 在按 <tab> 時提示這兩個選項.
若是光標前面是 c ab, 那麼返回的內容對應 [[abc], 'ab'],
那麼這時候 Node 就會對原來的內容進行替換, 變成 c abc
而光標之後的內容保持不變.api

至於其中查找匹配的選項的邏輯, 徹底能夠本身去定製,
能夠參考我寫在 Cirru-Shell 裏的例子less


看了一下 readline 模塊的源碼, 中間有一部分紅能夠看懂的
https://github.com/joyent/node/blob/master/lib/readline.js
好比 self.cursor self._moveCursor self.line 能猜到意思
以此是, 光標位置, 光標移動距離, 當前行的內容
另外 self.question 方法的劣跡也清晰多了, 僅僅是 self.emit 'line'工具

因而稍微 hack 了一些, 在 <tab> 時我實現了對括號自動補全
補全括號的光標移動順序我目前沒法改動 completer, 經過事件解決:測試

completer = (line) ->
  if line[line.length - 1] is '('
    # console.log shell.line, shell.cursor
    if shell.line[shell.cursor] isnt ')'
      setTimeout ->
        shell._moveCursor -1
      , 0
      return [['()'], '(']
    else
      return [[], null]

這是我看到的包含了私有的 API 的一份列表:
https://gist.github.com/viatropos/3843973
參考這裏, 相似還有 history 之類的接口, 對照源碼對歷史進行存儲ui

相關文章
相關標籤/搜索