Following my last message, I’ve managed to hack Jamie’s filter plugin to autocomplete tags. It’s a real kludge, so while I’m including what I’ve done here, I’m only doing so in the hope that someone who actually understands the code fully can tell me how what I’ve done is akin to a caveman putting a stick of dynamite in the tailpipe of a car to make it go faster, and instead show me a better way.
If you’re not someone who writes code, I ACCEPT NO RESPONSIBILITY if you make your own plugin out of the code below and let it loose on your own files. I’ve seen no problems with it thus far, but hey— ymmv.
Finally, it’s not as tidy as the built-in mechanism, but I’ve wired it into a shift-cmd-2 keypress (i.e. @+cmd) which is close enough for me. And it might be rendered redundant when the next version of Folding Text is released. That said, this was one of only two bottlenecks in my (daily) use of FoldingText, and I’m happy to have found a fix.
UPDATE 2015-02-08: resolved conflict with Jamie’s plugin (I hadn’t defined unique properties for the Extensions.addCommands statements). Also pointed the shortcut at the more appropriate “show autocomplete panel with tags” command, which does pretty much what it says on the tin.
/* ------------------------------------------------------------- *
| Tag Autocomplete for FoldingText 2.0+
| based on Jamie Kowalski's Filter plugin, github.com/jamiekowalski/foldingtext-extra
* ------------------------------------------------------------- */
define(function(require, exports, module) {
'use strict';
// config
var delim = '/', // can be anywhere in expression
filterOnTextChange = true,
debug = false;
// internal variables
var Extensions = require('ft/core/extensions').Extensions,
NodePath = require('ft/core/nodepath').NodePath,
Panel = require('../jmk_panel.ftplugin/jmk_panel.js').Panel,
editor, // this variable is assigned in the 'init' function below
panel,
headingType,
prevNodePath,
prevSelectedRange,
tags,
// tag regexs adapted from Jesse Grosjean's ft/taxonomy/helpers/tagshelper.js
tagStartChars = '[A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD]',
tagWordChars = '[\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]',
tagRegexString = '@((?:' + tagStartChars + '(?:' + tagStartChars + '|' + tagWordChars + ')*)?)',
selectionBug = false,
selectionBugFirstChar,
selectionBugDetermined = false;
// from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
function regexEsc(s) {
return s.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
};
// escape the syntax characters
delim = regexEsc(delim);
function showFilterPanelac( text, selection, selectionEnd ) {
prevNodePath = editor.nodePath();
prevSelectedRange = editor.selectedRange();
tags = editor.tree().tags(true).sort(); // get tags without internal, i.e. 'name'
panel.show( text, selection, selectionEnd );
}
function hideFilterPanelac(panelEle, input) {
if (prevNodePath) {
editor.setNodePath(prevNodePath);
}
if (prevSelectedRange) {
editor.setSelectedRange(prevSelectedRange)
}
panel.hide(false);
}
Extensions.addCommand({
name: 'show autocomplete panel',
performCommand: showFilterPanelac
});
Extensions.addCommand({
name: 'show autocomplete panel with tags',
performCommand: function() {
showFilterPanelac('@', 'end');
panel.input.dispatchEvent(new CustomEvent('input')); // TODO hack
}
});
Extensions.addInit(function(ed) {
editor = ed; // copy editor to wider scope
// TODO This is a hack
if (editor.tree().taxonomy.name === 'markdown') {
headingType = 'heading'
} else {
headingType = 'project'
}
panel = new Panel({
placeholder: 'enter tag...',
onReturn: function(event) {
editor.replaceSelection(panel.input.value);
},
onEscape: function() {
hideFilterPanelac();
},
onTextChange: function(event) {
var cursorPos = panel.selection(event)[1];
var tagMatch = panel.input.value.substring(0, cursorPos).match(new RegExp(tagRegexString + '$'));
if (tagMatch) {
var query = new RegExp('(^|_)' + tagMatch[1], 'i');
panel.showMenu(query, tags); // panel automatically decides when to build
// menu, and when to simply filter
} else {
panel.hideMenu();
if (filterOnTextChange) {
filterByPath(parsePath(panel.input.value));
}
}
},
onMenuSelect: function (event, panel, value) {
var cursorPos = panel.selection()[1];
var start = '';
var postfix = '';
if (event.which === 32) { // space key was used to select item
postfix = ' ';
}
if (value) {
start = panel.input.value.substring(0, cursorPos).
replace(new RegExp(tagRegexString + '$'), '@' + value + postfix);
} else {
start = panel.input.value.substring(0, cursorPos) + postfix; // TODO what case is this for?
}
panel.input.value = start + panel.input.value.substring(cursorPos);
panel.input.setSelectionRange(start.length, start.length);
filterByPath(parsePath(panel.input.value));
},
spaceSelectsMenuItem: true,
ignoreWhiteSpace: false
})
editor.addKeyMap({
"Shift-Cmd-2" : 'show autocomplete panel with tags'
/* Info about keyboard shortcuts
* from http://codemirror.net/doc/manual.html#keymaps
*
* Examples of names defined here are Enter, F5, and Q. These can be
* prefixed with Shift-, Cmd-, Ctrl-, and Alt- (in that order!) to
* specify a modifier. So for example, Shift-Ctrl-Space would be a valid
* key identifier.
*/
})
});
});