]> git.gir.st - VimFx.git/blob - extension/lib/commands.coffee
Fix `scroll.reset_timeout` pref typo
[VimFx.git] / extension / lib / commands.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2012, 2013, 2014.
3 # Copyright Simon Lydell 2013, 2014, 2015, 2016.
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 # This file defines all Normal mode commands. Commands that need to interact
23 # with web page content do so by running `vim._run(name)`, which invokes `name`
24 # in commands-frame.coffee.
25
26 # NOTE: Most tab related commands need to do their actual tab manipulations in
27 # the next tick (`utils.nextTick`) to work around bug 1200334.
28
29 config = require('./config')
30 help = require('./help')
31 hints = require('./hints')
32 prefs = require('./prefs')
33 translate = require('./l10n')
34 utils = require('./utils')
35 SelectionManager = require('./selection')
36 viewportUtils = require('./viewport')
37
38 {ContentClick} = Cu.import('resource:///modules/ContentClick.jsm', {})
39 {FORWARD, BACKWARD} = SelectionManager
40
41 SPRING_CONSTANT_PREF = 'layout.css.scroll-behavior.spring-constant'
42
43 commands = {}
44
45
46
47 commands.focus_location_bar = ({vim}) ->
48 vim.window.focusAndSelectUrlBar()
49
50 commands.focus_search_bar = ({vim, count}) ->
51 # The `.webSearch()` method opens a search engine in a tab if the search bar
52 # has been removed. Therefore we first check if it exists.
53 if vim.window.BrowserSearch.searchBar
54 vim.window.BrowserSearch.webSearch()
55 else
56 vim.notify(translate('notification.focus_search_bar.none'))
57
58 helper_paste_and_go = (props, {vim}) ->
59 {gURLBar} = vim.window
60 gURLBar.value = vim.window.readFromClipboard()
61 gURLBar.handleCommand(new vim.window.KeyboardEvent('keydown', props))
62
63 commands.paste_and_go = helper_paste_and_go.bind(null, null)
64
65 commands.paste_and_go_in_tab = helper_paste_and_go.bind(null, {altKey: true})
66
67 commands.copy_current_url = ({vim}) ->
68 utils.writeToClipboard(vim.window.gBrowser.currentURI.spec)
69 vim.notify(translate('notification.copy_current_url.success'))
70
71 commands.go_up_path = ({vim, count}) ->
72 vim._run('go_up_path', {count})
73
74 commands.go_to_root = ({vim}) ->
75 vim._run('go_to_root')
76
77 commands.go_home = ({vim}) ->
78 vim.window.BrowserHome()
79
80 helper_go_history = (direction, {vim, count = 1}) ->
81 {window} = vim
82 {SessionStore, gBrowser} = window
83
84 if (direction == 'back' and not gBrowser.canGoBack) or
85 (direction == 'forward' and not gBrowser.canGoForward)
86 vim.notify(translate("notification.history_#{direction}.limit"))
87 return
88
89 # `SessionStore.getSessionHistory()` (used below to support counts) starts
90 # lots of asynchronous tasks internally, which is a bit unreliable, it has
91 # turned out. The primary use of the `history_back` and `history_forward`
92 # commands is to go _one_ step back or forward, though, so those cases are
93 # optimized to use more reliable ways of going back and forward. Also, some
94 # extensions override the following functions, so calling them also gives
95 # better interoperability.
96 if count == 1
97 if direction == 'back'
98 window.BrowserBack()
99 else
100 window.BrowserForward()
101 return
102
103 SessionStore.getSessionHistory(gBrowser.selectedTab, (sessionHistory) ->
104 {index} = sessionHistory
105 newIndex = index + count * (if direction == 'back' then -1 else 1)
106 newIndex = Math.max(newIndex, 0)
107 newIndex = Math.min(newIndex, sessionHistory.entries.length - 1)
108 gBrowser.gotoIndex(newIndex)
109 )
110
111 commands.history_back = helper_go_history.bind(null, 'back')
112
113 commands.history_forward = helper_go_history.bind(null, 'forward')
114
115 commands.history_list = ({vim}) ->
116 menu = vim.window.document.getElementById('backForwardMenu')
117 utils.openPopup(menu)
118 if menu.childElementCount == 0
119 vim.notify(translate('notification.history_list.none'))
120
121 commands.reload = ({vim}) ->
122 vim.window.BrowserReload()
123
124 commands.reload_force = ({vim}) ->
125 vim.window.BrowserReloadSkipCache()
126
127 commands.reload_all = ({vim}) ->
128 vim.window.gBrowser.reloadAllTabs()
129
130 commands.reload_all_force = ({vim}) ->
131 for tab in vim.window.gBrowser.visibleTabs
132 gBrowser = tab.linkedBrowser
133 consts = gBrowser.webNavigation
134 flags = consts.LOAD_FLAGS_BYPASS_PROXY | consts.LOAD_FLAGS_BYPASS_CACHE
135 gBrowser.reload(flags)
136 return
137
138 commands.stop = ({vim}) ->
139 vim.window.BrowserStop()
140
141 commands.stop_all = ({vim}) ->
142 for tab in vim.window.gBrowser.visibleTabs
143 tab.linkedBrowser.stop()
144 return
145
146
147
148 springConstant = {
149 nonce: null
150 value: null
151 }
152
153 helper_scroll = (vim, uiEvent, args...) ->
154 [
155 method, type, directions, amounts
156 properties = null, adjustment = 0, name = 'scroll'
157 ] = args
158 options = {
159 method, type, directions, amounts, properties, adjustment
160 smooth: (
161 prefs.root.get('general.smoothScroll') and
162 prefs.root.get("general.smoothScroll.#{type}")
163 )
164 }
165
166 # Temporarily set Firefox’s “spring constant” pref to get the desired smooth
167 # scrolling speed. Reset it `reset_timeout` milliseconds after the last
168 # scrolling command was invoked.
169 springConstant.nonce = nonce = {}
170 springConstant.value ?= prefs.root.get(SPRING_CONSTANT_PREF)
171 prefs.root.set(
172 SPRING_CONSTANT_PREF,
173 vim.options["smoothScroll.#{type}.spring-constant"]
174 )
175 reset = ->
176 vim.window.setTimeout((->
177 return unless springConstant.nonce == nonce
178 prefs.root.set(SPRING_CONSTANT_PREF, springConstant.value)
179 springConstant.nonce = null
180 springConstant.value = null
181 ), vim.options['scroll.reset_timeout'])
182
183 helpScroll = help.getHelp(vim.window)?.querySelector('.wrapper')
184 if uiEvent or helpScroll
185 activeElement = helpScroll or utils.getActiveElement(vim.window)
186 if vim._state.scrollableElements.has(activeElement) or helpScroll
187 viewportUtils.scroll(activeElement, options)
188 reset()
189 return
190
191 vim._run(name, options, reset)
192
193
194 helper_scrollByLinesX = (amount, {vim, uiEvent, count = 1}) ->
195 distance = prefs.root.get('toolkit.scrollbox.horizontalScrollDistance')
196 helper_scroll(
197 vim, uiEvent, 'scrollBy', 'lines', ['left'], [amount * distance * count * 5]
198 )
199
200 helper_scrollByLinesY = (amount, {vim, uiEvent, count = 1}) ->
201 distance = prefs.root.get('toolkit.scrollbox.verticalScrollDistance')
202 helper_scroll(
203 vim, uiEvent, 'scrollBy', 'lines', ['top'], [amount * distance * count * 20]
204 )
205
206 helper_scrollByPagesY = (amount, type, {vim, uiEvent, count = 1}) ->
207 adjustment = vim.options["scroll.#{type}_page_adjustment"]
208 helper_scroll(
209 vim, uiEvent, 'scrollBy', 'pages', ['top'], [amount * count],
210 ['clientHeight'], adjustment
211 )
212
213 helper_scrollToX = (amount, {vim, uiEvent}) ->
214 helper_mark_last_scroll_position(vim)
215 helper_scroll(
216 vim, uiEvent, 'scrollTo', 'other', ['left'], [amount], ['scrollLeftMax']
217 )
218
219 helper_scrollToY = (amount, {vim, uiEvent}) ->
220 helper_mark_last_scroll_position(vim)
221 helper_scroll(
222 vim, uiEvent, 'scrollTo', 'other', ['top'], [amount], ['scrollTopMax']
223 )
224
225 commands.scroll_left = helper_scrollByLinesX.bind(null, -1)
226 commands.scroll_right = helper_scrollByLinesX.bind(null, +1)
227 commands.scroll_down = helper_scrollByLinesY.bind(null, +1)
228 commands.scroll_up = helper_scrollByLinesY.bind(null, -1)
229 commands.scroll_page_down = helper_scrollByPagesY.bind(null, +1, 'full')
230 commands.scroll_page_up = helper_scrollByPagesY.bind(null, -1, 'full')
231 commands.scroll_half_page_down = helper_scrollByPagesY.bind(null, +0.5, 'half')
232 commands.scroll_half_page_up = helper_scrollByPagesY.bind(null, -0.5, 'half')
233 commands.scroll_to_top = helper_scrollToY.bind(null, 0)
234 commands.scroll_to_bottom = helper_scrollToY.bind(null, Infinity)
235 commands.scroll_to_left = helper_scrollToX.bind(null, 0)
236 commands.scroll_to_right = helper_scrollToX.bind(null, Infinity)
237
238 helper_mark_last_scroll_position = (vim) ->
239 keyStr = vim.options['scroll.last_position_mark']
240 vim._run('mark_scroll_position', {keyStr, notify: false})
241
242 commands.mark_scroll_position = ({vim}) ->
243 vim.enterMode('marks', (keyStr) -> vim._run('mark_scroll_position', {keyStr}))
244 vim.notify(translate('notification.mark_scroll_position.enter'))
245
246 commands.scroll_to_mark = ({vim}) ->
247 vim.enterMode('marks', (keyStr) ->
248 unless keyStr == vim.options['scroll.last_position_mark']
249 helper_mark_last_scroll_position(vim)
250 helper_scroll(
251 vim, null, 'scrollTo', 'other', ['top', 'left'], keyStr,
252 ['scrollTopMax', 'scrollLeftMax'], 0, 'scroll_to_mark'
253 )
254 )
255 vim.notify(translate('notification.scroll_to_mark.enter'))
256
257
258
259 commands.tab_new = ({vim}) ->
260 utils.nextTick(vim.window, ->
261 vim.window.BrowserOpenTab()
262 )
263
264 commands.tab_new_after_current = ({vim}) ->
265 {window} = vim
266 newTabPosition = window.gBrowser.selectedTab._tPos + 1
267 utils.nextTick(window, ->
268 utils.listenOnce(window, 'TabOpen', (event) ->
269 newTab = event.originalTarget
270 window.gBrowser.moveTabTo(newTab, newTabPosition)
271 )
272 window.BrowserOpenTab()
273 )
274
275 commands.tab_duplicate = ({vim}) ->
276 {gBrowser} = vim.window
277 utils.nextTick(vim.window, ->
278 gBrowser.duplicateTab(gBrowser.selectedTab)
279 )
280
281 absoluteTabIndex = (relativeIndex, gBrowser, {pinnedSeparate}) ->
282 tabs = gBrowser.visibleTabs
283 {selectedTab} = gBrowser
284
285 currentIndex = tabs.indexOf(selectedTab)
286 absoluteIndex = currentIndex + relativeIndex
287 numTabsTotal = tabs.length
288 numPinnedTabs = gBrowser._numPinnedTabs
289
290 [numTabs, min] = switch
291 when not pinnedSeparate
292 [numTabsTotal, 0]
293 when selectedTab.pinned
294 [numPinnedTabs, 0]
295 else
296 [numTabsTotal - numPinnedTabs, numPinnedTabs]
297
298 # Wrap _once_ if at one of the ends of the tab bar and cannot move in the
299 # current direction.
300 if (relativeIndex < 0 and currentIndex == min) or
301 (relativeIndex > 0 and currentIndex == min + numTabs - 1)
302 if absoluteIndex < min
303 absoluteIndex += numTabs
304 else if absoluteIndex >= min + numTabs
305 absoluteIndex -= numTabs
306
307 absoluteIndex = Math.max(min, absoluteIndex)
308 absoluteIndex = Math.min(absoluteIndex, min + numTabs - 1)
309
310 return absoluteIndex
311
312 helper_switch_tab = (direction, {vim, count = 1}) ->
313 {gBrowser} = vim.window
314 index = absoluteTabIndex(direction * count, gBrowser, {pinnedSeparate: false})
315 utils.nextTick(vim.window, ->
316 gBrowser.selectTabAtIndex(index)
317 )
318
319 commands.tab_select_previous = helper_switch_tab.bind(null, -1)
320
321 commands.tab_select_next = helper_switch_tab.bind(null, +1)
322
323 helper_is_visited = (tab) ->
324 return tab.getAttribute('VimFx-visited') or not tab.getAttribute('unread')
325
326 commands.tab_select_most_recent = ({vim, count = 1}) ->
327 {gBrowser} = vim.window
328 tabsSorted =
329 Array.filter(
330 gBrowser.tabs,
331 (tab) -> not tab.closing and helper_is_visited(tab)
332 ).sort((a, b) -> b.lastAccessed - a.lastAccessed)[1..] # Remove current tab.
333 tab = tabsSorted[Math.min(count - 1, tabsSorted.length - 1)]
334 if tab
335 gBrowser.selectedTab = tab
336 else
337 vim.notify(translate('notification.tab_select_most_recent.none'))
338
339 commands.tab_select_oldest_unvisited = ({vim, count = 1}) ->
340 {gBrowser} = vim.window
341 tabsSorted =
342 Array.filter(
343 gBrowser.tabs,
344 (tab) -> not tab.closing and not helper_is_visited(tab)
345 ).sort((a, b) -> a.lastAccessed - b.lastAccessed)
346 tab = tabsSorted[Math.min(count - 1, tabsSorted.length - 1)]
347 if tab
348 gBrowser.selectedTab = tab
349 else
350 vim.notify(translate('notification.tab_select_oldest_unvisited.none'))
351
352 helper_move_tab = (direction, {vim, count = 1}) ->
353 {gBrowser} = vim.window
354 index = absoluteTabIndex(direction * count, gBrowser, {pinnedSeparate: true})
355 utils.nextTick(vim.window, ->
356 gBrowser.moveTabTo(gBrowser.selectedTab, index)
357 )
358
359 commands.tab_move_backward = helper_move_tab.bind(null, -1)
360
361 commands.tab_move_forward = helper_move_tab.bind(null, +1)
362
363 commands.tab_move_to_window = ({vim}) ->
364 {gBrowser} = vim.window
365 gBrowser.replaceTabWithWindow(gBrowser.selectedTab)
366
367 commands.tab_select_first = ({vim, count = 1}) ->
368 utils.nextTick(vim.window, ->
369 vim.window.gBrowser.selectTabAtIndex(count - 1)
370 )
371
372 commands.tab_select_first_non_pinned = ({vim, count = 1}) ->
373 firstNonPinned = vim.window.gBrowser._numPinnedTabs
374 utils.nextTick(vim.window, ->
375 vim.window.gBrowser.selectTabAtIndex(firstNonPinned + count - 1)
376 )
377
378 commands.tab_select_last = ({vim, count = 1}) ->
379 utils.nextTick(vim.window, ->
380 vim.window.gBrowser.selectTabAtIndex(-count)
381 )
382
383 commands.tab_toggle_pinned = ({vim}) ->
384 currentTab = vim.window.gBrowser.selectedTab
385 if currentTab.pinned
386 vim.window.gBrowser.unpinTab(currentTab)
387 else
388 vim.window.gBrowser.pinTab(currentTab)
389
390 commands.tab_close = ({vim, count = 1}) ->
391 {gBrowser} = vim.window
392 return if gBrowser.selectedTab.pinned
393 currentIndex = gBrowser.visibleTabs.indexOf(gBrowser.selectedTab)
394 utils.nextTick(vim.window, ->
395 for tab in gBrowser.visibleTabs[currentIndex...(currentIndex + count)]
396 gBrowser.removeTab(tab)
397 return
398 )
399
400 commands.tab_restore = ({vim, count = 1}) ->
401 utils.nextTick(vim.window, ->
402 for index in [0...count] by 1
403 restoredTab = vim.window.undoCloseTab()
404 if not restoredTab and index == 0
405 vim.notify(translate('notification.tab_restore.none'))
406 break
407 return
408 )
409
410 commands.tab_restore_list = ({vim}) ->
411 {window} = vim
412 fragment = window.RecentlyClosedTabsAndWindowsMenuUtils.getTabsFragment(
413 window, 'menuitem'
414 )
415 if fragment.childElementCount == 0
416 vim.notify(translate('notification.tab_restore.none'))
417 else
418 utils.openPopup(utils.injectTemporaryPopup(window.document, fragment))
419
420 commands.tab_close_to_end = ({vim}) ->
421 {gBrowser} = vim.window
422 gBrowser.removeTabsToTheEndFrom(gBrowser.selectedTab)
423
424 commands.tab_close_other = ({vim}) ->
425 {gBrowser} = vim.window
426 gBrowser.removeAllTabsBut(gBrowser.selectedTab)
427
428
429
430 helper_follow = (name, vim, callback, count = null) ->
431 vim.markPageInteraction()
432 help.removeHelp(vim.window)
433
434 # Enter hints mode immediately, with an empty set of markers. The user might
435 # press keys before the `vim._run` callback is invoked. Those key presses
436 # should be handled in hints mode, not normal mode.
437 initialMarkers = []
438 storage = vim.enterMode(
439 'hints', initialMarkers, callback, count, vim.options.hints_sleep
440 )
441
442 setMarkers = ({wrappers, viewport}) ->
443 if wrappers.length > 0
444 {markers, markerMap} = hints.injectHints(
445 vim.window, wrappers, viewport, vim.options
446 )
447 storage.markers = markers
448 storage.markerMap = markerMap
449 else
450 vim.notify(translate('notification.follow.none'))
451 vim.enterMode('normal')
452
453 vim._run(name, {}, (result) ->
454 # The user might have exited hints mode (and perhaps even entered it again)
455 # before this callback is invoked. If so, `storage.markers` has been
456 # cleared, or set to a new value. Only proceed if it is unchanged.
457 return unless storage.markers == initialMarkers
458 setMarkers(result)
459 storage.markEverything = ->
460 lastMarkers = storage.markers
461 vim._run(name, {markEverything: true}, (newResult) ->
462 return unless storage.markers == lastMarkers
463 setMarkers(newResult)
464 )
465 )
466
467 helper_follow_clickable = (options, {vim, count = 1}) ->
468 callback = (marker, timesLeft, keyStr) ->
469 {inTab, inBackground} = options
470 {type, elementIndex} = marker.wrapper
471 isLast = (timesLeft == 1)
472 isLink = (type == 'link')
473
474 switch
475 when keyStr.startsWith(vim.options.hints_toggle_in_tab)
476 inTab = not inTab
477 when keyStr.startsWith(vim.options.hints_toggle_in_background)
478 inTab = true
479 inBackground = not inBackground
480 else
481 unless isLast
482 inTab = true
483 inBackground = true
484
485 inTab = false unless isLink
486
487 if type == 'text' or (isLink and not (inTab and inBackground))
488 isLast = true
489
490 vim._focusMarkerElement(elementIndex)
491
492 if inTab
493 utils.nextTick(vim.window, ->
494 # `ContentClick.contentAreaClick` is what Firefox invokes when you click
495 # links using the mouse. Using that instead of simply
496 # `gBrowser.loadOneTab(url, options)` gives better interoperability with
497 # other add-ons, such as Tree Style Tab and BackTrack Tab History.
498 reset = prefs.root.tmp('browser.tabs.loadInBackground', true)
499 ContentClick.contentAreaClick({
500 href: marker.wrapper.href
501 shiftKey: not inBackground
502 ctrlKey: true
503 metaKey: true
504 }, vim.browser)
505 reset()
506 )
507 else
508 vim._run('click_marker_element', {
509 elementIndex, type
510 preventTargetBlank: vim.options.prevent_target_blank
511 })
512
513 return not isLast
514
515 name = if options.inTab then 'follow_in_tab' else 'follow'
516 helper_follow(name, vim, callback, count)
517
518 commands.follow =
519 helper_follow_clickable.bind(null, {inTab: false, inBackground: true})
520
521 commands.follow_in_tab =
522 helper_follow_clickable.bind(null, {inTab: true, inBackground: true})
523
524 commands.follow_in_focused_tab =
525 helper_follow_clickable.bind(null, {inTab: true, inBackground: false})
526
527 commands.follow_in_window = ({vim}) ->
528 callback = (marker) ->
529 vim._focusMarkerElement(marker.wrapper.elementIndex)
530 {href} = marker.wrapper
531 vim.window.openLinkIn(href, 'window', {}) if href
532 helper_follow('follow_in_tab', vim, callback)
533
534 commands.follow_multiple = (args) ->
535 args.count = Infinity
536 commands.follow(args)
537
538 commands.follow_copy = ({vim}) ->
539 callback = (marker) ->
540 property = switch marker.wrapper.type
541 when 'link'
542 'href'
543 when 'text'
544 'value'
545 when 'contenteditable', 'other'
546 '_selection'
547 helper_copy_marker_element(vim, marker.wrapper.elementIndex, property)
548 helper_follow('follow_copy', vim, callback)
549
550 commands.follow_focus = ({vim}) ->
551 callback = (marker) ->
552 vim._focusMarkerElement(marker.wrapper.elementIndex, {select: true})
553 helper_follow('follow_focus', vim, callback)
554
555 commands.click_browser_element = ({vim}) ->
556 markerElements = []
557
558 filter = ({markEverything}, element, getElementShape) ->
559 document = element.ownerDocument
560 semantic = true
561 type = switch
562 when vim._state.scrollableElements.has(element)
563 'scrollable'
564 when utils.isFocusable(element) or
565 (element.onclick and element.localName != 'statuspanel')
566 'clickable'
567
568 if markEverything and not type
569 type = 'other'
570 semantic = false
571
572 return unless type
573 return unless shape = getElementShape(element)
574 length = markerElements.push(element)
575 return {type, semantic, shape, elementIndex: length - 1}
576
577 callback = (marker) ->
578 element = markerElements[marker.wrapper.elementIndex]
579 switch marker.wrapper.type
580 when 'scrollable'
581 utils.focusElement(element, {flag: 'FLAG_BYKEY'})
582 when 'clickable', 'other'
583 sequence =
584 if element.localName == 'tab'
585 ['mousedown']
586 else
587 'click-xul'
588 utils.focusElement(element)
589 utils.simulateMouseEvents(element, sequence)
590
591 createMarkers = (wrappers) ->
592 viewport = viewportUtils.getWindowViewport(vim.window)
593 {markers} = hints.injectHints(vim.window, wrappers, viewport, {
594 hint_chars: vim.options.hint_chars
595 ui: true
596 })
597 return markers
598
599 wrappers = hints.getMarkableElements(
600 vim.window, filter.bind(null, {markEverything: false})
601 )
602
603 if wrappers.length > 0
604 storage = vim.enterMode('hints', createMarkers(wrappers), callback)
605 storage.markEverything = ->
606 newWrappers = hints.getMarkableElements(
607 vim.window, filter.bind(null, {markEverything: true})
608 )
609 storage.markers = createMarkers(newWrappers)
610 else
611 vim.notify(translate('notification.follow.none'))
612
613 helper_follow_pattern = (type, {vim}) ->
614 options = {
615 pattern_selector: vim.options.pattern_selector
616 pattern_attrs: vim.options.pattern_attrs
617 patterns: vim.options["#{type}_patterns"]
618 }
619 vim._run('follow_pattern', {type, options})
620
621 commands.follow_previous = helper_follow_pattern.bind(null, 'prev')
622
623 commands.follow_next = helper_follow_pattern.bind(null, 'next')
624
625 commands.focus_text_input = ({vim, count}) ->
626 vim.markPageInteraction()
627 vim._run('focus_text_input', {count})
628
629 helper_follow_selectable = ({select}, {vim}) ->
630 callback = (marker) ->
631 vim._run('element_text_select', {
632 elementIndex: marker.wrapper.elementIndex
633 full: select
634 scroll: select
635 })
636 vim.enterMode('caret', select)
637 helper_follow('follow_selectable', vim, callback)
638
639 commands.element_text_caret =
640 helper_follow_selectable.bind(null, {select: false})
641
642 commands.element_text_select =
643 helper_follow_selectable.bind(null, {select: true})
644
645 commands.element_text_copy = ({vim}) ->
646 callback = (marker) ->
647 helper_copy_marker_element(vim, marker.wrapper.elementIndex, '_selection')
648 helper_follow('follow_selectable', vim, callback)
649
650 helper_copy_marker_element = (vim, elementIndex, property) ->
651 if property == '_selection'
652 # Selecting the text and then copying that selection is better than copying
653 # `.textContent`. Slack uses markdown-style backtick syntax for code spans
654 # and then includes those backticks in the compiled output (!), in hidden
655 # `<span>`s, so `.textContent` would copy those too. In `contenteditable`
656 # elements, text selection gives better whitespace than `.textContent`.
657 vim._run('element_text_select', {elementIndex, full: true}, ->
658 vim.window.goDoCommand('cmd_copy') # See `caret.copy_selection_and_exit`.
659 vim._run('clear_selection')
660 )
661 else
662 vim._run('copy_marker_element', {elementIndex, property})
663
664
665
666 findStorage = {lastSearchString: ''}
667
668 helper_find_from_top_of_viewport = (vim, direction, callback) ->
669 if vim.options.find_from_top_of_viewport
670 vim._run('find_from_top_of_viewport', {direction}, callback)
671 else
672 callback()
673
674 helper_find = ({highlight, linksOnly = false}, {vim}) ->
675 helpSearchInput = help.getSearchInput(vim.window)
676 if helpSearchInput
677 helpSearchInput.select()
678 return
679
680 helper_mark_last_scroll_position(vim)
681 helper_find_from_top_of_viewport(vim, FORWARD, ->
682 findBar = vim.window.gBrowser.getFindBar()
683
684 mode = if linksOnly then findBar.FIND_LINKS else findBar.FIND_NORMAL
685 findBar.startFind(mode)
686 utils.focusElement(findBar._findField, {select: true})
687
688 return if linksOnly
689 return unless highlightButton = findBar.getElement('highlight')
690 if highlightButton.checked != highlight
691 highlightButton.click()
692 )
693
694 commands.find = helper_find.bind(null, {highlight: false})
695
696 commands.find_highlight_all = helper_find.bind(null, {highlight: true})
697
698 commands.find_links_only = helper_find.bind(null, {linksOnly: true})
699
700 helper_find_again = (direction, {vim}) ->
701 findBar = vim.window.gBrowser.getFindBar()
702 if findStorage.lastSearchString.length == 0
703 vim.notify(translate('notification.find_again.none'))
704 return
705
706 helper_mark_last_scroll_position(vim)
707 helper_find_from_top_of_viewport(vim, direction, ->
708 findBar._findField.value = findStorage.lastSearchString
709 findBar.onFindAgainCommand(not direction)
710 message = findBar._findStatusDesc.textContent
711 vim.notify(message) if message
712 )
713
714 commands.find_next = helper_find_again.bind(null, FORWARD)
715
716 commands.find_previous = helper_find_again.bind(null, BACKWARD)
717
718
719
720 commands.window_new = ({vim}) ->
721 vim.window.OpenBrowserWindow()
722
723 commands.window_new_private = ({vim}) ->
724 vim.window.OpenBrowserWindow({private: true})
725
726 commands.enter_mode_ignore = ({vim}) ->
727 vim.enterMode('ignore', {type: 'explicit'})
728
729 # Quote next keypress (pass it through to the page).
730 commands.quote = ({vim, count = 1}) ->
731 vim.enterMode('ignore', {type: 'explicit', count})
732
733 commands.enter_reader_view = ({vim}) ->
734 button = vim.window.document.getElementById('reader-mode-button')
735 if not button?.hidden
736 button.click()
737 else
738 vim.notify(translate('notification.enter_reader_view.none'))
739
740 commands.reload_config_file = ({vim}) ->
741 vim._parent.emit('shutdown')
742 config.load(vim._parent, (status) -> switch status
743 when null
744 vim.notify(translate('notification.reload_config_file.none'))
745 when true
746 vim.notify(translate('notification.reload_config_file.success'))
747 else
748 vim.notify(translate('notification.reload_config_file.failure'))
749 )
750
751 commands.help = ({vim}) ->
752 help.injectHelp(vim.window, vim._parent)
753
754 commands.dev = ({vim}) ->
755 vim.window.DeveloperToolbar.show(true) # `true` to focus.
756
757 commands.esc = ({vim}) ->
758 vim._run('esc')
759 utils.blurActiveBrowserElement(vim)
760 vim.window.gBrowser.getFindBar().close()
761 hints.removeHints(vim.window) # Better safe than sorry.
762
763 # Calling `.hide()` when the toolbar is not open can destroy it for the rest
764 # of the Firefox session. The code here is taken from the `.toggle()` method.
765 {DeveloperToolbar} = vim.window
766 if DeveloperToolbar.visible
767 DeveloperToolbar.hide().catch(console.error)
768
769 unless help.getSearchInput(vim.window)?.getAttribute('focused')
770 help.removeHelp(vim.window)
771
772
773
774 module.exports = {
775 commands
776 findStorage
777 }
Imprint / Impressum