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