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