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