]> git.gir.st - VimFx.git/blob - extension/packages/find.coffee
Closes #95. Search will now focus the element that contains the matching text
[VimFx.git] / extension / packages / find.coffee
1 utils = require 'utils'
2
3 CONTAINER_ID = 'VimFxFindContainer'
4 DIRECTION_FORWARDS = 0
5 DIRECTION_BACKWARDS = 1
6
7 # Create and inserts into DOM find controls and handlers
8 injectFind = (document, cb) ->
9 # Clean up just in case...
10 removeFind document
11
12 # Create container div insert a text input into it
13 [div, input] = createFindContainer(document)
14
15 # Call back in new input
16 input.addEventListener 'input', (event) ->
17 result = cb(input.value)
18 if result
19 utils.removeClass input, 'VimFxNotFound'
20 else
21 utils.addClass input, 'VimFxNotFound'
22
23 # Call back on (Shift)-Enter with proper direction
24 input.addEventListener 'keypress', (event) ->
25 if event.keyCode == event.DOM_VK_RETURN
26 focusSelection(document, Ci.nsISelectionController.SELECTION_FIND)
27 removeFind(document, false)
28
29 document.documentElement.appendChild div
30 input.focus()
31
32 # Removes find controls from DOM
33 removeFind = (document, clear = true) ->
34 if div = document.getElementById CONTAINER_ID
35 document.documentElement.removeChild div
36
37 if clear
38 clearSelection(document.defaultView)
39
40 focusSelection = (document, selectionType) ->
41 if controller = getController(document.defaultView)
42 if selection = controller.getSelection(selectionType)
43 if selection.rangeCount > 0
44 element = selection.getRangeAt(0).commonAncestorContainer?.parentNode
45 if element.focus
46 element.focus()
47
48 createFindContainer = (document) ->
49 div = document.createElement 'div'
50 div.className = 'VimFxReset'
51 div.id = CONTAINER_ID
52
53 input = document.createElement 'input'
54 input.type = 'text'
55 input.className = 'VimFxReset'
56 input.id = 'VimFxFindInput'
57
58 div.appendChild input
59
60 return [ div, input ]
61
62 clearSelection = (window, selectionType = Ci.nsISelectionController.SELECTION_FIND) ->
63 for frame in window.frames
64 clearSelection(frame)
65
66 if controller = getController(window)
67 controller.getSelection(selectionType).removeAllRanges()
68
69 findFactory = (selectionType) ->
70 finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
71 .createInstance()
72 .QueryInterface(Components.interfaces.nsIFind)
73
74 return (window, findStr, findRng = null, direction = DIRECTION_FORWARDS, focus = false) ->
75 # `find` will also recursively search in all frames. # `innerFind` does the work:
76 # searches, selects, scrolls, and optionally reaches into frames
77 innerFind = (window) ->
78 if controller = getController(window)
79 finder.findBackwards = (direction == DIRECTION_BACKWARDS)
80 finder.caseSensitive = (findStr != findStr.toLowerCase())
81
82 searchRange = window.document.createRange()
83 searchRange.selectNodeContents window.document.body
84
85 if findRng and findRng.commonAncestorContainer.ownerDocument == window.document
86 if finder.findBackwards
87 searchRange.setEnd(findRng.startContainer, findRng.startOffset)
88 else
89 searchRange.setStart(findRng.endContainer, findRng.endOffset)
90
91 (startPt = searchRange.cloneRange()).collapse(true)
92 (endPt = searchRange.cloneRange()).collapse(false)
93
94 if finder.findBackwards
95 [startPt, endPt] = [endPt, startPt]
96
97 if range = finder.Find(findStr, searchRange, startPt, endPt)
98 controller.getSelection(selectionType).addRange(range)
99 controller.scrollSelectionIntoView(selectionType, range, Ci.nsISelectionController.SCROLL_CENTER_VERTICALLY)
100 if focus
101 focusSelection(window.document, selectionType)
102
103 return range
104
105 clearSelection(window, selectionType)
106
107 if findStr.length > 0
108 # Get all embedded windows/frames including the passed window
109 wnds = getAllWindows window
110 # In backward searching reverse windows mode so that
111 # it starts off the deepest iframe
112 if finder.findBackwards
113 wnds.reverse()
114
115 # First search in the same window to which current `findRng` belongs
116 if rngWindow = findRng?.commonAncestorContainer.ownerDocument.defaultView
117 wnds = cycleToItem wnds, rngWindow
118
119 for w in wnds
120 if range = innerFind w
121 break
122
123 return if (findStr.length == 0) then true else range
124
125 highlightFactory = (selectionType) ->
126 finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
127 .createInstance()
128 .QueryInterface(Components.interfaces.nsIFind)
129
130 return (window, findStr) ->
131 matchesCount = 0
132 finder.findBackwards = false
133 finder.caseSensitive = (findStr != findStr.toLowerCase())
134
135 innerHighlight = (window) ->
136 if controller = getController(window)
137 searchRange = window.document.createRange()
138 searchRange.selectNodeContents window.document.body
139
140 (startPt = searchRange.cloneRange()).collapse(true)
141 (endPt = searchRange.cloneRange()).collapse(false)
142
143 selection = controller.getSelection(selectionType)
144 while (range = finder.Find(findStr, searchRange, startPt, endPt))
145 selection.addRange(range)
146 matchesCount += 1
147 (startPt = range.cloneRange()).collapse(false)
148
149 # Highlight in iframes
150 for frame in window.frames
151 innerHighlight(frame)
152
153 clearSelection(window, selectionType)
154
155 if findStr.length > 0
156 innerHighlight(window)
157
158 return if (findStr.length == 0) then true else matchesCount
159
160 getController = (window) ->
161 if not window.innerWidth or not window.innerHeight
162 return null
163
164 return window.QueryInterface(Ci.nsIInterfaceRequestor)
165 .getInterface(Ci.nsIWebNavigation)
166 .QueryInterface(Ci.nsIInterfaceRequestor)
167 .getInterface(Ci.nsISelectionDisplay)
168 .QueryInterface(Ci.nsISelectionController)
169
170 # Returns flat list of frmaes within provided window
171 getAllWindows = (window) ->
172 result = [window]
173 for frame in window.frames
174 result = result.concat(getAllWindows(frame))
175
176 return result
177
178 cycleToItem = (array, item) ->
179 if item and array.indexOf(item) != -1
180 while array[0] != item
181 array.push array.shift()
182
183 return array
184
185 exports.injectFind = injectFind
186 exports.removeFind = removeFind
187 exports.find = findFactory(Ci.nsISelectionController.SELECTION_FIND)
188 exports.highlight = highlightFactory(Ci.nsISelectionController.SELECTION_FIND)
189 exports.DIRECTION_FORWARDS = DIRECTION_FORWARDS
190 exports.DIRECTION_BACKWARDS = DIRECTION_BACKWARDS
Imprint / Impressum