]>
Commit | Line | Data |
---|---|---|
24c7594d BB |
1 | path = require 'path' |
2 | CommandError = require './command-error' | |
455f099b BB |
3 | fs = require 'fs-plus' |
4 | VimOption = require './vim-option' | |
24c7594d BB |
5 | |
6 | trySave = (func) -> | |
7 | deferred = Promise.defer() | |
8 | ||
9 | try | |
10 | func() | |
11 | deferred.resolve() | |
12 | catch error | |
13 | if error.message.endsWith('is a directory') | |
14 | atom.notifications.addWarning("Unable to save file: #{error.message}") | |
15 | else if error.path? | |
16 | if error.code is 'EACCES' | |
17 | atom.notifications | |
18 | .addWarning("Unable to save file: Permission denied '#{error.path}'") | |
19 | else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST'] | |
20 | atom.notifications.addWarning("Unable to save file '#{error.path}'", | |
21 | detail: error.message) | |
22 | else if error.code is 'EROFS' | |
23 | atom.notifications.addWarning( | |
24 | "Unable to save file: Read-only file system '#{error.path}'") | |
25 | else if (errorMatch = | |
26 | /ENOTDIR, not a directory '([^']+)'/.exec(error.message)) | |
27 | fileName = errorMatch[1] | |
28 | atom.notifications.addWarning("Unable to save file: A directory in the "+ | |
29 | "path '#{fileName}' could not be written to") | |
30 | else | |
31 | throw error | |
32 | ||
33 | deferred.promise | |
34 | ||
455f099b BB |
35 | saveAs = (filePath) -> |
36 | editor = atom.workspace.getActiveTextEditor() | |
37 | fs.writeFileSync(filePath, editor.getText()) | |
38 | ||
24c7594d | 39 | getFullPath = (filePath) -> |
455f099b BB |
40 | filePath = fs.normalize(filePath) |
41 | ||
42 | if path.isAbsolute(filePath) | |
43 | filePath | |
44 | else if atom.project.getPaths().length == 0 | |
45 | path.join(fs.normalize('~'), filePath) | |
46 | else | |
47 | path.join(atom.project.getPaths()[0], filePath) | |
48 | ||
49 | replaceGroups = (groups, string) -> | |
50 | replaced = '' | |
51 | escaped = false | |
52 | while (char = string[0])? | |
53 | string = string[1..] | |
54 | if char is '\\' and not escaped | |
55 | escaped = true | |
56 | else if /\d/.test(char) and escaped | |
57 | escaped = false | |
58 | group = groups[parseInt(char)] | |
59 | group ?= '' | |
60 | replaced += group | |
61 | else | |
62 | escaped = false | |
63 | replaced += char | |
64 | ||
65 | replaced | |
24c7594d BB |
66 | |
67 | class Ex | |
68 | @singleton: => | |
69 | @ex ||= new Ex | |
70 | ||
71 | @registerCommand: (name, func) => | |
72 | @singleton()[name] = func | |
73 | ||
74 | quit: -> | |
75 | atom.workspace.getActivePane().destroyActiveItem() | |
76 | ||
77 | q: => @quit() | |
78 | ||
455f099b BB |
79 | tabedit: (range, args) => |
80 | if args.trim() isnt '' | |
81 | @edit(range, args) | |
24c7594d | 82 | else |
455f099b | 83 | @tabnew(range, args) |
24c7594d BB |
84 | |
85 | tabe: (args...) => @tabedit(args...) | |
86 | ||
455f099b BB |
87 | tabnew: (range, args) => |
88 | if args.trim() is '' | |
89 | atom.workspace.open() | |
90 | else | |
91 | @tabedit(range, args) | |
24c7594d | 92 | |
455f099b | 93 | tabclose: (args...) => @quit(args...) |
24c7594d BB |
94 | |
95 | tabc: => @tabclose() | |
96 | ||
97 | tabnext: -> | |
98 | pane = atom.workspace.getActivePane() | |
99 | pane.activateNextItem() | |
100 | ||
101 | tabn: => @tabnext() | |
102 | ||
103 | tabprevious: -> | |
104 | pane = atom.workspace.getActivePane() | |
105 | pane.activatePreviousItem() | |
106 | ||
107 | tabp: => @tabprevious() | |
108 | ||
109 | edit: (range, filePath) -> | |
110 | filePath = filePath.trim() | |
455f099b BB |
111 | if filePath[0] is '!' |
112 | force = true | |
113 | filePath = filePath[1..].trim() | |
114 | else | |
115 | force = false | |
116 | ||
117 | editor = atom.workspace.getActiveTextEditor() | |
118 | if editor.isModified() and not force | |
119 | throw new CommandError('No write since last change (add ! to override)') | |
24c7594d BB |
120 | if filePath.indexOf(' ') isnt -1 |
121 | throw new CommandError('Only one file name allowed') | |
455f099b BB |
122 | |
123 | if filePath.length isnt 0 | |
124 | fullPath = getFullPath(filePath) | |
125 | if fullPath is editor.getPath() | |
126 | editor.getBuffer().reload() | |
127 | else | |
128 | atom.workspace.open(fullPath) | |
129 | else | |
130 | if editor.getPath()? | |
131 | editor.getBuffer().reload() | |
132 | else | |
133 | throw new CommandError('No file name') | |
24c7594d BB |
134 | |
135 | e: (args...) => @edit(args...) | |
136 | ||
137 | enew: -> | |
138 | buffer = atom.workspace.getActiveTextEditor().buffer | |
139 | buffer.setPath(undefined) | |
140 | buffer.load() | |
141 | ||
142 | write: (range, filePath) -> | |
455f099b BB |
143 | if filePath[0] is '!' |
144 | force = true | |
145 | filePath = filePath[1..] | |
146 | else | |
147 | force = false | |
148 | ||
24c7594d | 149 | filePath = filePath.trim() |
455f099b BB |
150 | if filePath.indexOf(' ') isnt -1 |
151 | throw new CommandError('Only one file name allowed') | |
152 | ||
24c7594d BB |
153 | deferred = Promise.defer() |
154 | ||
24c7594d | 155 | editor = atom.workspace.getActiveTextEditor() |
455f099b BB |
156 | saved = false |
157 | if filePath.length isnt 0 | |
158 | fullPath = getFullPath(filePath) | |
159 | if editor.getPath()? and (not fullPath? or editor.getPath() == fullPath) | |
160 | # Use editor.save when no path is given or the path to the file is given | |
161 | trySave(-> editor.save()).then(deferred.resolve) | |
162 | saved = true | |
163 | else if not fullPath? | |
164 | fullPath = atom.showSaveDialogSync() | |
165 | ||
166 | if not saved and fullPath? | |
167 | if not force and fs.existsSync(fullPath) | |
168 | throw new CommandError("File exists (add ! to override)") | |
169 | trySave(-> saveAs(fullPath)).then(deferred.resolve) | |
24c7594d BB |
170 | |
171 | deferred.promise | |
172 | ||
173 | w: (args...) => | |
174 | @write(args...) | |
175 | ||
176 | wq: (args...) => | |
177 | @write(args...).then => @quit() | |
178 | ||
455f099b | 179 | xit: (args...) => @wq(args...) |
24c7594d BB |
180 | |
181 | wa: -> | |
182 | atom.workspace.saveAll() | |
183 | ||
184 | split: (range, args) -> | |
185 | args = args.trim() | |
186 | filePaths = args.split(' ') | |
187 | filePaths = undefined if filePaths.length is 1 and filePaths[0] is '' | |
188 | pane = atom.workspace.getActivePane() | |
189 | if filePaths? and filePaths.length > 0 | |
190 | newPane = pane.splitUp() | |
191 | for file in filePaths | |
192 | do -> | |
193 | atom.workspace.openURIInPane file, newPane | |
194 | else | |
195 | pane.splitUp(copyActiveItem: true) | |
196 | ||
197 | sp: (args...) => @split(args...) | |
198 | ||
199 | substitute: (range, args) -> | |
200 | args = args.trimLeft() | |
201 | delim = args[0] | |
202 | if /[a-z]/i.test(delim) | |
203 | throw new CommandError( | |
204 | "Regular expressions can't be delimited by letters") | |
205 | delimRE = new RegExp("[^\\\\]#{delim}") | |
206 | spl = [] | |
207 | args_ = args[1..] | |
208 | while (i = args_.search(delimRE)) isnt -1 | |
209 | spl.push args_[..i] | |
210 | args_ = args_[i + 2..] | |
211 | if args_.length is 0 and spl.length is 3 | |
212 | throw new CommandError('Trailing characters') | |
213 | else if args_.length isnt 0 | |
214 | spl.push args_ | |
215 | if spl.length > 3 | |
216 | throw new CommandError('Trailing characters') | |
217 | spl[1] ?= '' | |
218 | spl[2] ?= '' | |
455f099b BB |
219 | notDelimRE = new RegExp("\\\\#{delim}", 'g') |
220 | spl[0] = spl[0].replace(notDelimRE, delim) | |
221 | spl[1] = spl[1].replace(notDelimRE, delim) | |
24c7594d BB |
222 | |
223 | try | |
224 | pattern = new RegExp(spl[0], spl[2]) | |
225 | catch e | |
226 | if e.message.indexOf('Invalid flags supplied to RegExp constructor') is 0 | |
227 | # vim only says 'Trailing characters', but let's be more descriptive | |
228 | throw new CommandError("Invalid flags: #{e.message[45..]}") | |
229 | else if e.message.indexOf('Invalid regular expression: ') is 0 | |
230 | throw new CommandError("Invalid RegEx: #{e.message[27..]}") | |
231 | else | |
232 | throw e | |
233 | ||
234 | buffer = atom.workspace.getActiveTextEditor().buffer | |
455f099b BB |
235 | atom.workspace.getActiveTextEditor().transact -> |
236 | for line in [range[0]..range[1]] | |
237 | buffer.scanInRange(pattern, | |
238 | [[line, 0], [line, buffer.lines[line].length]], | |
239 | ({match, matchText, range, stop, replace}) -> | |
240 | replace(replaceGroups(match[..], spl[1])) | |
241 | ) | |
24c7594d BB |
242 | |
243 | s: (args...) => @substitute(args...) | |
244 | ||
245 | vsplit: (range, args) -> | |
246 | args = args.trim() | |
247 | filePaths = args.split(' ') | |
248 | filePaths = undefined if filePaths.length is 1 and filePaths[0] is '' | |
249 | pane = atom.workspace.getActivePane() | |
250 | if filePaths? and filePaths.length > 0 | |
251 | newPane = pane.splitLeft() | |
252 | for file in filePaths | |
253 | do -> | |
254 | atom.workspace.openURIInPane file, newPane | |
255 | else | |
256 | pane.splitLeft(copyActiveItem: true) | |
257 | ||
258 | vsp: (args...) => @vsplit(args...) | |
259 | ||
260 | delete: (range) -> | |
261 | range = [[range[0], 0], [range[1] + 1, 0]] | |
262 | atom.workspace.getActiveTextEditor().buffer.setTextInRange(range, '') | |
263 | ||
455f099b BB |
264 | set: (range, args) -> |
265 | args = args.trim() | |
266 | if args == "" | |
267 | throw new CommandError("No option specified") | |
268 | options = args.split(' ') | |
269 | for option in options | |
270 | do -> | |
271 | if option.includes("=") | |
272 | nameValPair = option.split("=") | |
273 | if (nameValPair.length != 2) | |
274 | throw new CommandError("Wrong option format. [name]=[value] format is expected") | |
275 | optionName = nameValPair[0] | |
276 | optionValue = nameValPair[1] | |
277 | optionProcessor = VimOption.singleton()[optionName] | |
278 | if not optionProcessor? | |
279 | throw new CommandError("No such option: #{optionName}") | |
280 | optionProcessor(optionValue) | |
281 | else | |
282 | optionProcessor = VimOption.singleton()[option] | |
283 | if not optionProcessor? | |
284 | throw new CommandError("No such option: #{option}") | |
285 | optionProcessor() | |
286 | ||
24c7594d | 287 | module.exports = Ex |