A quicker selection of tags

real cool is: when adding an “@” you will get a selection of the default and the tags you have used in your text. But than you have ether to hit the arrow-keys till you get where you want, or you have to go back to text and use the “complete” command, when you don’t want to type it out.

What I intentionally would do is to hit the first letter of the tag to thrill the selection down. Maybe a second or third, to narrow down to the tag I want.

I agree, it would be nicer if it kept open until there was no match. Its on my good list, but the current behavior is actually default OS X autocomplete behavior, so I don’t see this as a strong priority at the moment compared to other things. So yes! But will probably be a while.

Just to add another voice to this discussion. Tagging would be so much more intuitive if the tag list offered some kind of drill-down functionality as Slovi describes. Thanks for considering it, Jesse.

I keep starting to type the first letters of tags, expecting to re-use in-use tags easily by filtering down as described. More like Pinboard.in tags, I suppose, even though their use is slightly different. I would like this feature a lot! :slight_smile:

I know Jesse has said that he’d consider this, but I’m guessing he’s got other more pressing priorities. Does anyone else have any kind of workaround in the meantime?

If Jamie Kowalski’s filter plugin can select operate a filter using the kind of auto-complete we’re looking for, can anyone suggest how difficult it might be to script something that replaces/improves the current auto-complete behaviour?

I think this one is probably the biggest speed-bump in my day-to-day use of FoldingText…

Thanks in advance for any informed responses!

Investigating further— Jamie’s plugin does all of the heavy lifting already, I just need to figure out how to dump the matched tag when enter or tab is pressed. Seems simple enough, except for the fact that I don’t speak .js. So close…

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.
       */

    })
  });
});

Thanks for sharing this. You’re on a roll!