]> git.gir.st - VimFx.git/blob - extension/packages/mode-hints/marker.coffee
Change license to GPLv3
[VimFx.git] / extension / packages / 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 'mode-hints/bloomfilter'
24
25 { getPref } = require 'prefs'
26
27 HTMLDocument = Ci.nsIDOMHTMLDocument
28 HTMLAnchorElement = Ci.nsIDOMHTMLAnchorElement
29
30 realBloomFilter = new SerializableBloomFilter('hints_bloom_data', 256 * 32, 16)
31 dummyBloomFilter = new DummyBloomFilter()
32
33 # Wraps the markable element and provides methods to manipulate the markers.
34 class Marker
35 # Creates the marker DOM node.
36 constructor: (@element, @elementShape) ->
37 document = @element.ownerDocument
38 @markerElement = createElement(document, 'div', {class: 'VimFxHintMarker'})
39
40 Object.defineProperty this, 'bloomFilter',
41 get: -> if getPref('hints_bloom_on') then realBloomFilter else dummyBloomFilter
42
43 show: -> @setVisibility(true)
44 hide: -> @setVisibility(false)
45 setVisibility: (visible) ->
46 @markerElement.classList.toggle('VimFxHiddenHintMarker', !visible)
47 updateVisibility: ->
48 if @hintChars.startsWith(@enteredHintChars) then @show() else @hide()
49
50 # To be called when the marker has been both assigned a hint and inserted
51 # into the DOM, and thus gotten a height and width.
52 setPosition: (viewport) ->
53 {
54 markerElement: { offsetHeight: height, offsetWidth: width }
55 elementShape: { nonCoveredPoint: { x: left, y: top, offset, rect } }
56 } = this
57
58 # Center the marker vertically on the non-covered point.
59 top -= height / 2
60
61 # Make sure that the marker stays within its element (vertically).
62 top = Math.min(top, rect.bottom - height)
63 top = Math.max(top, rect.top)
64
65 # Make the position relative to the top frame.
66 left += offset.left
67 top += offset.top
68
69 # Make sure that the marker stays within the viewport.
70 left = Math.min(left, viewport.right - width)
71 top = Math.min(top, viewport.bottom - height)
72 left = Math.max(left, viewport.left)
73 top = Math.max(top, viewport.top)
74
75 # Make the position relative to the document, rather than to the viewport.
76 left += viewport.scrollX
77 top += viewport.scrollY
78
79 # The positioning is absolute.
80 @markerElement.style.left = "#{ left }px"
81 @markerElement.style.top = "#{ top }px"
82
83 # For quick access.
84 @position = {
85 left, right: left + width,
86 top, bottom: top + height,
87 height, width
88 }
89
90 setHint: (@hintChars) ->
91 # Hint chars that have been matched so far.
92 @enteredHintChars = ''
93
94 document = @element.ownerDocument
95
96 while @markerElement.hasChildNodes()
97 @markerElement.firstChild.remove()
98
99 fragment = document.createDocumentFragment()
100 for char in @hintChars
101 charContainer = createElement(document, 'span')
102 charContainer.textContent = char.toUpperCase()
103 fragment.appendChild(charContainer)
104
105 @markerElement.appendChild(fragment)
106
107 matchHintChar: (char) ->
108 @toggleLastHintChar(true)
109 @enteredHintChars += char.toLowerCase()
110 @updateVisibility()
111
112 deleteHintChar: ->
113 @enteredHintChars = @enteredHintChars[...-1]
114 @toggleLastHintChar(false)
115 @updateVisibility()
116
117 toggleLastHintChar: (visible) ->
118 @markerElement.children[@enteredHintChars.length]
119 ?.classList.toggle('VimFxCharMatch', visible)
120
121 isMatched: ->
122 return @hintChars == @enteredHintChars
123
124 reset: ->
125 @setHint(@hintChars)
126 @show()
127
128 # Returns string features of the element that can be used in the bloom filter
129 # in order to add relevance to the hint marker.
130 extractBloomFeatures: ->
131 features = {}
132
133 # Class name of an element (walks up the node tree to find first element
134 # with at least one class).
135 suffix = ''
136 el = @element
137 while el.classList?.length == 0 and el not instanceof HTMLDocument
138 suffix += " #{ el.tagName }"
139 el = el.parentNode
140 if el?.classList?
141 for className in el.classList
142 features["#{ el.tagName }.#{ className }#{ suffix }"] = 10
143
144 if @element.id
145 features["#{ el.tagName }.#{ @element.id }"] = 5
146
147 if @element instanceof HTMLAnchorElement
148 features["a"] = 20 # Reward links no matter what.
149 features["#{ el.tagName }.#{ @element.href }"] = 60
150 features["#{ el.tagName }.#{ @element.title }"] = 40
151
152 return features
153
154 # Returns rating of all present bloom features (plus 1).
155 calcBloomRating: ->
156 rating = 1
157 for feature, weight of @extractBloomFeatures()
158 rating += if @bloomFilter.test(feature) then weight else 0
159
160 return rating
161
162 reward: ->
163 for feature, weight of @extractBloomFeatures()
164 @bloomFilter.add(feature)
165 @bloomFilter.save()
166
167 exports.Marker = Marker
Imprint / Impressum