]>
Commit | Line | Data |
---|---|---|
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 |