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