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