Blame view

node_modules/coa/src/completion.coffee 5.39 KB
aaac7fed   liuqimichale   add
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
  ###*
  Most of the code adopted from the npm package shell completion code.
  See https://github.com/isaacs/npm/blob/master/lib/completion.js
  ###
  
  Q = require 'q'
  escape = require('./shell').escape
  unescape = require('./shell').unescape
  
  module.exports = ->
      @title('Shell completion')
          .helpful()
          .arg()
              .name('raw')
              .title('Completion words')
              .arr()
              .end()
          .act (opts, args) ->
              if process.platform == 'win32'
                  e = new Error 'shell completion not supported on windows'
                  e.code = 'ENOTSUP'
                  e.errno = require('constants').ENOTSUP
                  return @reject(e)
  
              # if the COMP_* isn't in the env, then just dump the script
              if !process.env.COMP_CWORD? or !process.env.COMP_LINE? or !process.env.COMP_POINT?
                  return dumpScript(@_cmd._name)
  
              console.error 'COMP_LINE:  %s', process.env.COMP_LINE
              console.error 'COMP_CWORD: %s', process.env.COMP_CWORD
              console.error 'COMP_POINT: %s', process.env.COMP_POINT
              console.error 'args: %j', args.raw
  
              # completion opts
              opts = getOpts args.raw
  
              # cmd
              { cmd, argv } = @_cmd._parseCmd opts.partialWords
              Q.when complete(cmd, opts), (compls) ->
                  console.error 'filtered: %j', compls
                  console.log compls.map(escape).join('\n')
  
  
  dumpScript = (name) ->
      fs = require 'fs'
      path = require 'path'
      defer = Q.defer()
  
      fs.readFile path.resolve(__dirname, 'completion.sh'), 'utf8', (err, d) ->
          if err then return defer.reject err
          d = d.replace(/{{cmd}}/g, path.basename name).replace(/^\#\!.*?\n/, '')
  
          onError = (err) ->
              # Darwin is a real dick sometimes.
              #
              # This is necessary because the "source" or "." program in
              # bash on OS X closes its file argument before reading
              # from it, meaning that you get exactly 1 write, which will
              # work most of the time, and will always raise an EPIPE.
              #
              # Really, one should not be tossing away EPIPE errors, or any
              # errors, so casually. But, without this, `. <(cmd completion)`
              # can never ever work on OS X.
              if err.errno == require('constants').EPIPE
                  process.stdout.removeListener 'error', onError
                  defer.resolve()
              else
                  defer.reject(err)
  
          process.stdout.on 'error', onError
          process.stdout.write d, -> defer.resolve()
  
      defer.promise
  
  
  getOpts = (argv) ->
      # get the partial line and partial word, if the point isn't at the end
      # ie, tabbing at: cmd foo b|ar
      line = process.env.COMP_LINE
      w = +process.env.COMP_CWORD
      point = +process.env.COMP_POINT
      words = argv.map unescape
      word = words[w]
      partialLine = line.substr 0, point
      partialWords = words.slice 0, w
  
      # figure out where in that last word the point is
      partialWord = argv[w] or ''
      i = partialWord.length
      while partialWord.substr(0, i) isnt partialLine.substr(-1 * i) and i > 0
          i--
      partialWord = unescape partialWord.substr 0, i
      if partialWord then partialWords.push partialWord
  
      {
          line: line
          w: w
          point: point
          words: words
          word: word
          partialLine: partialLine
          partialWords: partialWords
          partialWord: partialWord
      }
  
  
  complete = (cmd, opts) ->
      compls = []
  
      # complete on cmds
      if opts.partialWord.indexOf('-')
          compls = Object.keys(cmd._cmdsByName)
          # Complete on required opts without '-' in last partial word
          # (if required not already specified)
          #
          # Commented out because of uselessness:
          # -b, --block suggest results in '-' on cmd line;
          # next completion suggest all options, because of '-'
          #.concat Object.keys(cmd._optsByKey).filter (v) -> cmd._optsByKey[v]._req
      else
          # complete on opt values: --opt=| case
          if m = opts.partialWord.match /^(--\w[\w-_]*)=(.*)$/
              optWord = m[1]
              optPrefix = optWord + '='
          else
              # complete on opts
              # don't complete on opts in case of --opt=val completion
              # TODO: don't complete on opts in case of unknown arg after commands
              # TODO: complete only on opts with arr() or not already used
              # TODO: complete only on full opts?
              compls = Object.keys cmd._optsByKey
  
      # complete on opt values: next arg case
      if not (o = opts.partialWords[opts.w - 1]).indexOf '-'
          optWord = o
  
      # complete on opt values: completion
      if optWord and opt = cmd._optsByKey[optWord]
          if not opt._flag and opt._comp
              compls = Q.join compls, Q.when opt._comp(opts), (c, o) ->
                  c.concat o.map (v) -> (optPrefix or '') + v
  
      # TODO: complete on args values (context aware, custom completion?)
  
      # custom completion on cmds
      if cmd._comp
          compls = Q.join compls, Q.when(cmd._comp(opts)), (c, o) ->
              c.concat o
  
      # TODO: context aware custom completion on cmds, opts and args
      # (can depend on already entered values, especially options)
  
      Q.when compls, (compls) ->
          console.error 'partialWord: %s', opts.partialWord
          console.error 'compls: %j', compls
          compls.filter (c) -> c.indexOf(opts.partialWord) is 0