caret の 位置 を hints で 操作

caret が 思うように 移動できなかったので作ってみた。

  • let g:user_caret_target_tags="a,div" (コンマ区切りタグネーム)で 対象タグを設定(即反映)
  • let g:user_caret_mapping_key="x" で hints の 変更(要再起動)
  • hists の 対象は 対象タグを内包しない対象タグで テキストノードを 持つもの
  • caret の 位置は 選択対象の [^\s\n] な 文字の 位置もしくは、最初のテキストノード

あまり意味は無いけど
user_caret_target_tags は Object.prototype.watch() - JavaScript | MDNを使って
xpath を 再構成してみた。
検証系の機能を実装して代入を取り消したりすると便利そうですね。

(function(){
  const global_attr1 = "user_caret_target_tags";
  const global_attr2 = "user_caret_mapping_key";
  const default_tags = ["div","li","td","h1","h2","h3","h4","h5","h6","p","pre","dt"];
  var target ,xpath;
  var mappingKey = liberator.globalVariables[global_attr2] || "c";

  function createXpath(t){
    target = (t && [x.replace(/\s/g,"") for each(x in t.toLowerCase().split(","))])
      || default_tags;
    xpath = "//*[@@cond1][descendant-or-self::text()][count(@@cond2)=0]"
      .replace(/@@cond1/ ,["name(.)='"+t.toUpperCase()+"'" for each(t in target)].join(" or "))
      .replace(/@@cond2/ ,[".//"+t for each(t in target)].join("|"))
    ;
  }

  function watchValue(id, oldval, newval){
    if(oldval !== newval){
      createXpath(newval);
    }
    return newval;
  }

  liberator.globalVariables.unwatch(global_attr1, watchValue);
  liberator.globalVariables.watch(global_attr1, watchValue);

  createXpath(liberator.globalVariables[global_attr1]);

  var offset=0;
  function action(e){
    var ex;
    try{
      var sel = (new XPCNativeWrapper(content.window)).getSelection();
      var doc = e.ownerDocument;
      var range = doc.createRange();
      var e1,e2,offset=0;
      let(it = doc.evaluate(".//text()", e, null, 5, null)){
        let t;
        while(t = it.iterateNext()){
          if(t.parentNode&&t.parentNode.offsetParent){
            if(!e1) e1 = t;
            if((offset=t.textContent.search(/[^\s\n]/))>=0){
              e2 = t;
              break;
            }
          }
        }
      }
      e = e2 || e1 || e;
      if(offset == -1) offset=0;

      if(e){
        range.setStart(e, offset);
        sel.removeAllRanges();
        sel.addRange(range);
        options.setPref("accessibility.browsewithcaret", true);
      }else{
        liberator.echoerr("cannot found text node!!");
      }
    }catch(ex){
      liberator.echoerr(ex);
    }
  }

  hints.addMode(mappingKey,"caret position",function() action.apply(this,arguments),function() xpath);
  mappings.addUserMap([modes.CARET],[";"+mappingKey],"caret position",function() hints.show(mappingKey));
})();