# NOTE(smblott). Ultimately, all of the FindMode-related code should be moved here. # This prevents unmapped printable characters from being passed through to underlying page; see #1415. Only # used by PostFindMode, below. class SuppressPrintable extends Mode constructor: (options) -> super options handler = (event) => if KeyboardUtils.isPrintable event then @suppressEvent else @continueBubbling type = document.getSelection().type # We use unshift here, so we see events after normal mode, so we only see unmapped keys. @unshift _name: "mode-#{@id}/suppress-printable" keydown: handler keypress: handler keyup: (event) => # If the selection type has changed (usually, no longer "Range"), then the user is interacting with # the input element, so we get out of the way. See discussion of option 5c from #1415. if document.getSelection().type != type then @exit() else handler event # When we use find, the selection/focus can land in a focusable/editable element. In this situation, special # considerations apply. We implement three special cases: # 1. Disable insert mode, because the user hasn't asked to enter insert mode. We do this by using # InsertMode.suppressEvent. # 2. Prevent unmapped printable keyboard events from propagating to the page; see #1415. We do this by # inheriting from SuppressPrintable. # 3. If the very-next keystroke is Escape, then drop immediately into insert mode. # class PostFindMode extends SuppressPrintable constructor: -> return unless document.activeElement and DomUtils.isEditable document.activeElement element = document.activeElement super name: "post-find" # PostFindMode shares a singleton with focusInput; each displaces the other. singleton: "post-find-mode/focus-input" exitOnBlur: element exitOnClick: true keydown: (event) -> InsertMode.suppressEvent event # Always truthy, so always continues bubbling. keypress: (event) -> InsertMode.suppressEvent event keyup: (event) -> InsertMode.suppressEvent event # If the very-next keydown is Escape, then exit immediately, thereby passing subsequent keys to the # underlying insert-mode instance. @push _name: "mode-#{@id}/handle-escape" keydown: (event) => if KeyboardUtils.isEscape event DomUtils.suppressKeyupAfterEscape handlerStack @exit() @suppressEvent else handlerStack.remove() @continueBubbling class FindMode extends Mode @query: rawQuery: "" matchCount: 0 hasResults: false constructor: (options = {}) -> # Save the selection, so findInPlace can restore it. @initialRange = getCurrentRange() FindMode.query = rawQuery: "" if options.returnToViewport @scrollX = window.scrollX @scrollY = window.scrollY super extend options, name: "find" indicator: false exitOnClick: true exitOnEscape: true # This prevents further Vimium commands launching before the find-mode HUD receives the focus. # E.g. "/" followed quickly by "i" should not leave us in insert mode. suppressAllKeyboardEvents: true HUD.showFindMode this exit: (event) -> super() handleEscapeForFindMode() if event restoreSelection: -> range = @initialRange selection = getSelection() selection.removeAllRanges() selection.addRange range findInPlace: (query) -> # If requested, restore the scroll position (so that failed searches leave the scroll position unchanged). @checkReturnToViewPort() FindMode.updateQuery query # Restore the selection. That way, we're always searching forward from the same place, so we find the right # match as the user adds matching characters, or removes previously-matched characters. See #1434. @restoreSelection() query = if FindMode.query.isRegex then FindMode.getNextQueryFromRegexMatches(0) else FindMode.query.parsedQuery FindMode.query.hasResults = FindMode.execute query @updateQuery: (query) -> @query.rawQuery = query # the query can be treated differently (e.g. as a plain string versus regex depending on the presence of # escape sequences. '\' is the escape character and needs to be escaped itself to be used as a normal # character. here we grep for the relevant escape sequences. @query.isRegex = Settings.get 'regexFindMode' hasNoIgnoreCaseFlag = false @query.parsedQuery = @query.rawQuery.replace /(\\{1,2})([rRI]?)/g, (match, slashes, flag) => return match if flag == "" or slashes.length != 1 switch (flag) when "r" @query.isRegex = true when "R" @query.isRegex = false when "I" hasNoIgnoreCaseFlag = true "" # default to 'smartcase' mode, unless noIgnoreCase is explicitly specified @query.ignoreCase = !hasNoIgnoreCaseFlag && !Utils.hasUpperCase(@query.parsedQuery) regexPattern = if @query.isRegex @query.parsedQuery else Utils.escapeRegexSpecialCharacters @query.parsedQuery # If we are dealing with a regex, grep for all matches in the text, and then call window.find() on them # sequentially so the browser handles the scrolling / text selection. # If we are doing a basic plain string match, we still want to grep for matches of the string, so we can # show a the number of results. try pattern = new RegExp regexPattern, "g#{if @query.ignoreCase then "i" else ""}" catch error return # If we catch a SyntaxError, assume the user is not done typing yet and return quietly. # innerText will not return the text of hidden elements, and strip out tags while preserving newlines. # NOTE(mrmr1993): innerText doesn't include the text contents of s and