1 // Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2010 by anekos <anekos@snca.net>
3 // Copyright (c) 2010-2011 by Kris Maglione <maglione.k at Gmail>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
11 var Abbreviation = Class("Abbreviation", {
12 init: function (modes, lhs, rhs) {
13 this.modes = modes.sort();
18 equals: function (other) this.lhs == other.lhs && this.rhs == other.rhs,
20 expand: function (editor) String(callable(this.rhs) ? this.rhs(editor) : this.rhs),
22 modesEqual: function (modes) array.equals(this.modes, modes),
24 inMode: function (mode) this.modes.some(function (_mode) _mode == mode),
26 inModes: function (modes) modes.some(function (mode) this.inMode(mode), this),
28 removeMode: function (mode) {
29 this.modes = this.modes.filter(function (m) m != mode).sort();
32 get modeChar() Abbreviation.modeChar(this.modes)
34 modeChar: function (_modes) {
35 let result = array.uniq(_modes.map(function (m) m.char)).join("");
42 var AbbrevHive = Class("AbbrevHive", Contexts.Hive, {
43 init: function init(group) {
44 init.superapply(this, arguments);
48 get empty() !values(this._store).nth(util.identity, 0),
51 * Adds a new abbreviation.
53 * @param {Abbreviation} abbr The abbreviation to add.
55 add: function (abbr) {
56 if (!(abbr instanceof Abbreviation))
57 abbr = Abbreviation.apply(null, arguments);
59 for (let [, mode] in Iterator(abbr.modes)) {
60 if (!this._store[mode])
61 this._store[mode] = {};
62 this._store[mode][abbr.lhs] = abbr;
67 * Returns the abbreviation with *lhs* in the given *mode*.
69 * @param {Mode} mode The mode of the abbreviation.
70 * @param {string} lhs The LHS of the abbreviation.
72 get: function (mode, lhs) {
73 let abbrevs = this._store[mode];
74 return abbrevs && set.has(abbrevs, lhs) ? abbrevs[lhs] : null;
78 * @property {Abbreviation[]} The list of the abbreviations merged from
83 let map = values(this._store).map(Iterator).map(iter.toArray)
84 .flatten().toObject();
85 return Object.keys(map).sort().map(function (k) map[k]);
89 * Remove the specified abbreviations.
91 * @param {Array} modes List of modes.
92 * @param {string} lhs The LHS of the abbreviation.
93 * @returns {boolean} Did the deleted abbreviation exist?
95 remove: function (modes, lhs) {
97 for (let [, mode] in Iterator(modes)) {
98 if ((mode in this._store) && (lhs in this._store[mode])) {
100 this._store[mode][lhs].removeMode(mode);
101 delete this._store[mode][lhs];
108 * Removes all abbreviations specified in *modes*.
110 * @param {Array} modes List of modes.
112 clear: function (modes) {
113 for (let mode in values(modes)) {
114 for (let abbr in values(this._store[mode]))
115 abbr.removeMode(mode);
116 delete this._store[mode];
121 var Abbreviations = Module("abbreviations", {
124 // (summarized from Vim's ":help abbreviations")
126 // There are three types of abbreviations.
128 // full-id: Consists entirely of keyword characters.
129 // ("foo", "g3", "-1")
131 // end-id: Ends in a keyword character, but all other
132 // are not keyword characters.
133 // ("#i", "..f", "$/7")
135 // non-id: Ends in a non-keyword character, but the
136 // others can be of any type other than space
140 // Example strings that cannot be abbreviations:
141 // "a.b", "#def", "a b", "_$r"
143 // For now, a keyword character is anything except for \s, ", or '
144 // (i.e., whitespace and quotes). In Vim, a keyword character is
145 // specified by the 'iskeyword' setting and is a much less inclusive
148 // TODO: Make keyword definition closer to Vim's default keyword
149 // definition (which differs across platforms).
151 let params = { // This is most definitely not Vim compatible.
156 this._match = util.regexp(<><![CDATA[
157 (^ | \s | <nonkeyword>) (<keyword>+ )$ | // full-id
158 (^ | \s | <keyword> ) (<nonkeyword>+ <keyword>)$ | // end-id
159 (^ | \s ) (\S* <nonkeyword> )$ // non-id
160 ]]></>, "x", params);
161 this._check = util.regexp(<><![CDATA[
163 <keyword>+ | // full-id
164 <nonkeyword>+ <keyword> | // end-id
165 \S* <nonkeyword> // non-id
167 ]]></>, "x", params);
170 get: deprecated("group.abbrevs.get", { get: function get() this.user.closure.get }),
171 set: deprecated("group.abbrevs.set", { get: function set() this.user.closure.set }),
172 remove: deprecated("group.abbrevs.remove", { get: function remove() this.user.closure.remove }),
173 removeAll: deprecated("group.abbrevs.clear", { get: function removeAll() this.user.closure.clear }),
176 * Returns the abbreviation for the given *mode* if *text* matches the
177 * abbreviation expansion criteria.
179 * @param {Mode} mode The mode to search.
180 * @param {string} text The string to test against the expansion criteria.
182 * @returns {Abbreviation}
184 match: function (mode, text) {
185 let match = this._match.exec(text);
187 return this.hives.map(function (h) h.get(mode, match[2] || match[4] || match[6])).nth(util.identity, 0);
192 * Lists all abbreviations matching *modes* and *lhs*.
194 * @param {Array} modes List of modes.
195 * @param {string} lhs The LHS of the abbreviation.
197 list: function (modes, lhs) {
198 let hives = contexts.allGroups.abbrevs.filter(function (h) !h.empty);
200 function abbrevs(hive)
201 hive.merged.filter(function (abbr) (abbr.inModes(modes) && abbr.lhs.indexOf(lhs) == 0));
204 <tr highlight="Title">
206 <td style="padding-right: 1em;">Mode</td>
207 <td style="padding-right: 1em;">Abbrev</td>
208 <td style="padding-right: 1em;">Replacement</td>
210 <col style="min-width: 6em; padding-right: 1em;"/>
212 template.map(hives, function (hive) let (i = 0)
213 <tr style="height: .5ex;"/> +
214 template.map(abbrevs(hive), function (abbrev)
216 <td highlight="Title">{!i++ ? hive.name : ""}</td>
217 <td>{abbrev.modeChar}</td>
218 <td>{abbrev.lhs}</td>
219 <td>{abbrev.rhs}</td>
221 <tr style="height: .5ex;"/>)
225 // TODO: Move this to an ItemList to show this automatically
226 if (list.*.length() === list.text().length() + 2)
227 dactyl.echomsg(_("abbrev.none"));
229 commandline.commandOutput(list);
234 contexts: function initContexts(dactyl, modules, window) {
235 update(Abbreviations.prototype, {
236 hives: contexts.Hives("abbrevs", AbbrevHive),
237 user: contexts.hives.abbrevs.user
240 completion: function () {
241 completion.abbreviation = function abbreviation(context, modes, group) {
242 group = group || abbreviations.user;
243 let fn = modes ? function (abbr) abbr.inModes(modes) : util.identity;
244 context.keys = { text: "lhs" , description: "rhs" };
245 context.completions = group.merged.filter(fn);
249 commands: function () {
250 function addAbbreviationCommands(modes, ch, modeDescription) {
252 modeDescription = modeDescription ? " in " + modeDescription + " mode" : "";
254 commands.add([ch ? ch + "a[bbreviate]" : "ab[breviate]"],
255 "Abbreviate a key sequence" + modeDescription,
257 let [lhs, rhs] = args;
258 dactyl.assert(!args.length || abbreviations._check.test(lhs),
259 _("error.invalidArgument"));
262 abbreviations.list(modes, lhs || "");
264 if (args["-javascript"])
265 rhs = contexts.bindMacro({ literalArg: rhs }, "-javascript", ["editor"]);
266 args["-group"].add(modes, lhs, rhs);
269 completer: function (context, args) {
270 if (args.length == 1)
271 return completion.abbreviation(context, modes, args["-group"]);
272 else if (args["-javascript"])
273 return completion.javascript(context);
278 contexts.GroupFlag("abbrevs"),
280 names: ["-javascript", "-js", "-j"],
281 description: "Expand this abbreviation by evaluating its right-hand-side as JavaScript"
284 serialize: function () [
287 arguments: [abbr.lhs],
288 literalArg: abbr.rhs,
289 options: callable(abbr.rhs) ? {"-javascript": null} : {}
291 for ([, abbr] in Iterator(abbreviations.user.merged))
292 if (abbr.modesEqual(modes))
296 commands.add([ch + "una[bbreviate]"],
297 "Remove an abbreviation" + modeDescription,
299 util.assert(args.bang ^ !!args[0], _("error.argumentOrBang"));
302 args["-group"].clear(modes);
303 else if (!args["-group"].remove(modes, args[0]))
304 return dactyl.echoerr(_("abbrev.noSuch"));
308 completer: function (context, args) completion.abbreviation(context, modes, args["-group"]),
310 options: [contexts.GroupFlag("abbrevs")]
314 addAbbreviationCommands([modes.INSERT, modes.COMMAND_LINE], "", "");
315 addAbbreviationCommands([modes.INSERT], "i", "insert");
316 addAbbreviationCommands([modes.COMMAND_LINE], "c", "command line");
320 // vim: set fdm=marker sw=4 ts=4 et: