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