]> git.gir.st - VimFx.git/blob - extension/lib/commands.coffee
Introduce a `VimFx` instance to hold global state
[VimFx.git] / extension / lib / commands.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2012, 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014.
4 # Copyright Wang Zhuochun 2013, 2014.
5 #
6 # This file is part of VimFx.
7 #
8 # VimFx is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # VimFx is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
20 ###
21
22 notation = require('vim-like-key-notation')
23 Command = require('./command')
24 { Marker } = require('./marker')
25 utils = require('./utils')
26 help = require('./help')
27 { getPref
28 , getFirefoxPref
29 , withFirefoxPrefAs } = require('./prefs')
30
31 { isProperLink, isTextInputElement, isContentEditable } = utils
32
33 { classes: Cc, interfaces: Ci, utils: Cu } = Components
34
35 XULDocument = Ci.nsIDOMXULDocument
36
37 command_focus_location_bar = (vim) ->
38 # This function works even if the Address Bar has been removed.
39 vim.rootWindow.focusAndSelectUrlBar()
40
41 command_focus_search_bar = (vim) ->
42 # The `.webSearch()` method opens a search engine in a tab if the Search Bar
43 # has been removed. Therefore we first check if it exists.
44 if vim.rootWindow.BrowserSearch.searchBar
45 vim.rootWindow.BrowserSearch.webSearch()
46
47 helper_paste = (vim) ->
48 url = vim.rootWindow.readFromClipboard()
49 postData = null
50 if not utils.isURL(url) and submission = utils.browserSearchSubmission(url)
51 url = submission.uri.spec
52 { postData } = submission
53 return {url, postData}
54
55 command_paste_and_go = (vim) ->
56 { url, postData } = helper_paste(vim)
57 vim.rootWindow.gBrowser.loadURIWithFlags(url, {postData})
58
59 command_paste_and_go_in_tab = (vim) ->
60 { url, postData } = helper_paste(vim)
61 vim.rootWindow.gBrowser.selectedTab =
62 vim.rootWindow.gBrowser.addTab(url, {postData})
63
64 command_copy_current_url = (vim) ->
65 utils.writeToClipboard(vim.window.location.href)
66
67 # Go up one level in the URL hierarchy.
68 command_go_up_path = (vim, event, count = 1) ->
69 { pathname } = vim.window.location
70 vim.window.location.pathname = pathname.replace(
71 /// (?: /[^/]+ ){1,#{ count }} /?$ ///, ''
72 )
73
74 # Go up to root of the URL hierarchy.
75 command_go_to_root = (vim) ->
76 vim.window.location.href = vim.window.location.origin
77
78 command_go_home = (vim) ->
79 vim.rootWindow.BrowserHome()
80
81 helper_go_history = (num, vim, event, count = 1) ->
82 { index } = vim.rootWindow.getWebNavigation().sessionHistory
83 { history } = vim.window
84 num *= count
85 num = Math.max(num, -index)
86 num = Math.min(num, history.length - 1 - index)
87 return if num == 0
88 history.go(num)
89
90 command_history_back = helper_go_history.bind(undefined, -1)
91
92 command_history_forward = helper_go_history.bind(undefined, +1)
93
94 command_reload = (vim) ->
95 vim.rootWindow.BrowserReload()
96
97 command_reload_force = (vim) ->
98 vim.rootWindow.BrowserReloadSkipCache()
99
100 command_reload_all = (vim) ->
101 vim.rootWindow.gBrowser.reloadAllTabs()
102
103 command_reload_all_force = (vim) ->
104 for tab in vim.rootWindow.gBrowser.visibleTabs
105 window = tab.linkedBrowser.contentWindow
106 window.location.reload(true)
107
108 command_stop = (vim) ->
109 vim.window.stop()
110
111 command_stop_all = (vim) ->
112 for tab in vim.rootWindow.gBrowser.visibleTabs
113 window = tab.linkedBrowser.contentWindow
114 window.stop()
115
116 axisMap =
117 x: ['left', 'scrollLeftMax', 'clientWidth', 'horizontalScrollDistance', 5]
118 y: ['top', 'scrollTopMax', 'clientHeight', 'verticalScrollDistance', 20]
119 helper_scroll = (method, type, axis, amount, vim, event, count = 1) ->
120 frameDocument = event.target.ownerDocument
121 element =
122 if vim.state.scrollableElements.has(event.target)
123 event.target
124 else
125 frameDocument.documentElement
126
127 [ direction, max, dimension, distance, lineAmount ] = axisMap[axis]
128
129 if method == 'scrollTo'
130 amount = Math.min(amount, element[max])
131 else
132 unit = switch type
133 when 'lines'
134 getFirefoxPref("toolkit.scrollbox.#{ distance }") * lineAmount
135 when 'pages'
136 element[dimension]
137 amount *= unit * count
138
139 options = {}
140 options[direction] = amount
141 if getFirefoxPref('general.smoothScroll') and
142 getFirefoxPref("general.smoothScroll.#{ type }")
143 options.behavior = 'smooth'
144
145 withFirefoxPrefAs(
146 'layout.css.scroll-behavior.spring-constant',
147 getPref("smoothScroll.#{ type }.spring-constant"),
148 ->
149 element[method](options)
150 # When scrolling the whole page, the body sometimes needs to be scrolled
151 # too.
152 if element == frameDocument.documentElement
153 frameDocument.body?[method](options)
154 )
155
156 command_scroll_left =
157 helper_scroll.bind(undefined, 'scrollBy', 'lines', 'x', -1)
158 command_scroll_right =
159 helper_scroll.bind(undefined, 'scrollBy', 'lines', 'x', +1)
160 command_scroll_down =
161 helper_scroll.bind(undefined, 'scrollBy', 'lines', 'y', +1)
162 command_scroll_up =
163 helper_scroll.bind(undefined, 'scrollBy', 'lines', 'y', -1)
164 command_scroll_page_down =
165 helper_scroll.bind(undefined, 'scrollBy', 'pages', 'y', +1)
166 command_scroll_page_up =
167 helper_scroll.bind(undefined, 'scrollBy', 'pages', 'y', -1)
168 command_scroll_half_page_down =
169 helper_scroll.bind(undefined, 'scrollBy', 'pages', 'y', +0.5)
170 command_scroll_half_page_up =
171 helper_scroll.bind(undefined, 'scrollBy', 'pages', 'y', -0.5)
172 command_scroll_to_top =
173 helper_scroll.bind(undefined, 'scrollTo', 'other', 'y', 0)
174 command_scroll_to_bottom =
175 helper_scroll.bind(undefined, 'scrollTo', 'other', 'y', Infinity)
176 command_scroll_to_left =
177 helper_scroll.bind(undefined, 'scrollTo', 'other', 'x', 0)
178 command_scroll_to_right =
179 helper_scroll.bind(undefined, 'scrollTo', 'other', 'x', Infinity)
180
181 command_tab_new = (vim) ->
182 vim.rootWindow.BrowserOpenTab()
183
184 command_tab_duplicate = (vim) ->
185 { gBrowser } = vim.rootWindow
186 gBrowser.duplicateTab(gBrowser.selectedTab)
187
188 absoluteTabIndex = (relativeIndex, gBrowser) ->
189 tabs = gBrowser.visibleTabs
190 { selectedTab } = gBrowser
191
192 currentIndex = tabs.indexOf(selectedTab)
193 absoluteIndex = currentIndex + relativeIndex
194 numTabs = tabs.length
195
196 wrap = (Math.abs(relativeIndex) == 1)
197 if wrap
198 absoluteIndex %%= numTabs
199 else
200 absoluteIndex = Math.max(0, absoluteIndex)
201 absoluteIndex = Math.min(absoluteIndex, numTabs - 1)
202
203 return absoluteIndex
204
205 helper_switch_tab = (direction, vim, event, count = 1) ->
206 { gBrowser } = vim.rootWindow
207 gBrowser.selectTabAtIndex(absoluteTabIndex(direction * count, gBrowser))
208
209 command_tab_select_previous = helper_switch_tab.bind(undefined, -1)
210
211 command_tab_select_next = helper_switch_tab.bind(undefined, +1)
212
213 helper_move_tab = (direction, vim, event, count = 1) ->
214 { gBrowser } = vim.rootWindow
215 { selectedTab } = gBrowser
216 { pinned } = selectedTab
217
218 index = absoluteTabIndex(direction * count, gBrowser)
219
220 if index < gBrowser._numPinnedTabs
221 gBrowser.pinTab(selectedTab) unless pinned
222 else
223 gBrowser.unpinTab(selectedTab) if pinned
224
225 gBrowser.moveTabTo(selectedTab, index)
226
227 command_tab_move_backward = helper_move_tab.bind(undefined, -1)
228
229 command_tab_move_forward = helper_move_tab.bind(undefined, +1)
230
231 command_tab_select_first = (vim) ->
232 vim.rootWindow.gBrowser.selectTabAtIndex(0)
233
234 command_tab_select_first_non_pinned = (vim) ->
235 firstNonPinned = vim.rootWindow.gBrowser._numPinnedTabs
236 vim.rootWindow.gBrowser.selectTabAtIndex(firstNonPinned)
237
238 command_tab_select_last = (vim) ->
239 vim.rootWindow.gBrowser.selectTabAtIndex(-1)
240
241 command_tab_toggle_pinned = (vim) ->
242 currentTab = vim.rootWindow.gBrowser.selectedTab
243
244 if currentTab.pinned
245 vim.rootWindow.gBrowser.unpinTab(currentTab)
246 else
247 vim.rootWindow.gBrowser.pinTab(currentTab)
248
249 command_tab_close = (vim, event, count = 1) ->
250 { gBrowser } = vim.rootWindow
251 return if gBrowser.selectedTab.pinned
252 currentIndex = gBrowser.visibleTabs.indexOf(gBrowser.selectedTab)
253 for tab in gBrowser.visibleTabs[currentIndex...(currentIndex + count)]
254 gBrowser.removeTab(tab)
255
256 command_tab_restore = (vim, event, count = 1) ->
257 vim.rootWindow.undoCloseTab() for [1..count]
258
259 command_tab_close_to_end = (vim) ->
260 { gBrowser } = vim.rootWindow
261 gBrowser.removeTabsToTheEndFrom(gBrowser.selectedTab)
262
263 command_tab_close_other = (vim) ->
264 { gBrowser } = vim.rootWindow
265 gBrowser.removeAllTabsBut(gBrowser.selectedTab)
266
267 # Combine links with the same href.
268 combine = (hrefs, marker) ->
269 if marker.type == 'link'
270 { href } = marker.element
271 if href of hrefs
272 parent = hrefs[href]
273 marker.parent = parent
274 parent.weight += marker.weight
275 parent.numChildren++
276 else
277 hrefs[href] = marker
278 return marker
279
280 # Follow links, focus text inputs and click buttons with hint markers.
281 command_follow = (vim, event, count = 1) ->
282 hrefs = {}
283 filter = (element, getElementShape) ->
284 document = element.ownerDocument
285 isXUL = (document instanceof XULDocument)
286 semantic = true
287 switch
288 when isProperLink(element)
289 type = 'link'
290 when isTextInputElement(element) or isContentEditable(element)
291 type = 'text'
292 when element.tabIndex > -1 and
293 not (isXUL and element.nodeName.endsWith('box'))
294 type = 'clickable'
295 unless isXUL or element.nodeName in ['A', 'INPUT', 'BUTTON']
296 semantic = false
297 when element != document.documentElement and
298 vim.state.scrollableElements.has(element)
299 type = 'scrollable'
300 when element.hasAttribute('onclick') or
301 element.hasAttribute('onmousedown') or
302 element.hasAttribute('onmouseup') or
303 element.hasAttribute('oncommand') or
304 element.getAttribute('role') in ['link', 'button'] or
305 # Twitter special-case.
306 element.classList.contains('js-new-tweets-bar') or
307 # Feedly special-case.
308 element.hasAttribute('data-app-action') or
309 element.hasAttribute('data-uri') or
310 element.hasAttribute('data-page-action')
311 type = 'clickable'
312 semantic = false
313 # Putting markers on `<label>` elements is generally redundant, because
314 # its `<input>` gets one. However, some sites hide the actual `<input>`
315 # but keeps the `<label>` to click, either for styling purposes or to keep
316 # the `<input>` hidden until it is used. In those cases we should add a
317 # marker for the `<label>`.
318 when element.nodeName == 'LABEL'
319 if element.htmlFor
320 input = document.getElementById(element.htmlFor)
321 if input and not getElementShape(input)
322 type = 'clickable'
323 # Elements that have “button” somewhere in the class might be clickable,
324 # unless they contain a real link or button or yet an element with
325 # “button” somewhere in the class, in which case they likely are
326 # “button-wrapper”s. (`<SVG element>.className` is not a string!)
327 when not isXUL and typeof element.className == 'string' and
328 element.className.toLowerCase().contains('button')
329 unless element.querySelector('a, button, [class*=button]')
330 type = 'clickable'
331 semantic = false
332 # When viewing an image it should get a marker to toggle zoom.
333 when document.body?.childElementCount == 1 and
334 element.nodeName == 'IMG' and
335 (element.classList.contains('overflowing') or
336 element.classList.contains('shrinkToFit'))
337 type = 'clickable'
338 return unless type
339 return unless shape = getElementShape(element)
340 return combine(hrefs, new Marker(element, shape, {semantic, type}))
341
342 callback = (marker) ->
343 { element } = marker
344 element.focus()
345 last = (count == 1)
346 if not last and marker.type == 'link'
347 utils.openTab(vim.rootWindow, element.href, {
348 inBackground: true
349 relatedToCurrent: true
350 })
351 else
352 if element.target == '_blank'
353 targetReset = element.target
354 element.target = ''
355 utils.simulateClick(element)
356 element.target = targetReset if targetReset
357 count--
358 return (not last and marker.type != 'text')
359
360 vim.enterMode('hints', filter, callback)
361
362 # Follow links in a new background tab with hint markers.
363 command_follow_in_tab = (vim, event, count = 1, inBackground = true) ->
364 hrefs = {}
365 filter = (element, getElementShape) ->
366 return unless isProperLink(element)
367 return unless shape = getElementShape(element)
368 return combine(hrefs, new Marker(element, shape, {semantic: true}))
369
370 callback = (marker) ->
371 last = (count == 1)
372 utils.openTab(vim.rootWindow, marker.element.href, {
373 inBackground: if last then inBackground else true
374 relatedToCurrent: true
375 })
376 count--
377 return not last
378
379 vim.enterMode('hints', filter, callback)
380
381 # Follow links in a new foreground tab with hint markers.
382 command_follow_in_focused_tab = (vim, event, count = 1) ->
383 command_follow_in_tab(vim, event, count, false)
384
385 # Like command_follow but multiple times.
386 command_follow_multiple = (vim, event) ->
387 command_follow(vim, event, Infinity)
388
389 # Copy the URL or text of a markable element to the system clipboard.
390 command_follow_copy = (vim) ->
391 hrefs = {}
392 filter = (element, getElementShape) ->
393 type = switch
394 when isProperLink(element) then 'link'
395 when isTextInputElement(element) then 'textInput'
396 when isContentEditable(element) then 'contenteditable'
397 return unless type
398 return unless shape = getElementShape(element)
399 return combine(hrefs, new Marker(element, shape, {semantic: true, type}))
400
401 callback = (marker) ->
402 { element } = marker
403 text = switch marker.type
404 when 'link' then element.href
405 when 'textInput' then element.value
406 when 'contenteditable' then element.textContent
407 utils.writeToClipboard(text)
408
409 vim.enterMode('hints', filter, callback)
410
411 # Focus element with hint markers.
412 command_follow_focus = (vim) ->
413 filter = (element, getElementShape) ->
414 type = switch
415 when element.tabIndex > -1
416 'focusable'
417 when element != element.ownerDocument.documentElement and
418 vim.state.scrollableElements.has(element)
419 'scrollable'
420 return unless type
421 return unless shape = getElementShape(element)
422 return new Marker(element, shape, {semantic: true, type})
423
424 callback = (marker) ->
425 { element } = marker
426 element.focus()
427 element.select?()
428
429 vim.enterMode('hints', filter, callback)
430
431 # Search for the prev/next patterns in the following attributes of the element.
432 # `rel` should be kept as the first attribute, since the standard way of marking
433 # up prev/next links (`rel="prev"` and `rel="next"`) should be favored. Even
434 # though some of these attributes only allow a fixed set of keywords, we
435 # pattern-match them anyways since lots of sites don’t follow the spec and use
436 # the attributes arbitrarily.
437 attrs = ['rel', 'role', 'data-tooltip', 'aria-label']
438 helper_follow_pattern = (type, vim) ->
439 { document } = vim.window
440
441 # If there’s a `<link rel=prev/next>` element we use that.
442 for link in document.head.getElementsByTagName('link')
443 # Also support `rel=previous`, just like Google.
444 if type == link.rel.toLowerCase().replace(/^previous$/, 'prev')
445 vim.rootWindow.gBrowser.loadURI(link.href)
446 return
447
448 # Otherwise we look for a link or button on the page that seems to go to the
449 # previous or next page.
450 candidates = document.querySelectorAll('a, button')
451 patterns = utils.splitListString(getPref("#{ type }_patterns"))
452 if matchingLink = utils.getBestPatternMatch(patterns, attrs, candidates)
453 utils.simulateClick(matchingLink)
454
455 command_follow_previous = helper_follow_pattern.bind(undefined, 'prev')
456
457 command_follow_next = helper_follow_pattern.bind(undefined, 'next')
458
459 # Focus last focused or first text input and enter text input mode.
460 command_text_input = (vim, event, count) ->
461 { lastFocusedTextInput } = vim.state
462 inputs = Array.filter(
463 vim.window.document.querySelectorAll('input, textarea'), (element) ->
464 return utils.isTextInputElement(element) and utils.area(element) > 0
465 )
466 if lastFocusedTextInput and lastFocusedTextInput not in inputs
467 inputs.push(lastFocusedTextInput)
468 return unless inputs.length > 0
469 inputs.sort((a, b) -> a.tabIndex - b.tabIndex)
470 if count == null and lastFocusedTextInput
471 count = inputs.indexOf(lastFocusedTextInput) + 1
472 inputs[count - 1].select()
473 vim.enterMode('text-input', inputs)
474
475 findStorage = {lastSearchString: ''}
476
477 helper_find = (highlight, vim) ->
478 findBar = vim.rootWindow.gBrowser.getFindBar()
479
480 findBar.onFindCommand()
481 findBar._findField.focus()
482 findBar._findField.select()
483
484 return unless highlightButton = findBar.getElement('highlight')
485 if highlightButton.checked != highlight
486 highlightButton.click()
487
488 # Open the find bar, making sure that hightlighting is off.
489 command_find = helper_find.bind(undefined, false)
490
491 # Open the find bar, making sure that hightlighting is on.
492 command_find_highlight_all = helper_find.bind(undefined, true)
493
494 helper_find_again = (direction, vim) ->
495 findBar = vim.rootWindow.gBrowser.getFindBar()
496 if findStorage.lastSearchString.length > 0
497 findBar._findField.value = findStorage.lastSearchString
498 findBar.onFindAgainCommand(direction)
499
500 command_find_next = helper_find_again.bind(undefined, false)
501
502 command_find_previous = helper_find_again.bind(undefined, true)
503
504 command_enter_mode_insert = (vim) ->
505 vim.enterMode('insert')
506
507 # Quote next keypress (pass it through to the page).
508 command_quote = (vim, event, count = 1) ->
509 vim.enterMode('insert', count)
510
511 # Display the Help Dialog.
512 command_help = (vim) ->
513 help.injectHelp(vim.window.document, vim.parent)
514
515 # Open and focus the Developer Toolbar.
516 command_dev = (vim) ->
517 vim.rootWindow.DeveloperToolbar.show(true) # `true` to focus.
518
519 command_esc = (vim, event) ->
520 utils.blurActiveElement(vim.window)
521
522 # Blur active XUL control.
523 callback = -> event.originalTarget?.ownerDocument?.activeElement?.blur()
524 vim.window.setTimeout(callback, 0)
525
526 help.removeHelp(vim.window.document)
527
528 vim.rootWindow.DeveloperToolbar.hide()
529
530 vim.rootWindow.gBrowser.getFindBar().close()
531
532 vim.rootWindow.TabView.hide()
533
534 { document } = vim.window
535 if document.exitFullscreen
536 document.exitFullscreen()
537 else
538 document.mozCancelFullScreen()
539
540
541 # coffeelint: disable=max_line_length
542 commands = [
543 new Command('location', 'focus_location_bar', command_focus_location_bar)
544 new Command('location', 'focus_search_bar', command_focus_search_bar)
545 new Command('location', 'paste_and_go', command_paste_and_go)
546 new Command('location', 'paste_and_go_in_tab', command_paste_and_go_in_tab)
547 new Command('location', 'copy_current_url', command_copy_current_url)
548 new Command('location', 'go_up_path', command_go_up_path)
549 new Command('location', 'go_to_root', command_go_to_root)
550 new Command('location', 'go_home', command_go_home)
551 new Command('location', 'history_back', command_history_back)
552 new Command('location', 'history_forward', command_history_forward)
553 new Command('location', 'reload', command_reload)
554 new Command('location', 'reload_force', command_reload_force)
555 new Command('location', 'reload_all', command_reload_all)
556 new Command('location', 'reload_all_force', command_reload_all_force)
557 new Command('location', 'stop', command_stop)
558 new Command('location', 'stop_all', command_stop_all)
559
560 new Command('scrolling', 'scroll_left', command_scroll_left)
561 new Command('scrolling', 'scroll_right', command_scroll_right )
562 new Command('scrolling', 'scroll_down', command_scroll_down)
563 new Command('scrolling', 'scroll_up', command_scroll_up)
564 new Command('scrolling', 'scroll_page_down', command_scroll_page_down)
565 new Command('scrolling', 'scroll_page_up', command_scroll_page_up)
566 new Command('scrolling', 'scroll_half_page_down', command_scroll_half_page_down)
567 new Command('scrolling', 'scroll_half_page_up', command_scroll_half_page_up)
568 new Command('scrolling', 'scroll_to_top', command_scroll_to_top )
569 new Command('scrolling', 'scroll_to_bottom', command_scroll_to_bottom)
570 new Command('scrolling', 'scroll_to_left', command_scroll_to_left )
571 new Command('scrolling', 'scroll_to_right', command_scroll_to_right)
572
573 new Command('tabs', 'tab_new', command_tab_new)
574 new Command('tabs', 'tab_duplicate', command_tab_duplicate)
575 new Command('tabs', 'tab_select_previous', command_tab_select_previous)
576 new Command('tabs', 'tab_select_next', command_tab_select_next)
577 new Command('tabs', 'tab_move_backward', command_tab_move_backward)
578 new Command('tabs', 'tab_move_forward', command_tab_move_forward)
579 new Command('tabs', 'tab_select_first', command_tab_select_first)
580 new Command('tabs', 'tab_select_first_non_pinned', command_tab_select_first_non_pinned)
581 new Command('tabs', 'tab_select_last', command_tab_select_last)
582 new Command('tabs', 'tab_toggle_pinned', command_tab_toggle_pinned)
583 new Command('tabs', 'tab_close', command_tab_close)
584 new Command('tabs', 'tab_restore', command_tab_restore)
585 new Command('tabs', 'tab_close_to_end', command_tab_close_to_end)
586 new Command('tabs', 'tab_close_other', command_tab_close_other)
587
588 new Command('browsing', 'follow', command_follow)
589 new Command('browsing', 'follow_in_tab', command_follow_in_tab)
590 new Command('browsing', 'follow_in_focused_tab', command_follow_in_focused_tab)
591 new Command('browsing', 'follow_multiple', command_follow_multiple)
592 new Command('browsing', 'follow_copy', command_follow_copy)
593 new Command('browsing', 'follow_focus', command_follow_focus)
594 new Command('browsing', 'follow_previous', command_follow_previous)
595 new Command('browsing', 'follow_next', command_follow_next)
596 new Command('browsing', 'text_input', command_text_input)
597
598 new Command('find', 'find', command_find)
599 new Command('find', 'find_highlight_all', command_find_highlight_all)
600 new Command('find', 'find_next', command_find_next)
601 new Command('find', 'find_previous', command_find_previous)
602
603 new Command('misc', 'enter_mode_insert', command_enter_mode_insert)
604 new Command('misc', 'quote', command_quote)
605 new Command('misc', 'help', command_help)
606 new Command('misc', 'dev', command_dev)
607
608 escapeCommand =
609 new Command('misc', 'esc', command_esc)
610 ]
611 # coffeelint: enable=max_line_length
612
613 exports.commands = commands
614 exports.escapeCommand = escapeCommand
615 exports.findStorage = findStorage
Imprint / Impressum