]> git.r.bdr.sh - rbdr/dotfiles/blob - atom/packages/ex-mode/lib/command.coffee
115b273c54ae52e34f96cb26db320e792f655568
[rbdr/dotfiles] / atom / packages / ex-mode / lib / command.coffee
1 ExViewModel = require './ex-view-model'
2 Ex = require './ex'
3 Find = require './find'
4 CommandError = require './command-error'
5
6 class Command
7 constructor: (@editor, @exState) ->
8 @viewModel = new ExViewModel(@)
9
10 parseAddr: (str, curPos) ->
11 if str is '.'
12 addr = curPos.row
13 else if str is '$'
14 # Lines are 0-indexed in Atom, but 1-indexed in vim.
15 addr = @editor.getBuffer().lines.length - 1
16 else if str[0] in ["+", "-"]
17 addr = curPos.row + @parseOffset(str)
18 else if not isNaN(str)
19 addr = parseInt(str) - 1
20 else if str[0] is "'" # Parse Mark...
21 unless @vimState?
22 throw new CommandError("Couldn't get access to vim-mode.")
23 mark = @vimState.marks[str[1]]
24 unless mark?
25 throw new CommandError("Mark #{str} not set.")
26 addr = mark.bufferMarker.range.end.row
27 else if str[0] is "/"
28 addr = Find.findNextInBuffer(@editor.buffer, curPos, str[1...-1])
29 unless addr?
30 throw new CommandError("Pattern not found: #{str[1...-1]}")
31 else if str[0] is "?"
32 addr = Find.findPreviousInBuffer(@editor.buffer, curPos, str[1...-1])
33 unless addr?
34 throw new CommandError("Pattern not found: #{str[1...-1]}")
35
36 return addr
37
38 parseOffset: (str) ->
39 if str.length is 0
40 return 0
41 if str.length is 1
42 o = 1
43 else
44 o = parseInt(str[1..])
45 if str[0] is '+'
46 return o
47 else
48 return -o
49
50 execute: (input) ->
51 @vimState = @exState.globalExState.vim?.getEditorState(@editor)
52 # Command line parsing (mostly) following the rules at
53 # http://pubs.opengroup.org/onlinepubs/9699919799/utilities
54 # /ex.html#tag_20_40_13_03
55
56 # Steps 1/2: Leading blanks and colons are ignored.
57 cl = input.characters
58 cl = cl.replace(/^(:|\s)*/, '')
59 return unless cl.length > 0
60
61 # Step 3: If the first character is a ", ignore the rest of the line
62 if cl[0] is '"'
63 return
64
65 # Step 4: Address parsing
66 lastLine = @editor.getBuffer().lines.length - 1
67 if cl[0] is '%'
68 range = [0, lastLine]
69 cl = cl[1..]
70 else
71 addrPattern = ///^
72 (?: # First address
73 (
74 \.| # Current line
75 \$| # Last line
76 \d+| # n-th line
77 '[\[\]<>'`"^.(){}a-zA-Z]| # Marks
78 /.*?[^\\]/| # Regex
79 \?.*?[^\\]\?| # Backwards search
80 [+-]\d* # Current line +/- a number of lines
81 )((?:\s*[+-]\d*)*) # Line offset
82 )?
83 (?:, # Second address
84 ( # Same as first address
85 \.|
86 \$|
87 \d+|
88 '[\[\]<>'`"^.(){}a-zA-Z]|
89 /.*?[^\\]/|
90 \?.*?[^\\]\?|
91 [+-]\d*
92 )((?:\s*[+-]\d*)*)
93 )?
94 ///
95
96 [match, addr1, off1, addr2, off2] = cl.match(addrPattern)
97
98 curPos = @editor.getCursorBufferPosition()
99
100 if addr1?
101 address1 = @parseAddr(addr1, curPos)
102 else
103 # If no addr1 is given (,+3), assume it is '.'
104 address1 = curPos.row
105 if off1?
106 address1 += @parseOffset(off1)
107
108 address1 = 0 if address1 is -1
109
110 if address1 < 0 or address1 > lastLine
111 throw new CommandError('Invalid range')
112
113 if addr2?
114 address2 = @parseAddr(addr2, curPos)
115 if off2?
116 address2 += @parseOffset(off2)
117
118 if address2 < 0 or address2 > lastLine
119 throw new CommandError('Invalid range')
120
121 if address2 < address1
122 throw new CommandError('Backwards range given')
123
124 range = [address1, if address2? then address2 else address1]
125 cl = cl[match?.length..]
126
127 # Step 5: Leading blanks are ignored
128 cl = cl.trimLeft()
129
130 # Step 6a: If no command is specified, go to the last specified address
131 if cl.length is 0
132 @editor.setCursorBufferPosition([range[1], 0])
133 return
134
135 # Ignore steps 6b and 6c since they only make sense for print commands and
136 # print doesn't make sense
137
138 # Ignore step 7a since flags are only useful for print
139
140 # Step 7b: :k<valid mark> is equal to :mark <valid mark> - only a-zA-Z is
141 # in vim-mode for now
142 if cl.length is 2 and cl[0] is 'k' and /[a-z]/i.test(cl[1])
143 command = 'mark'
144 args = cl[1]
145 else if not /[a-z]/i.test(cl[0])
146 command = cl[0]
147 args = cl[1..]
148 else
149 [m, command, args] = cl.match(/^(\w+)(.*)/)
150
151 # If the command matches an existing one exactly, execute that one
152 if (func = Ex.singleton()[command])?
153 func(range, args)
154 else
155 # Step 8: Match command against existing commands
156 matching = (name for name, val of Ex.singleton() when \
157 name.indexOf(command) is 0)
158
159 matching.sort()
160
161 command = matching[0]
162
163 func = Ex.singleton()[command]
164 if func?
165 func(range, args)
166 else
167 throw new CommandError("Not an editor command: #{input.characters}")
168
169 module.exports = Command