]> git.gir.st - VimFx.git/blob - extension/lib/mode-hints/marker.coffee
Fix #249: Support other keyboard layouts than en-US
[VimFx.git] / extension / lib / mode-hints / marker.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2012, 2013.
3 # Copyright Simon Lydell 2013, 2014.
4 #
5 # This file is part of VimFx.
6 #
7 # VimFx is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # VimFx is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
19 ###
20
21 { createElement } = require('../utils')
22 { SerializableBloomFilter
23 , DummyBloomFilter } = require('./bloomfilter')
24 { getPref } = require('../prefs')
25
26 HTMLDocument = Ci.nsIDOMHTMLDocument
27 HTMLAnchorElement = Ci.nsIDOMHTMLAnchorElement
28
29 realBloomFilter = new SerializableBloomFilter('hints_bloom_data', 256 * 32, 16)
30 dummyBloomFilter = new DummyBloomFilter()
31
32 # Wraps the markable element and provides methods to manipulate the markers.
33 class Marker
34 # Creates the marker DOM node.
35 constructor: (@element, @elementShape) ->
36 document = @element.ownerDocument
37 @markerElement = createElement(document, 'div', {class: 'VimFxHintMarker'})
38
39 Object.defineProperty(this, 'bloomFilter',
40 get: ->
41 if getPref('hints_bloom_on')
42 realBloomFilter
43 else
44 dummyBloomFilter
45 )
46
47 show: -> @setVisibility(true)
48 hide: -> @setVisibility(false)
49 setVisibility: (visible) ->
50 @markerElement.classList.toggle('VimFxHiddenHintMarker', not visible)
51 updateVisibility: ->
52 if @hintChars.startsWith(@enteredHintChars) then @show() else @hide()
53
54 # To be called when the marker has been both assigned a hint and inserted
55 # into the DOM, and thus gotten a height and width.
56 setPosition: (viewport) ->
57 {
58 markerElement: { offsetHeight: height, offsetWidth: width }
59 elementShape: { nonCoveredPoint: { x: left, y: top, offset, rect } }
60 } = this
61
62 # Center the marker vertically on the non-covered point.
63 top -= height / 2
64
65 # Make sure that the marker stays within its element (vertically).
66 top = Math.min(top, rect.bottom - height)
67 top = Math.max(top, rect.top)
68
69 # Make the position relative to the top frame.
70 left += offset.left
71 top += offset.top
72
73 # Make sure that the marker stays within the viewport.
74 left = Math.min(left, viewport.right - width)
75 top = Math.min(top, viewport.bottom - height)
76 left = Math.max(left, viewport.left)
77 top = Math.max(top, viewport.top)
78
79 # Make the position relative to the document, rather than to the viewport.
80 left += viewport.scrollX
81 top += viewport.scrollY
82
83 # The positioning is absolute.
84 @markerElement.style.left = "#{ left }px"
85 @markerElement.style.top = "#{ top }px"
86
87 # For quick access.
88 @position = {
89 left, right: left + width,
90 top, bottom: top + height,
91 height, width
92 }
93
94 setHint: (@hintChars) ->
95 # Hint chars that have been matched so far.
96 @enteredHintChars = ''
97
98 document = @element.ownerDocument
99
100 while @markerElement.hasChildNodes()
101 @markerElement.firstChild.remove()
102
103 fragment = document.createDocumentFragment()
104 for char in @hintChars
105 charContainer = createElement(document, 'span')
106 charContainer.textContent = char.toUpperCase()
107 fragment.appendChild(charContainer)
108
109 @markerElement.appendChild(fragment)
110
111 matchHintChar: (char) ->
112 @toggleLastHintChar(true)
113 @enteredHintChars += char.toLowerCase()
114 @updateVisibility()
115
116 deleteHintChar: ->
117 @enteredHintChars = @enteredHintChars[...-1]
118 @toggleLastHintChar(false)
119 @updateVisibility()
120
121 toggleLastHintChar: (visible) ->
122 @markerElement.children[@enteredHintChars.length]
123 ?.classList.toggle('VimFxCharMatch', visible)
124
125 isMatched: ->
126 return @hintChars == @enteredHintChars
127
128 reset: ->
129 @setHint(@hintChars)
130 @show()
131
132 # Returns string features of the element that can be used in the bloom filter
133 # in order to add relevance to the hint marker.
134 extractBloomFeatures: ->
135 features = {}
136
137 # Class name of an element (walks up the node tree to find first element
138 # with at least one class).
139 suffix = ''
140 el = @element
141 while el.classList?.length == 0 and el not instanceof HTMLDocument
142 suffix += " #{ el.tagName }"
143 el = el.parentNode
144 if el?.classList?
145 for className in el.classList
146 features["#{ el.tagName }.#{ className }#{ suffix }"] = 10
147
148 if @element.id
149 features["#{ el.tagName }.#{ @element.id }"] = 5
150
151 if @element instanceof HTMLAnchorElement
152 features['a'] = 20 # Reward links no matter what.
153 features["#{ el.tagName }.#{ @element.href }"] = 60
154 features["#{ el.tagName }.#{ @element.title }"] = 40
155
156 return features
157
158 # Returns rating of all present bloom features (plus 1).
159 calcBloomRating: ->
160 rating = 1
161 for feature, weight of @extractBloomFeatures()
162 rating += if @bloomFilter.test(feature) then weight else 0
163
164 return rating
165
166 reward: ->
167 for feature, weight of @extractBloomFeatures()
168 @bloomFilter.add(feature)
169 @bloomFilter.save()
170
171 exports.Marker = Marker
Imprint / Impressum