古参の野良用法

  1. ◎野良なので、ここの内容は MeryWiki での転載・引用などをお断りします。

    先頭から検索(Ctrl+Alt+F).js
    ----------------------------------------

    // -----------------------------------------------------------------------------
    // 先頭から検索(Ctrl+Alt+F)  by inuuik
    // -----------------------------------------------------------------------------
    
    {
      document.selection.StartOfDocument(false);
      editor.ExecuteCommandByID(2133);
    }
    

    Ctrl+Backspace は標準でキー割り当てがなく DEL (7Fh) コードが入力されます。

    メモ帳は Windows 10 October 2018 Update で「前の語削除」が
    機能追加され Ctrl+BS キーに割り当てられました。
    「前の語削除」は、語の前にある 半角空白とタブ も同時に削除します。

    Mery の「単語左削除」 MEID_EDIT_DELETE_LEFT_WORD = 2224 機能を使い、
    全角空白などの連続も削除する「前の語削除」です。

    前の語削除(Ctrl+BkSp).js
    ----------------------------------------

    // -----------------------------------------------------------------------------
    // 前の語削除(Ctrl+BkSp).js  by inuuik
    // -----------------------------------------------------------------------------
    //   半角空白またはタブの並びと、前の語を削除
    //
    //   メモ帳 October 2018 Update の Ctrl+BkSp 「前の語削除」に相当
    //   Mery 「単語左削除」 MEID_EDIT_DELETE_LEFT_WORD = 2224 の機能補完
    //
    // - ------------------------ --------------------------------------------------
    {
      Redraw = false;
      do {
        var cp = document.selection.GetActivePos();
        if (cp && (/[\t \u00a0\u2000-\u2003\u3000]/.test(document.Text.charAt(cp - 1)))) {
          editor.ExecuteCommandByID(2224);
        }
        else { break; }
      } while (1);
      editor.ExecuteCommandByID(2224);
      Redraw = true;
    }
    

    かなカナ変換補.js
    ----------------------------------------

    // -----------------------------------------------------------------------------
    // かなカナ変換補  by inuuik  2020-03-20
    // -----------------------------------------------------------------------------
    
    out_of: {
      var t = document.selection.Text.replace(/\n$/, "");
      if (t == "") { break out_of; }
      var a = t.split("\n");
      var b = (t.replace(/([ぁ-ゖゝゞ])|([ァ-ヶヽヾ])/g, function(s0, p1, p2) {
        if (p1 !== undefined) { return (String.fromCharCode(p1.charCodeAt(0) + 0x60)); }
        if (p2 !== undefined) { return (String.fromCharCode(p2.charCodeAt(0) - 0x60)); }
        return;
      })).split("\n");
      var r = Status;
    
      Redraw = false;
      try  {
        BeginUndoGroup();
      } catch (e) { ; }
      var fl = (meFindNext | meFindReplaceCase | meFindReplaceOnlyWord | meReplaceSelOnly | meReplaceAll);
      for (var i = 0; i < a.length; i++) {
        var ai = a[i];
        if (ai !== "" && ai !== b[i]) {
          document.selection.Replace(ai, b[i], fl);
          document.HighlightFind = false;
          for (var j = i + 1; j < a.length; j++) {
            if (ai === a[j]) { a[j] = ""; }
          }
        }
      }
      document.selection.Replace("^", "", meFindNext | meFindReplaceCase | meFindReplaceRegExp | meReplaceSelOnly | meReplaceAll);
      document.HighlightFind = false;
      Status = r;
      try {
        EndUndoGroup();
      } catch (e) { ; }
      Redraw = true;
    }
    

    作者様の標準添付マクロに乗っかりました。ご免なさい。

    昇順で並べ替え補.js
    ----------------------------------------

    // -----------------------------------------------------------------------------
    // 昇順で並べ替え補  by inuuik
    // -----------------------------------------------------------------------------
    
    out_of: {
      try {
        var s = document.selection;
        if (s.Mode == meModeBox) {
          var IsBox = true;
          var ap = s.GetAnchorPos();
        }
        if ((s.Mode == meModeMulti && s.Count > 0) || IsBox) {
          BeginUndoGroup();
          editor.ExecuteCommandByID(2254);
          if (! IsBox) {
            s.EndOfLine(false, mePosLogical);
            s.StartOfLine(true, mePosLogical);
          }
          var t = s.Text.replace(/\n$/, "").split("\n");
          for (var i = 0; i < s.Count; i++) {
            if ((s.GetTopPointX(mePosLogical, i) == s.GetBottomPointX(mePosLogical, i)) && t[i] != "") {
              t.splice(i, 0, "");
            }
          }
          t = (t.sort().join("\n")) + "\n";
          ClipboardData.SetData(t);
          s.Delete();
          s.Paste();
          if (! IsBox) {
            s.StartOfLine(true, mePosLogical);
          }
          else {
            s.SetAnchorPos(ap);
            s.Mode = meModeBox;
          }
          EndUndoGroup();
          break out_of;
        }
      }
      catch (e) { ; }
    // -----------------------------------------------------------------------------
    // 昇順で並べ替え
    //
    // Copyright (c) Kuro. All Rights Reserved.
    // www: https://www.haijin-boys.com/
    // -----------------------------------------------------------------------------
    
    if (document.selection.Text == "")
    	document.selection.SelectAll();
    document.selection.Text = document.selection.Text.replace(/\n?$/, "").split("\n").sort().join("\n") + RegExp.lastMatch;
    // -----------------------------------------------------------------------------
    }
    

    降順で並べ替え補.js
    ----------------------------------------

    // -----------------------------------------------------------------------------
    // 降順で並べ替え補  by inuuik
    // -----------------------------------------------------------------------------
    
    out_of: {
      try {
        var s = document.selection;
        if (s.Mode == meModeBox) {
          var IsBox = true;
          var ap = s.GetAnchorPos();
        }
        if ((s.Mode == meModeMulti && s.Count > 0) || IsBox) {
          BeginUndoGroup();
          editor.ExecuteCommandByID(2254);
          if (! IsBox) {
            s.EndOfLine(false, mePosLogical);
            s.StartOfLine(true, mePosLogical);
          }
          var t = s.Text.replace(/\n$/, "").split("\n");
          for (var i = 0; i < s.Count; i++) {
            if ((s.GetTopPointX(mePosLogical, i) == s.GetBottomPointX(mePosLogical, i)) && t[i] != "") {
              t.splice(i, 0, "");
            }
          }
          t = (t.sort(function(a, b){ return ((a < b) ? 1 : ((a > b) ? -1 : 0)) }).join("\n")) + "\n";
          ClipboardData.SetData(t);
          s.Delete();
          s.Paste();
          if (! IsBox) {
            s.StartOfLine(true, mePosLogical);
          }
          else {
            s.SetAnchorPos(ap);
            s.Mode = meModeBox;
          }
          EndUndoGroup();
          break out_of;
        }
      }
      catch (e) { ; }
    // -----------------------------------------------------------------------------
    // 降順で並べ替え
    //
    // Copyright (c) Kuro. All Rights Reserved.
    // www: https://www.haijin-boys.com/
    // -----------------------------------------------------------------------------
    
    if (document.selection.Text == "")
    	document.selection.SelectAll();
    document.selection.Text = document.selection.Text.replace(/\n?$/, "").split("\n").sort(function(a, b){ return ((a < b) ? 1 : ((a > b) ? -1 : 0)) }).join("\n") + RegExp.lastMatch;
    // -----------------------------------------------------------------------------
    }
    
     |  虚inuuik  |  返信
  2. ◎野良なので、ここの内容は MeryWiki での転載・引用などをお断りします。

    README.txt から

    マルチカーソルの使い方

    マルチカーソルの使い方 2
    mery-3-0-0-4.gif

    この画像の複数選択のときの位置表示、範囲統合を少し理解するために

      == Positions in PosView ==
    
    GetActivePointX-Y:  25-15
    GetAnchorPointX-Y:  25-15
    GetTopPointX-Y:     25-15
    GetBottomPointX-Y:  25-15
    GetActivePos:       566
    GetAnchorPos:       566
    ScrollX-Y:          1-1
    
    Lines in Clipboard:  0
    ClipboardData:  74 [h]
    Clipboard newline-terminated:  false
    
    document.Text:  3006 [#]
    
    ActivePos.Text:  566 [載] (8F09)
    AnchorPos.Text:  566 [載] (8F09)
    Active - Anchor:   0
    
    Lines of selection:  1
    selection.Text:  189 [M]
    selection line#1:  23
    selection line#1:  2
    selection IsEmpty:    false
    selection non-box:    true
    selection empty-box:  false
    selection 1x1-box:    false
    
    selection Mode:    (3) meModeMulti
    selection.Count:    18
    
      == Positions in PosLogical ==
    selection[0]  TopPointX-Y:  1-2  BottomPointX-Y:  24-2    len 23
    selection[1]  TopPointX-Y:  1-5  BottomPointX-Y:  1-6  
    selection[2]  TopPointX-Y:  3-7  BottomPointX-Y:  8-7    len 5
    selection[3]  TopPointX-Y:  9-7  BottomPointX-Y:  16-7    len 7
    selection[4]  TopPointX-Y:  35-8  BottomPointX-Y:  37-8    len 2
    selection[5]  TopPointX-Y:  3-9  BottomPointX-Y:  12-9    len 9
    selection[6]  TopPointX-Y:  25-9  BottomPointX-Y:  27-9    len 2
    selection[7]  TopPointX-Y:  3-10  BottomPointX-Y:  12-10    len 9
    selection[8]  TopPointX-Y:  29-10  BottomPointX-Y:  31-10    len 2
    selection[9]  TopPointX-Y:  3-11  BottomPointX-Y:  12-11    len 9
    selection[10]  TopPointX-Y:  17-11  BottomPointX-Y:  32-11    len 15
    selection[11]  TopPointX-Y:  3-12  BottomPointX-Y:  12-12    len 9
    selection[12]  TopPointX-Y:  23-12  BottomPointX-Y:  25-12    len 2
    selection[13]  TopPointX-Y:  3-13  BottomPointX-Y:  12-13    len 9
    selection[14]  TopPointX-Y:  23-13  BottomPointX-Y:  25-13    len 2
    selection[15]  TopPointX-Y:  3-14  BottomPointX-Y:  12-14    len 9
    selection[16]  TopPointX-Y:  23-14  BottomPointX-Y:  25-14    len 2
    selection[17]  TopPointX-Y:  23-15  BottomPointX-Y:  25-15    len 2
    
    
     |  虚inuuik  |  返信
  3. こんばんは、ご無沙汰しております。早速 Mery Ver 3.0.0 をお試しいただきありがとうございます。

    > メモ帳は Windows 10 October 2018 Update で「前の語削除」が機能追加され Ctrl+BS キーに割り当てられました。

    そんな新機能が!最近、メモ帳って地味にパワーアップしてますね。

    目に見えない機能追加って素敵。Mery はついついメニューに項目追加してしまいがちなのでメモ帳先生を見習わないとですね。

    > かなカナ変換補.js

    すごすぎます。

    どういう原理なのかさっぱりわかりませんが、かなカナ変換、滅茶苦茶短いコードなのに複数選択対応していて選択範囲も維持できてる!

    > 昇順で並べ替え補.js
    > 降順で並べ替え補.js

    こちらも複数選択に対応されてる!なんという技術力…。

    > 作者様の標準添付マクロに乗っかりました。ご免なさい。

    標準添付マクロも inuuik 様にお作り頂いたものですから、著作権主張してくださって大丈夫ですよ (w

    > マルチカーソルの使い方 2
    > mery-3-0-0-4.gif

    解析ありがとうございます。

    mery-3-0-0-4.gif のウリとしては、複数選択に矩形選択 (っぽい縦方向に選択) を追加できるところで、これができるエディターは少ないと思います。(最初の一発目は矩形選択できるけど、複数選択と矩形選択は同時に使えないのが多いかなと)

    が…、正直、マルチカーソルの実装は完全にオリジナルなので他のエディターがどうやってるのか知りませんから、何か致命的な問題あるんじゃないかと日々、不安ですね。

     |  Kuro  |  返信
  4. ご返信ありがとうございます。ずっと長い間、使わせていただいてます。
    豊かな発想でどんどん高い次元に昇って行くので、見ていて楽しく、とても素晴らしいです。

    しかし、野良をそんなにおだてると、すぐ木に登って…。

    それと「標準添付マクロも…」などとおっしゃっては誤解を受けます。
    皆さんで知恵を出し合い協力しましたが、私は最後に ? を1文字追加したにすぎません(笑)

    > 複数選択に矩形選択 (っぽい縦方向に選択) を追加できるところで、これができるエディターは少ないと思います。

    ストリームと、論理行にスライスした矩形と、より小さい選択、の組合せで合成したところが、自由度が高いのに操作が容易で、とても面白い実装だと感じました。

    表示はできないものの、編集は矩形で行うマクロを多用していたので、感覚的にとても斬新です。

    ◎野良なので、ここの内容は MeryWiki での転載・引用などをお断りします。

    Mac な人が作ったテキストの濁点を変換するときに使うものです。

    かな濁点変換補.js
    ----------------------------------------

    // -----------------------------------------------------------------------------
    // かな濁点変換補  by inuuik  2020-03-21
    // -----------------------------------------------------------------------------
    
    out_of: {
      /*
        かな濁点付き文字 kana-voiced of
      */
      if (!String.prototype.kana_voiced_of) {
        String.prototype.kana_voiced_of = function(p) {
          var a, da = new Array();
          da = {
            "か":"が", "き":"ぎ", "く":"ぐ", "け":"げ", "こ":"ご", 
            "さ":"ざ", "し":"じ", "す":"ず", "せ":"ぜ", "そ":"ぞ", 
            "た":"だ", "ち":"ぢ", "つ":"づ", "て":"で", "と":"ど", 
            "は":"ば", "ひ":"び", "ふ":"ぶ", "へ":"べ", "ほ":"ぼ", 
            "う":"ゔ", "ゝ":"ゞ", 
            "カ":"ガ", "キ":"ギ", "ク":"グ", "ケ":"ゲ", "コ":"ゴ", 
            "サ":"ザ", "シ":"ジ", "ス":"ズ", "セ":"ゼ", "ソ":"ゾ", 
            "タ":"ダ", "チ":"ヂ", "ツ":"ヅ", "テ":"デ", "ト":"ド", 
            "ハ":"バ", "ヒ":"ビ", "フ":"ブ", "ヘ":"ベ", "ホ":"ボ", 
            "ウ":"ヴ", "ワ":"ヷ", "ヰ":"ヸ", "ヱ":"ヹ", "ヲ":"ヺ", 
            "ヽ":"ヾ"
          };
          var dr = new Array();
          dr = {
            "が":"か", "ぎ":"き", "ぐ":"く", "げ":"け", "ご":"こ", 
            "ざ":"さ", "じ":"し", "ず":"す", "ぜ":"せ", "ぞ":"そ", 
            "だ":"た", "ぢ":"ち", "づ":"つ", "で":"て", "ど":"と", 
            "ば":"は", "び":"ひ", "ぶ":"ふ", "べ":"へ", "ぼ":"ほ", 
            "ゔ":"う", "ゞ":"ゝ",                           
            "ガ":"カ", "ギ":"キ", "グ":"ク", "ゲ":"ケ", "ゴ":"コ", 
            "ザ":"サ", "ジ":"シ", "ズ":"ス", "ゼ":"セ", "ゾ":"ソ", 
            "ダ":"タ", "ヂ":"チ", "ヅ":"ツ", "デ":"テ", "ド":"ト", 
            "バ":"ハ", "ビ":"ヒ", "ブ":"フ", "ベ":"ヘ", "ボ":"ホ", 
            "ヴ":"ウ", "ヷ":"ワ", "ヸ":"ヰ", "ヹ":"ヱ", "ヺ":"ヲ", 
            "ヾ":"ヽ"
          };
          if (p === undefined || p === 1) {  // character entity reference value
            a = this.toString();
            return (a in da ? da[a].toString() : a);
          }
          else if (p === 0) {  // all values or-list (value|value)
            var strRE = "";
            for (var k in da) {
              strRE += (strRE !== "" ? "|" : "") + k;
            }
            return "(" + strRE + ")\u3099";
          }
          else if (p === -1) {  // character entity reference value
            a = this.toString();
            return (a in dr ? dr[a].toString() + "\u3099" : a);
          }
          else if (p === -2) {  // all values or-list (value|value)
            var strRE = "";
            for (var k in dr) {
              strRE += (strRE !== "" ? "|" : "") + k;
            }
            return "(" + strRE + ")";
          }
          else {  // no change
            return this;
          }
        };
      }
    
      /*
        かな半濁点付き文字 kana-semi_voiced of
      */
      if (!String.prototype.kana_semi_voiced_of) {
        String.prototype.kana_semi_voiced_of = function(p) {
          var a, da = new Array();
          da = {
            "は":"ぱ", "ひ":"ぴ", "ふ":"ぷ", "へ":"ぺ", "ほ":"ぽ", 
            "ハ":"パ", "ヒ":"ピ", "フ":"プ", "ヘ":"ペ", "ホ":"ポ"
          };
          var dr = new Array();
          dr = {
            "ぱ":"は", "ぴ":"ひ", "ぷ":"ふ", "ぺ":"へ", "ぽ":"ほ", 
            "パ":"ハ", "ピ":"ヒ", "プ":"フ", "ペ":"ヘ", "ポ":"ホ"
          };
          if (p === undefined || p === 1) {  // character entity reference value
            a = this.toString();
            return (a in da ? da[a].toString() : a);
          }
          else if (p === 0) {  // all values or-list (value|value)
            var strRE = "";
            for (var k in da) {
              strRE += (strRE !== "" ? "|" : "") + k;
            }
            return "(" + strRE + ")\u309A";
          }
          else if (p === -1) {  // character entity reference value
            a = this.toString();
            return (a in dr ? dr[a].toString() + "\u309A" : a);
          }
          else if (p === -2) {  // all values or-list (value|value)
            var strRE = "";
            for (var k in dr) {
              strRE += (strRE !== "" ? "|" : "") + k;
            }
            return "(" + strRE + ")";
          }
          else {  // no change
            return this;
          }
        };
      }
    
      var t = document.selection.Text.replace(/\n$/, "");
      if (t == "") { break out_of; }
      var pn = "(?:" + "".kana_voiced_of(0) + ")|(?:" + "".kana_semi_voiced_of(0) + ")";
      pn += "|(?:" + "".kana_voiced_of(-2) + ")|(?:" + "".kana_semi_voiced_of(-2) + ")";
      var re = new RegExp(pn, "g");
      var a = t.split("\n");
      var b = (t.replace(re, function(s0, p1, p2, p3, p4) {
        if (p1 !== undefined) { return p1.kana_voiced_of(1); }
        if (p2 !== undefined) { return p2.kana_semi_voiced_of(1); }
        if (p3 !== undefined) { return p3.kana_voiced_of(-1); }
        if (p4 !== undefined) { return p4.kana_semi_voiced_of(-1); }
        return;
      })).split("\n");
      var r = Status;
    
      Redraw = false;
      try  {
        BeginUndoGroup();
      } catch (e) { ; }
      var fl = (meFindNext | meFindReplaceCase | meFindReplaceOnlyWord | meReplaceSelOnly | meReplaceAll);
      for (var i = 0; i < a.length; i++) {
        var ai = a[i];
        if (ai !== "" && ai !== b[i]) {
          document.selection.Replace(ai, b[i], fl);
          document.HighlightFind = false;
          for (var j = i + 1; j < a.length; j++) {
            if (ai === a[j]) { a[j] = ""; }
          }
        }
      }
      document.selection.Replace("^", "", meFindNext | meFindReplaceCase | meFindReplaceRegExp | meReplaceSelOnly | meReplaceAll);
      document.HighlightFind = false;
      Status = r;
      try {
        EndUndoGroup();
      } catch (e) { ; }
      Redraw = true;
    }
    

    ----------------------------------------
    これは使う前に、
    表示 - 編集モード - 編集モードの設定
    Text

    プロパティ

    強調 - 強調文字列

    追加

    色 8
    (?:[^\n][\x{3099}\x{309A}]++)
    ☑大文字小文字を区別
    ☑正規表現

    OK

    として、結合文字の濁点と半濁点が、明示できるようにしておかないと、何が起きているか、見てわからないです。
    デフォルトのフォントの BIZ UDゴシック は、結合文字の濁点が表示できないので、このときだけは MS ゴシック など別のフォントにして下さい。
    ----------------------------------------

    ショートカットキーの設定 Shift+Ctrl+L
    ----------------------------------------
    複数選択機能に伴って Ctrl+U が Shift+Ctrl+U に変更されましたが、
    「大文字に変換」の相棒である「小文字に変換」も、お伴したほうが、操作の一貫性が保てるように思います。
    標準設定で Ctrl+L を Shift+Ctrl+L にしませんか?
    ----------------------------------------

    doMultiAction.js についてのご提案
    ----------------------------------------
    doMultiAction.js はじつに良い方法だと思います。
    でも、たとえば、現在の選択位置やカーソル位置から、行末までを削除や書き換えしたとき、次の選択位置が消失や書き換えの影響を受けます。

    selections として保持する ActivePos と AnchorPos をテキスト終端からのマイナス・オフセットで持ちませんか?

    まだ実装された環境がないので、動くコードが作れないのですが、こんな感じ、という参考にご覧下さい。

    doMultiActionA.js (doMultiAction補.js)
    ----------------------------------------

    // -----------------------------------------------------------------------------
    //  doMultiActionA.js  by inuuik
    //  
    //  reverse-selections (終端からの相対位置)
    // -----------------------------------------------------------------------------
    
    // -----------------------------------------------------------------------------
    //  doMultiAction.js
    //
    //  https://www.haijin-boys.com/discussions/5238#discussion-5289
    //  2020年3月19日 21:55  Kuro
    //
    //  [ 呼出し側 ]
    //  #include "doMultiAction.js"
    //  doMultiAction(function() {
    //    
    //    // ここは既存のマクロの内容そのまま
    //    
    //  });
    // -----------------------------------------------------------------------------
    function doMultiAction(fn) {
        var d = document, s = d.selection;
        var tp, bp;
        s.Mode = meModeMulti;
        l = d.Text.length;
        // ① まず、GetActivePos と GetAnchorPos で複数選択のリストを作成します
        var selections = [{ s: (s.GetAnchorPos() - l), e: (s.GetActivePos() - l) }];
        if (s.Count > 0) {
            selections = [];
            for (var i = 0; i < s.Count; i++)
                selections.push({ s: (s.GetActivePos(i) - l), e: (s.GetAnchorPos(i) - l) });
        }
        for (var i = 0; i < selections.length; i++) {
            // 処理後に重複した選択範囲の調整または削除
            if (i > 0) {
                if (selections[i].s < selections[i].e) {
                    if (tp > selections[i].s) {
                        if (tp > selections[i].e) {
                            selections.splice(i--, 1);
                            continue;
                        }
                        selections[i].s = bp;
                    }
                    else if (bp > selections[i].s) {
                        selections[i].s = bp;
                    }
                }
                else {
                    if (tp > selections[i].e) {
                        if (tp > selections[i].s) {
                            selections.splice(i--, 1);
                            continue;
                        }
                        selections[i].e = bp;
                    }
                    else if (bp > selections[i].e) {
                        selections[i].e = bp;
                    }
                }
            }
            // ② そのリストを上から順に SetActivePos で、「シングルカーソル」で範囲選択します。この段階で複数選択は解除され、通常のマクロ操作が可能となります
            s.SetAnchorPos(selections[i].s + l);
            s.SetActivePos(selections[i].e + l, true);
            // ③ <ここで通常のマクロの機能を使って普通に編集などを行います>
            fn();
            // ④ 処理を行った後の選択範囲をリストに反映し、これをリストの最後まで繰り返します
            l = d.Text.length;
            selections[i] = { s: (s.GetAnchorPos() - l), e: (s.GetActivePos() - l) };
            //  
            tp = selections[i].s;
            bp = selections[i].e;
            if (tp >= bp) {
                tp = bp;
                bp = selections[i].s;
            }
        }
        // ⑤ そのリストを上から順に document.selection.Add(StartPos, EndPos) で複数選択として復元します
        for (var i = 0; i < selections.length; i++)
            s.Add((selections[i].s + l), (selections[i].e + l));
    }
    
     |  虚inuuik  |  返信
  5. 長い間、ご愛用いただきありがとうございます。

    そう言っていただけると報われます。

    > それと「標準添付マクロも…」などとおっしゃっては誤解を受けます。

    失礼しました。過去のフォーラムを読み返してみると、みなさんのお力添えがあってのものでしたね。いやぁ、懐かしい~

    > ストリームと、論理行にスライスした矩形と、より小さい選択、の組合せで合成したところが、自由度が高いのに操作が容易で、とても面白い実装だと感じました。

    ありがとうございます。こればっかりは「他のエディターでもこうなっています」と言い訳ができない独自仕様なので、お褒めの言葉をいただけてほっとしました。

    > ショートカットキーの設定 Shift+Ctrl+L

    そうなんですよね、ここは非常に悩んだところです。

    といっても、[小文字に変換] に割り当てようとしていたわけではないのですが…

    従来 (現在でもですが)、Shift + Ctrl + L は [行削除] に割り当てられているのですが、本当にやりたかったのは Microsoft の Visual Studio Code に準拠して Shift + Ctrl + L を [すべて検索して選択] に割り当てたかったのです。

    とりあえず妥協して [すべて検索して選択] は Shift + Ctrl + A に割り当てていますが、これが押しづらくて仕方ない (右側の Shift + Ctrl を使うクセがないもので、左手が釣りそうになりますw)

    Ctrl + A が [すべて選択] なので語呂合わせとして Shift + Ctrl + A は覚えやすいかなという理由は一応考えていますが ^^;

    ただ、ショートカットキー割り当ての変更は問い合わせが来そうだなぁと思って最小限に抑えたかったので、Shift + Ctrl + L は諦め、もう少し優先度の高い Ctrl + U (これは多くのエディターでそうなってるみたいなので) のみ変更ということで落としどころとさせていただいた次第です。

    > selections として保持する ActivePos と AnchorPos をテキスト終端からのマイナス・オフセットで持ちませんか?

    なるほど、確かにこれだとループ中の d.Text.length が 1 つ少なくて済みますね。

    > でも、たとえば、現在の選択位置やカーソル位置から、行末までを削除や書き換えしたとき、次の選択位置が消失や書き換えの影響を受けます。

    そうですよね。doMultiAction.js を貼った段階では、とりあえず選択範囲を変換するマクロを複数選択に対応させるというのが目的だったので、「fn();」の中で改めて選択範囲を変更してから書き換えるというパターンは対応できてないですね。

    ご指摘の通り、「fn();」の中で選択範囲を変更されて、それが別の選択範囲と衝突してしまった場合は選択範囲のリストの調整やら削除やらが必要となりますね。

    ご提案頂いたコード、さすがです。

    選択範囲の調整方法を Mery 側が選択範囲を結合する条件と一致させないといけないので大変だったと思います ^^;

    ちょっと別件でご指摘をいただいたもので、選択範囲の結合条件が変更になる可能性がありますが、現状の仕様だとこの方法で「fn();」の中で何をされても大丈夫っぽいですね。

    ちなみに、↓結合条件の仕様変更が発生する可能性がある件です。
    https://www.haijin-boys.com/discussions/5309

    マイナスオフセット案はイメージということなので重箱の隅をつつくような感じですが、たぶん selections は { s: 選択開始位置, e: 選択終了位置, l: テキストの長さ } で、l も保持しておいて、復元するときにこの l を使わないとズレますよね。

    逆から計算するとコードが直感的に分かりづらいというのはありそうですが、d.Text.length を呼ぶ回数を減らすことはそれだけの価値がある気はします。

    一応、document.TextLength というプロパティも用意しようと思っているので、処理速度的には無視できるレベルになるとは思いますが… ^^;

     |  Kuro  |  返信
  6. こんばんは。ご返信ありがとうございます。

    > といっても、[小文字に変換] に割り当てようとしていたわけではないのですが…

    なるほど、ずっと深いところでの思索を知ることができて、おトボケ質問だった
    のに、すごく得した気分です。

    > マイナスオフセット案はイメージということなので重箱の隅をつつくような感じですが、たぶん selections は { s: 選択開始位置, e: 選択終了位置, l: テキストの長さ } で、l も保持しておいて、復元するときにこの l を使わないとズレますよね。

    > 逆から計算するとコードが直感的に分かりづらいというのはありそうですが、d.Text.length を呼ぶ回数を減らすことはそれだけの価値がある気はします。

    すみません、鋭いご指摘の通り、このままだと後半がすっぽり抜けてました。
    でも l は使わないのです。

    申しわけないことにあまり時間がとれなくて…、説明がうまくまとまらないのですが、
    d.Text.length が少ないことは副産物で、本来の目的は、

    始端 → 前の参照位置 → 編集位置 → 次の参照位置 → 終端

    となっているときに、今の編集が、次の参照点に影響しないようにすることでした。
    そして、すっぽり抜け落ちていたのは、編集が通り過ぎるとき、それより前は、
    マイナス・オフセットからプラスに転換して保持することでした。

    範囲の重複を比較するときに、編集した前の部分は、プラス・オフセットで
    今編集するところは、マイナス・オフセットなので、全体長の l で比較の片方
    だけを補正します。
    l の d.TextLength は、ループの下部で更新するので、次の頭の判断では最新に
    なっています。

    今回、範囲の統合基準が変更されましたが、食い込んでいない隣接ならば、今の
    判断のままでもいけるような気がして…、まだ詰めができてませんが、とりあえず、
    サンプルコードということでご覧ください。

    doMultiAction.js についてのご提案
    ----------------------------------------

    // -----------------------------------------------------------------------------
    //  doMultiAction.js
    //
    //  https://www.haijin-boys.com/discussions/5238#discussion-5289
    //  2020年3月19日 21:55  Kuro
    //
    //  [ 呼出し側 ]
    //  #include "doMultiAction.js"
    //  doMultiAction(function() {
    //    
    //    // ここは既存のマクロの内容そのまま
    //    
    //  });
    // -----------------------------------------------------------------------------
    function doMultiAction(fn) {
        var d = document, s = d.selection;
        var tp, bp;
        s.Mode = meModeMulti;
        l = d.TextLength;
        // ① まず、GetActivePos と GetAnchorPos で複数選択のリストを作成します
        var selections = [{ s: (s.GetAnchorPos() - l), e: (s.GetActivePos() - l) }];
        if (s.Count > 0) {
            selections = [];
            for (var i = 0; i < s.Count; i++)
                selections.push({ s: (s.GetActivePos(i) - l), e: (s.GetAnchorPos(i) - l) });
        }
        for (var i = 0; i < selections.length; i++) {
            // 処理後に重複した選択範囲の調整または削除
            if (i > 0) {
                if (selections[i].s < selections[i].e) {
                    if (tp > (selections[i].s + l)) {
                        if (tp > (selections[i].e + l)) {
                            selections.splice(i--, 1);
                            continue;
                        }
                        selections[i].s = bp - l;
                    }
                    else if (bp > (selections[i].s + l)) {
                        selections[i].s = bp - l;
                    }
                }
                else {
                    if (tp > (selections[i].e + l)) {
                        if (tp > (selections[i].s + l)) {
                            selections.splice(i--, 1);
                            continue;
                        }
                        selections[i].e = bp - l;
                    }
                    else if (bp > (selections[i].e + l)) {
                        selections[i].e = bp - l;
                    }
                }
            }
            // ② そのリストを上から順に SetActivePos で、「シングルカーソル」で範囲選択します。この段階で複数選択は解除され、通常のマクロ操作が可能となります
            s.SetAnchorPos(selections[i].s + l);
            s.SetActivePos(selections[i].e + l, true);
            // ③ <ここで通常のマクロの機能を使って普通に編集などを行います>
            fn();
            // ④ 処理を行った後の選択範囲をリストに反映し、これをリストの最後まで繰り返します
            l = d.TextLength;
            selections[i] = { s: s.GetAnchorPos(), e: s.GetActivePos() };
            //  
            tp = selections[i].s;
            bp = selections[i].e;
            if (tp >= bp) {
                tp = bp;
                bp = selections[i].s;
            }
        }
        // ⑤ そのリストを上から順に document.selection.Add(StartPos, EndPos) で複数選択として復元します
        for (var i = 0; i < selections.length; i++)
            s.AddPos(selections[i].s, selections[i].e);
    }
    

    ----------------------------------------

    単純な行末までの削除などでは、重複した選択範囲を消失させる動作ができましたが、
    まだほとんどテストしていません。

     |  虚inuuik  |  返信
  7. こんばんは。

    少しだけ直しましたが、まだテスト中です。

    doMultiAction.js についてのご提案
    ----------------------------------------

    // -----------------------------------------------------------------------------
    //  doMultiAction.js
    //
    //  https://www.haijin-boys.com/discussions/5238#discussion-5289
    //  2020年3月19日 21:55  Kuro
    //
    //  [ 呼出し側 ]
    //  #include "doMultiAction.js"
    //  doMultiAction(function() {
    //    
    //    // ここは既存のマクロの内容そのまま
    //    
    //  });
    // -----------------------------------------------------------------------------
    function doMultiAction(fn) {
        var d = document, s = d.selection;
        var tp, bp;
        try { var m = s.Mode; tp = s.GetAnchorPos(-1); } catch (e) {  // gate keeper
          fn();
          return;
        }
    //    s.Mode = meModeMulti;
        l = d.TextLength;
        // ① まず、GetActivePos と GetAnchorPos で複数選択のリストを作成します
        var selections = [{ s: (s.GetAnchorPos() - l), e: (s.GetActivePos() - l) }];
        if (s.Count > 0) {
            selections = [];
            for (var i = 0; i < s.Count; i++)
                selections.push({ s: (s.GetAnchorPos(i) - l), e: (s.GetActivePos(i) - l) });
        }
        for (var i = 0; i < selections.length; i++) {
            // 処理後に重複した選択範囲の調整または削除
            if (i > 0) {
                if (selections[i].s < selections[i].e) {
                    if (tp > (selections[i].s + l)) {
                        if (tp > (selections[i].e + l)) {
                            selections.splice(i--, 1);
                            continue;
                        }
                        selections[i].s = bp - l;
                    }
                    else if (bp > (selections[i].s + l)) {
                        selections[i].s = bp - l;
                    }
                }
                else {
                    if (tp > (selections[i].e + l)) {
                        if (tp > (selections[i].s + l)) {
                            selections.splice(i--, 1);
                            continue;
                        }
                        selections[i].e = bp - l;
                    }
                    else if (bp > (selections[i].e + l)) {
                        selections[i].e = bp - l;
                    }
                }
            }
            // ② そのリストを上から順に SetActivePos で、「シングルカーソル」で範囲選択します。この段階で複数選択は解除され、通常のマクロ操作が可能となります
            s.SetAnchorPos(selections[i].s + l);
            s.SetActivePos(selections[i].e + l, true);
            // ③ <ここで通常のマクロの機能を使って普通に編集などを行います>
            fn();
            // ④ 処理を行った後の選択範囲をリストに反映し、これをリストの最後まで繰り返します
            l = d.TextLength;
            selections[i] = { s: s.GetAnchorPos(), e: s.GetActivePos() };
            //  
            tp = selections[i].s;
            bp = selections[i].e;
            if (tp >= bp) {
                tp = bp;
                bp = selections[i].s;
            }
        }
        // ⑤ そのリストを上から順に document.selection.Add(StartPos, EndPos) で複数選択として復元します
        for (var i = 0; i < selections.length; i++) {
            s.AddPos(selections[i].s, selections[i].e);
        }
        s.Mode = m;
    }
    

    ----------------------------------------

    doMultiAction.js の動作例
    ----------------------------------------

    #include "doMultiActionA.js"
    // -----------------------------------------------------------------------------
    // 選択範囲の両端移動補  by inuuik  2020-03-24
    // -----------------------------------------------------------------------------
    
    doMultiAction(function fn() {
      var ap = document.selection.GetAnchorPos();
      var cp = document.selection.GetActivePos();
    //  try { var m = document.selection.Mode; } catch (e) { ; }
      
      document.selection.SetAnchorPos(cp);
      document.selection.SetActivePos(ap, true);
    //  if (m !== undefined && m === meModeBox) { document.selection.Mode = m; }
    });
    

    ----------------------------------------

     |  虚inuuik  |  返信
  8. こんばんは、ご返信ありがとうございます。

    > 今の編集が、次の参照点に影響しないようにすることでした。

    なるほど、確かにおっしゃるとおりでした。

    私も、現在の編集が次の箇所に影響を与えないようにループを逆から回すとかは試していたのですが、これだと結局、最後に選択範囲を復元するために位置の補正が必要なので意味ないなーとか思っていました。

    マイナスオフセットで保持すれば通常のループを使って、なおかつ位置の補正も不要ですね。

    ご提案いただいたコードでいくつかのマクロを試してみましたがうまく動いているようです。

    実はまだ「// 処理後に重複した選択範囲の調整または削除」の部分が完全には把握できていないのですが、勉強のため自分なりの解釈でプラスオフセット版に書き換えてみたらだいぶひどいコードになりました (w

    これは素晴らしいです!マルチカーソルへの対応が一気に捗りそうです。

     |  Kuro  |  返信
  9. 範囲の重複をチェックするなら Anchor/ActivePos を Top/BottomPos に変換してはどうかと考え、手前勝手ながらコードを書いてみました。
    重複した場合の調整は inuuik さんのコードと互換を図ったつもりですが、把握できている自信はないので大丈夫なのかどうか…。

    function doMultiAction(fn) {
      var Doc = Document, Sel = Doc.Selection;
      var mode = Sel.Mode;
      if (mode == null) { fn(); return; } //◆Mery 2 以前
      
      var len = Doc.TextLength;
      var top, btm, anc, act;
      var sels = [];
      for (var i=0, n=Sel.Count||1; i<n; i++) {
        anc = Sel.GetAnchorPos(i), act = Sel.GetActivePos(i);
        sels.push({ anc:anc-len, act:act-len }); // 選択範囲をオフセットで記憶
      }
      BeginUndoGroup();
      for (var i=0, s; s=sels[i]; i++) {
        var invert = (s.act < s.anc); // 選択方向が Bottom to Top か
        if (invert) { s.top = s.act; s.btm = s.anc; }
        else        { s.top = s.anc; s.btm = s.act; }
        s.top += len; s.btm += len;
        
        // 前(i-1)の処理が今(i)の範囲に影響する場合は調整
        // ※初回(i=0)は top,btm=undefined なので false になる
        // s.top < s.btm < top < btm  [今] [前]
        if (s.btm < top) { sels.splice(i--,1); continue; }
        // s.top < top < s.btm < btm  [今 [前 今] 前]
        // top < s.top < s.btm < btm  [前 [今] 前]
        // s.top < top < btm < s.btm  [今 [前] 今]
        // top < s.top < btm < s.btm  [前 [今 前] 今]
        if (s.top < btm) { s.top = btm; }
        // top < btm < s.top < s.btm  [前] [今]
        
        Sel.SetActivePos(invert? s.top: s.btm);
        Sel.SetAnchorPos(invert? s.btm: s.top); // シングル選択
        fn(); // 本処理
        anc = Sel.GetAnchorPos(), act = Sel.GetActivePos();
        sels[i] = { anc:anc, act:act }; // 選択範囲を更新
        len = Doc.TextLength;
        if (act<anc) { top = act; btm = anc; }
        else         { top = anc; btm = act; }
      }
      EndUndoGroup();
      for (var i=0, s; s=sels[i]; i++) {
        Sel.AddPos(s.anc, s.act); // 選択範囲を復元
      }
      Sel.Mode = mode;
    };
    
     |  masme  |  返信
  10. ご返信ありがとうございます。

    > 「意味ないなー…」

    もしかしてご提案した版そのものが、そうかも…、で「没」ということも十分あり得ますので、まだまだ信用してはいけません。

    > …だいぶひどいコードになりました

    そう、これはたぶんこのコードがそうなのです (^^;
    古式ゆかしいスパゲッティ・コードなだけで、高度だから読みにくいわけでは…。

    コンセプトというか、思い付きだけお伝えすれば、きっとスラスラとコードにして下さるに違いない、という期待でお気楽に投稿したもので、まさかこんな状況になるとは、、、ですので、ちょっとも「素晴らし」くはないです(笑)

    テストは遅々として進まずで面目ありません、もうしばしお時間を下さい。

    その前に、3.0.0 のときに作ったお助け道具の「位置表示.js」をご参考に。
    訳の分からん項目があると思いますけれど、適当に読んで下さい。
    長いと画面から切れますが、Enter か Esc で次の「May I Copy it?」に OK するとクリップボードに入ります。

    位置表示(Shift+Ctrl+Alt+P).js
    ----------------------------------------

    // - 糸くす~------------------------------------- Copyright(C)2020-2020 inuuik -
    // 位置表示(Shift+Ctrl+Alt+P).js
    //
    //   マクロで取得される位置を表示
    //   表示座標
    //
    // revised inuuik  2020-02-29 
    // revised inuuik  2020-03-03 AnchorPos.Text
    // revised inuuik  2020-03-04 slice→charAt, length of bottom
    // revised inuuik  2020-03-16 3.0.0 selection.Count selection.Mode
    // revised inuuik  2020-03-17 3.0.0 GetTopPointX(,i) GetBottomPointX(,i)
    // revised inuuik  2020-03-18 copy?
    // revised inuuik  2020-03-19 code point
    // revised inuuik  2020-03-21 yl(sl)
    // - ------------------------ --------------------------------------------------
    {
      var s = document.selection;
      var x = s.GetActivePointX(mePosView);
      var y = s.GetActivePointY(mePosView);
      var ax = s.GetAnchorPointX(mePosView);
      var ay = s.GetAnchorPointY(mePosView);
      var tx = s.GetTopPointX(mePosView);
      var ty = s.GetTopPointY(mePosView);
      var bx = s.GetBottomPointX(mePosView);
      var by = s.GetBottomPointY(mePosView);
      var cp = s.GetActivePos();
      var bp = s.GetAnchorPos();
      var lx = ScrollX;
      var ly = ScrollY;
    
      var c = (ClipboardData.GetData()||"");
      var cl = (c.replace(/[^\n]/g, "")).length;        // lines of clipboard
      var sl = (s.Text.replace(/[^\n]/g, "")).length;   // lines of selection
      var tl = (s.Text.replace(/\n[\s\S]*$/, "")).length;   // length of top line
      var bl = (s.Text.replace(/^[\s\S]*?([^\n]*)\n?$/, "$1")).length;   // length of bottom line
    
      var ce = (/\n$/.test(c));                         // true if clipboard is newline-terminated
    
      var nb = (sl !== (by - ty + 1));                  // true if it is non-box
      var eb = (!nb && s.Text.replace(/\n/g, "") === "");   // true if it is empty-box
      var ab = (s.Text.length === 1);                   // true if it is atom-box (1x1 box)
      var yl = (nb && s.Text.length ? (by - ty + 1) : sl);  // lines of selection non-box adjusted
      var cv = "", cc = "";
      var cv1, cv2;
    
      var sc;
      var sm = "";
      try {
        var sc = s.Count;
        switch (s.Mode) {
        case meModeStream: sm = "meModeStream"; break;
        case meModeBox: sm = "meModeBox"; break;
        case meModeMulti: sm = "meModeMulti"; break;
        }
      }
      catch (e) { ; }
    
      var nl = "\n";
      var outs = "  == Positions in PosView ==" + nl + nl;
    
      outs += "GetActivePointX-Y:  " + x + "-" + y + nl;
      outs += "GetAnchorPointX-Y:  " + ax + "-" + ay + nl;
      outs += "GetTopPointX-Y:     " + tx + "-" + ty + nl;
      outs += "GetBottomPointX-Y:  " + bx + "-" + by + nl;
      outs += "GetActivePos:       " + cp + nl;
      outs += "GetAnchorPos:       " + bp + nl;
      outs += "ScrollX-Y:          " + lx + "-" + ly + nl;
      outs += nl;
      outs += "Lines in Clipboard:  " + cl + nl;
      outs += "ClipboardData:  " + c.length + " [" + c.charAt(0) + "]" + nl;
      outs += "Clipboard newline-terminated:  " + ce + nl;
      outs += nl;
      outs += "document.Text:  " + document.Text.length + " [" + document.Text.charAt(0) + "]" + nl;
      outs += nl;
    
      cv1 = document.Text.charAt(cp).charCodeAt(0);
      if (cv1 >= 0xD800 && cv1 <= 0xDBFF) {
        cv2 = document.Text.charAt(cp + 1).charCodeAt(0);;
        cv = ((((cv1 & 0x000003ff) << 10) | (cv2 & 0x000003ff)) + 0x00010000).toString(16);
        cv = cv.toUpperCase();
        cc = String.fromCharCode(cv1, cv2);
      }
      else {
        cv = cv1.toString(16);
        if (cv.length < 4) { cv = ("0000" + cv.toUpperCase()).slice(-4); }
        else { cv = cv.toUpperCase(); }
        cc = String.fromCharCode(cv1);
      }
      outs += "ActivePos.Text:  " + cp + " [" + cc + "] (" + (document.Text.charAt(cp) === "" ? "EOF" : cv) + ")" + nl;
    
      cv1 = document.Text.charAt(bp).charCodeAt(0);
      if (cv1 >= 0xD800 && cv1 <= 0xDBFF) {
        cv2 = document.Text.charAt(bp + 1).charCodeAt(0);
        cv = ((((cv1 & 0x000003ff) << 10) | (cv2 & 0x000003ff)) + 0x00010000).toString(16);
        cv = cv.toUpperCase();
        cc = String.fromCharCode(cv1, cv2);
      }
      else {
        cv = cv1.toString(16);
        if (cv.length < 4) { cv = ("0000" + cv.toUpperCase()).slice(-4); }
        else { cv = cv.toUpperCase(); }
        cc = String.fromCharCode(cv1);
      }
      outs += "AnchorPos.Text:  " + bp + " [" + cc + "] (" + (document.Text.charAt(bp) === "" ? "EOF" : cv) + ")" + nl;
    
      outs += "Active - Anchor:   " + (cp - bp) + nl;
      outs += nl;
      outs += "Lines of selection:  " + yl + nl;
      outs += "selection.Text:  " + s.Text.length + " [" + s.Text.charAt(0) + "]" + nl;
      outs += "selection line#1:  " + tl + nl;
      outs += "selection line#" + (sl > 0 ? tl + sl - 1 : 1) + ":  " + bl + nl;
      outs += "selection IsEmpty:    " + s.IsEmpty + nl;
      outs += "selection non-box:    " + nb + nl;
      outs += "selection empty-box:  " + eb + nl;
      outs += "selection 1x1-box:    " + ab + nl;
      outs += nl;
      outs += "selection Mode:    " + "(" + s.Mode + ") " + sm + nl;
      outs += "selection.Count:    " + sc + nl;
      if (sm !== "" && sc > 0) {
        var sels = [];
        outs += nl;
        outs += "  == Positions in PosLogical ==" + nl;
        for (i = 0; i < sc; i++) {
          sels.push( {
            tx: s.GetTopPointX(mePosLogical, i), 
            ty: s.GetTopPointY(mePosLogical, i), 
            bx: s.GetBottomPointX(mePosLogical, i), 
            by: s.GetBottomPointY(mePosLogical, i)
          } );
          outs += "selection[" + i + "]  ";
          outs += "TopPointX-Y:  " + sels[i].tx + "-" + sels[i].ty + "  ";
          outs += "BottomPointX-Y:  " + sels[i].bx + "-" + sels[i].by + "  ";
          outs += ((sels[i].ty === sels[i].by) ? "  len " + (sels[i].bx - sels[i].tx) : "") + nl;
        }
      }
      outs += nl;
    
      alert(outs);
    
      if (confirm("May I copy it?")) {
        ClipboardData.SetData(outs);
      }
    }
    // - ------------------------ --------------------------------------------------
    

    ----------------------------------------

    テストの題材として、よく知られている Kuro さん作の「対応する括弧に移動」マクロを勝手に改造して使わせていただきたいです。名前に「補」がついてますが、ご容赦を。

    感覚として、離れたところの選択範囲を増減させる、これの addOne と addTwo でも動くようにしようとしたいのですが、これがなかなかハードルが高くて悶絶中です。
    なにも addOne addTwo を使わなれば、選択範囲が動かないので順調ですが、それだと Kuro さんオリジナル版と同じなので、「意味ないなー…」ですね。

    カーソルが括弧の後でも選択範囲でも反応します。入れ子なしなら引用符にも。エスケープ付きとコメント内の括弧は除外。
    ちなみに「すべて選択」で { を選択して実行すると、最終的に一番外側のブロックを通常選択して終わりますので、括弧の閉じ忘れチェッカーにはなるかと… w

    対応する括弧に移動補.js
    ----------------------------------------

    #include "doMultiActionA.js"
    try { (! doMultiAction) } catch (e) { function doMultiAction(fn) {fn(); return;}; }  // fail-safe
    doMultiAction(function fn() {
    // -----------------------------------------------------------------------------
    // 対応する括弧に移動補
    //
    // Copyright (c) Kuro. All Rights Reserved.
    // www:    http://www.haijin-boys.com/
    // Special Thanks for Kurama さん, Take さん
    // -----------------------------------------------------------------------------
    
    // シフトの状態(trueの場合は選択、falseの場合は移動)
    var shift = true;
    // 前後挿入の切換(trueの場合は起点/終点に挿入)
    var addOne = false;  // 起点
    var addTwo = false;  // 終点
    var ac = " ";   // add char
    // 削除の切換(trueの場合は削除)
    var remove = false;
    // 括弧として認識する文字(エスケープ \ 前置き除外、同一文字組の入れ子なし)
    var lp = "(<[{「『【(《\"\'";
    var rp = ")>]}」』】)》\"\'";
    var ec = "\\";  // escape char
    var cc = "/";   // comment char
    // 描画停止
    Redraw = false;
    // ステータスバーを消去
    Status = "";
    with (document.selection) {
      // カーソル位置を保存
      var cp = GetActivePos();
      // スクロール位置を保存
      var sx = ScrollX;
      var sy = ScrollY;
      // 右から左に探す
      var toLeft = (GetAnchorPos() < cp);  // fail-safe
      // カーソル位置を復元
      SetActivePos(cp, false);
      // 単語を選択
      SelectWord();
      // 現在位置の括弧を取得
      var c1 = Text.charAt(0);
      // 選択範囲を解除
      Collapse(meCollapseStart);
      cp = GetActivePos();
      var l = lp.indexOf(c1);
      var r = rp.indexOf(c1);
      // 括弧がなければ直前の文字を調べる
      if (!(l > -1 || r > -1) && cp > 0) {
        CharLeft(true , 1);
        c1 = Text.charAt(0);
        Collapse(meCollapseEnd);
        cp--;
        l = lp.indexOf(c1);
        r = rp.indexOf(c1);
        toLeft = false;
      }
      // 同一文字組の右か調べる
      if ((l > -1 && r > -1) && (rp.charAt(l) == lp.charAt(r)) && ! toLeft) {
        SetActivePos(cp, false);
        StartOfDocument(true);
        var s = Text.replace(/(?:^[\\t ]*(?:\/\/.*)?$\n)|(?:\/\/.*$)|(?:^[\t ]+)/gm, "");  // no comment
        SetActivePos(cp, false);
        toLeft = false;
        for (var i = s.length - 1; i >= 1; i--) {
          if (s.charAt(i) == c1) {
            if (s.charAt(i - 1) != ec) { toLeft = ! toLeft; }
            else if (i > 1 && s.charAt(i - 2) == ec) { toLeft = ! toLeft; }
          }
        }
        if (s.charAt(0) == c1) { toLeft = ! toLeft; }
      }
      var st = 0;
      if (l > -1 && !toLeft) {
        // 対応する括弧の種類を取得
        var c2 = rp.charAt(l);
        EndOfDocument(true);
        var s = Text;
        // カーソル位置を復元
        SetActivePos(cp, false);
        // スクロール位置を復元
        ScrollX = sx;
        ScrollY = sy;
        var p = cp + 1;
        if (addOne) {
          document.Write(ac);
          CharLeft(false, 1);
          cp++;
          p++;
        }
        if (remove) {
          CharRight(true, 1);
          Delete();
          p--;
        }
        var c0 = "", c9 = "", c8 = "";
        for (var i = 0; i < s.length; c8 = c9, c9 = c0, i++, p++) {
          c0 = s.charAt(i);
          if (c9 == ec && c8 != ec) { continue; }
          if (c9 == cc && c8 == cc) {  // comment remover
            c0 = c9 = c8 = "";
            p++;
            for (var j = ++i; ((s.charAt(j) != "\n") && (j < s.length)); j++, i++, p++) { ; }
            continue;
          }
          if (c0 == c1) {
            st++;
            if (c1 == c2) { c1 = ""; }
          }
          else if (c0 == c2) {
            st--;
          }
          if (st == 0) {
            if (addTwo) {
              SetActivePos(p, false);
              document.Write(ac);
            }
            if (remove) {
              SetActivePos(p - 1, false);
              CharRight(true, 1);
              Delete();
            }
            // カーソル位置を復元
            SetAnchorPos(cp);
            SetActivePos(p - 1, shift);
            // 左に戻る
            break;
          }
        }
      } else if (r > -1) {
        // 対応する括弧の種類を取得
        var c2 = lp.charAt(r);
        CharRight(false, 1);
        StartOfDocument(true);
        var s = Text;
        // カーソル位置を復元
        SetActivePos(cp, false);
        // スクロール位置を復元
        ScrollX = sx;
        ScrollY = sy;
        var p = cp;
        if (addOne) {
          CharRight(false, 1);
          document.Write(ac);
          CharLeft(false, 2);
        }
        if (remove) {
          CharRight(true, 1);
          Delete();
        }
        var c0 = "", c9 = "", c8 = "";
        for (var i = s.length - 1; i >= 0; c8 = c9, (i > 0 ? c9 = s.charAt(--i - 1) : ""), p--) {
          c0 = s.charAt(i);
          if (c9 == ec && c8 != ec) { continue; }
          if (c0 == "\n" && i > 1) {  // comment remover
            for (var j = i - 1; ((s.charAt(j) != "\n") && (j > 1)); j--) {
              if (s.charAt(j - 1) == cc && s.charAt(j) == cc) {
                p -= i - (j - 1);
                i = (j - 1);
                c0 = c9 = c8 = "";
                continue;
              }
            }
          }
          if (c0 == c2) {
            st--;
            if (c1 == c2) { c2 = ""; }
          }
          else if (c0 == c1) {
            st++;
          }
          if (st == 0) {
            if (addTwo) {
              SetActivePos(p, false);
              document.Write(ac);
              cp++;
              p++;
            }
            if (remove) {
              SetActivePos(p, false);
              CharRight(true, 1);
              Delete();
              cp--;
            }
            // カーソル位置を復元
            SetAnchorPos(cp);
            SetActivePos(p, shift);
            // 右に進む
            break;
          }
        }
      } else {
        // 括弧が無い場合は元の位置に戻す
        SetActivePos(cp, false);
        Status = "カーソル位置に括弧が見つかりませんでした";
      }
      if (st != 0) {
        SetActivePos(cp, false);
        Status = "対応する括弧が見つかりませんでした";
      }
    }
    // 描画開始
    Redraw = true;
    });
    

    ----------------------------------------
    ----------------------------------------

    masme さん

    ご自作コードを見せていただきました。ありがとうございます。

    あの私のコードはフニャフニャですので、「互換」などお考えいただくと恐縮です。それよりぜひ、実際にいろいろな選択領域をうまく処理できる方法を、新しく編み出していただき、Kuro さん版を増強して代替できるものが現れることを、期待しております。

    もちろん、コードは入れ替えて使わせていただきました。バッチリ同じようにに動作してます。というかより速いです。コンパクトな記述でうらやましい。自分の判定コードは、いつも試行錯誤中なので、冗長にしておかないと訳が分からないのです(笑)

     |  虚inuuik  |  返信
  11. >> masme さん

    ご協力ありがとうございます。

    すごいシンプル!

    Mery のバージョン判定方法も面白いですね。しかも Undo 処理も組み込まれてて完璧。

    いくつかのマクロで試させていただきましたが問題なく動いているようです。

    蛇足かもしれませんが矩形選択対応と Undo 対応を追加してみました。

    序盤のほうですが、矩形選択のときにうまく動かないようなので、選択モードを強制的に meModeMulti に変更する処理と…

      var Doc = Document, Sel = Doc.Selection;
      // var mode = Sel.Mode;
      // ↑を削除して↓これを追加
      Sel.Mode = meModeMulti;
    

    最後に選択モードを復元する処理は変換系のマクロだと処理後に矩形選択モードを維持し続けることは難しいので何もしない感じに。

        Sel.AddPos(s.anc, s.act); // 選択範囲を復元
      }
      // ↓ これを削除
      // Sel.Mode = mode;
    

    あと、Undo のグループ化が組み込まれていますが、変換系のマクロだと複数選択のときの [元に戻す] で選択範囲が消えてしまうので、以下を追加。

      BeginUndoGroup();
      // ↓これを追加
      AddUndo();
      for (var i=0, s; s=sels[i]; i++) {
    

    これで矩形選択対応と元に戻す対策ができるような気がします。と、新メソッドの AddUndo() を宣伝してみるテストです (w

    >> 虚inuuik さん

    ご返信ありがとうございます。

    > コンセプトというか、思い付きだけお伝えすれば、きっとスラスラとコードにして下さるに違いない

    ありえません (w

    私は根っからの手続き型なので、Mery の内部では goto 文というタブーまで使っている始末。Mery 本体のプログラムのスパゲッティ具合からすると神レベルの美しさです。

    私も時間が取れずマクロ仕様に関してはあまり進捗がないのですが、ご提案いただいたコードは楽しく読まさせていただいてます。

    > テストは遅々として進まずで面目ありません、もうしばしお時間を下さい。
    > 位置表示(Shift+Ctrl+Alt+P).js

    すごい…。Mery 開発のときにデバッグとしても使えそうですコレ。

    用途も含めてじっくりとお話を聞きたいところですが、こんな時期ですからなかなかまとまった時間も取れませんよね。

    > 対応する括弧に移動補.js
    > テストの題材として、よく知られている Kuro さん作の「対応する括弧に移動」マクロを勝手に改造して使わせていただきたいです。

    「対応する括弧に移動」マクロは私ではなく Kurama さんが作成されたものですね。

    当時はまだ Wiki システムというものが知られてなくて、ハンドルネームでも公の場に晒すのは抵抗があった時代でしたから、私の名前で掲載して、スペシャルサンクスでこっそり記載させていただく感じだったのだと思います。
    https://www.haijin-boys.com/discussions/411
    ↑当時のスレですが、Mery 1.0.5 とかの時代… (w

    >> masme さん、虚inuuik さん

    みなさん、Mery のことは二の次、三の次で構いませんのでご自愛くださいませ。

     |  Kuro  |  返信
  12. > 「対応する括弧に移動」マクロは私ではなく Kurama さんが作成されたものですね。

    > 私の名前で掲載して、スペシャルサンクスでこっそり記載させていただく感じだった…

    たいへんに失礼なことをしてしまいました。kurama さん、Kuro さん、ご免なさい。
    でも経緯を知ることができて、とてもよかったです。
    コメントはそのままにしてあったのは、せめてもの幸いでした。
    どうぞこのままで、こっそりここに置かせて下さい。

    take さんの要望 2009年7月29日 16:25
    kurama さんのマクロ作成 2009年8月3日 0:39
    Kuro さんのマクロ更新 2009年8月7日 21:56

    ということで、10年7か月ぶりの同コードベースのリメイクになりました。

    また masme さん版など皆さんの派生版が複数あることは、存じ上げていますが、最初に要件を満たして完成した Kuro さん版は馴染み易く、派生版はスマートな別コードベースだったので、Kuro さん版を元に機能補完しました。

    対応する括弧に移動補.js は Ctrl+] に割り当てて使ってます。自分には実用品です。

    ところで Kuro さんがご指摘の masme さん版 doMultiAction.js の選択モードを保存/復元する処理は、たぶん私の doMultiActionA.js の記述を意識して書かれたのではないかと思いますので、言い訳をします。

    doMultiAction.js を付加するマクロは、通常選択/矩形選択/複数選択のいずれでも起動されることを前提にしました。doMultiAction.js の目的は、複数選択への対応だけだと考えたので、通常選択/矩形選択では、処理を一度だけ行う前後に選択状態の保存/復元だけはするようにしていたのです。

    矩形選択の復元ができないのは、そのマクロの矩形選択への対応がそうなのだから、という視点で、敢えて矩形選択を強制的に複数選択に変換していた Kuro さんオリジナル版の挙動を変更していました。

    自分が矩形選択での動作をするマクロを多用していた関係で、矩形選択の処理が入口で完全に止められてしまうと不便だったのです。非常に手間のかかる矩形選択での処理を好んで行うのは、変り者の野良ユーザぐらいだろうと思います。というわけで、おそらく masme さんはこの部分にこだわりはないでしょう、きっと。

    「位置表示」マクロは、「箱貼り付け」マクロをテストするときに活用しました。

    矩形編集の Kuro さんが実装された仕様とは、少し異世界になります…(笑)
    といっても何を言っているかわからないですよね。

    このマクロには公開していない小さな補助プログラムが必要です。
    この補助プラグラムの機能をマクロ仕様に追加していただけないか、ご提案されていただきます。

    コンセプトをお伝えしますので、どうぞよろしく w

    クリップボードフォーマット追加機能のご提案
    ----------------------------------------
    マクロの機能に次のメソッドを追加していただけると、矩形選択の貼り付けの応用が広がります。

      ClipboardData.AddFormat メソッド    クリップボードにフォーマットを追加設定。 
        構文 
        function AddFormat(
            Format : int
        )
        パラメータ 
        Format クリップボードフォーマット。 
          meClipboardColumn               矩形コピー
          meClipboardLine                 行コピー
    
    --------------------------------------------------------------------------------
    これらは、既存のデータはそのままに、次のフォーマットの空文字列データを追加します。
    消去しなければオーナーが移行しないので、データの追記となることを使います。
    それぞれの複数フォーマットを内部で続けて追加することで、別エディタへの貼り付けにも対応します。
    
    フォーマットの設定は Mery がコピーの過程ですでに実装してある機能相当ではないかと思います。
    
      行コピー
        "MSDEVLineSelect"
        "VisualStudioEditorOperationsLineCutCopyClipboardTag"
    
      矩形コピー
        "MSDEVColumnSelect"
        "TEditor Box Type"
    

    実際にどのように使うのか、その実例として「箱貼り付け(Shift+Alt+B).js」マクロをご案内します。
    補助プログラムがないと機能しませんので、見るだけのコード例としてご覧下さい。

    このマクロで補助プログラムを WScript.Shell の Run で実行していますが、その部分をすべて、この ClipboardData.AddFormat(meClipboardColumn) で置き換えると、マクロだけで処理を完結させることができます。

    たとえば…

    通常選択での貼り付けは、その選択範囲を削除して、その位置に内容を貼り付けます。このときは、削除の手間を省くことが主な目的です。

    矩形選択での貼り付けは、その選択範囲を削除して、その位置に矩形コピーデータを貼り付けます。Mery 3.0.0 で導入された矩形編集では、貼り付けデータが通常コピー(ストリーム)データだと、1行ならば選択範囲の行数分繰り返した内容で矩形として貼り付け、そうでなければそのまま貼り付けます。

    このマクロでは、貼り付ける先に手間のかかる矩形選択をするのは、その行数に大きい意味があると考え、貼り付け内容が矩形コピーデータでも通常コピーデータでも、選択範囲の行数に満たないときは、その行数になるまで繰り返し、行数を超えた部分は除いた内容を矩形貼り付けします。

    この繰り返す処理は、クリップボードの内容を取り出して加工し、またクリップボードに収めていますが、そのままで貼り付けると、通常コピーデータとして貼り付けられてしまいます。そこで、補助プログラムを使って、現在のクリップボードの内容か矩形コピーのフォーマットであることを、追加設定して、矩形貼り付けとなるようにしています。

    テキスト形式であれば、いかなる通常コピーデータも、矩形コピーデータに変更できる、という機能はとても応用範囲が広く、矩形編集をさらに充実させる有用な機能ではないかと思い、この追加をご提案します。

    また、サクラエディタのマクロには、任意のクリップボードフォーマットを設定する関数が用意されています。

    箱貼り付け(Shift+Alt+B).js
    ----------------------------------------

    // - 糸くす~------------------------------------- Copyright(C)2017-2020 inuuik -
    // 箱貼り付け(Shift+Alt+B).js
    //
    //   箱(矩形)として貼り付け
    //     貼り付け後のカーソル位置は範囲の先頭、選択範囲なし
    //     
    //     選択範囲があれば、選択範囲の行数まで繰り返した内容を貼り付け
    //     選択範囲が1行1桁のときは、矩形1行分の内容を置き換えせず貼り付け
    //     (矩形で1行を選択できないので、これを代替にする)
    //     
    //     クリップボードの内容は、「繰り返した内容」に置き換え
    //     連続実行すると、元の選択範囲の行数で、貼り付けを繰り返し
    //     
    //     クリップボードの内容は矩形選択したものでなくてもよい
    //     
    //     クリップボードが空、または改行のみなら、何もしない
    //
    // revised inuuik  2017-06-05 派生
    // revised inuuik  2017-06-05 範囲選択なし/あり カーソル先頭/末尾
    // revised inuuik  2017-07-03 キー変更 Ctrl+Alt+V → Shift+Ctrl+V
    // revised inuuik  2017-07-04 関数 AlterPaste 動作切替
    // revised inuuik  2017-11-03 コメント変更
    // revised inuuik  2019-04-02 コメント変更
    // revised inuuik  2020-01-23 派生【貼り付け範囲選択(Shift+Ctrl+V).js】
    // revised inuuik  2020-01-24 エディタパス cfRect.exe
    // revised inuuik  2020-02-13 repeat による選択範囲内繰り返し
    // revised inuuik  2020-02-14 代替のスクリプトパス 選択範囲上書き貼り付け
    // revised inuuik  2020-02-19 選択が終端非改行または非矩形
    // revised inuuik  2020-02-20 1行1桁選択は上書き貼り付け除外、上書き空白埋め
    // revised inuuik  2020-02-21 条件記述を短縮
    // revised inuuik  2020-03-03 矩形選択↙↗
    // revised inuuik  2020-03-10 null
    // - ------------------------ --------------------------------------------------
    (function BoxPaste() {
      /*
        文字列を N 回繰り返す文字列を生成、-N 回では逆順文字列を繰り返す
      */
      if (!String.prototype.repeat) {
        String.prototype.repeat = function(n) {
          var rep = "";
          var src = this.toString();
          if (n === undefined) { n = 2; }
          if (n < 0) {
            n = - n;
            var rev = "";
            var p = src.length;
            while (--p >= 0) { rev += src.charAt(p); }
            src = rev;
          }
          while (--n >= 0) { rep += src; }
          return rep;
        };
      }
      if (!String.prototype.reverse) {
        String.prototype.reverse = function() { return this.repeat(-1); };
      }
    
      var s = document.selection;
      var tx = s.GetTopPointX(mePosView);
      var ty = s.GetTopPointY(mePosView);
      var by = s.GetBottomPointY(mePosView);
    
      var en = "cfRect.exe";
      var mn = '"' + editor.FullName.replace(/Mery\.exe/i, "") + en + '"';
      var sn = '"' + ScriptFullName.replace(ScriptName, "") + en + '"';
    
      var c = (ClipboardData.GetData()||"");
      var cl = (c.replace(/[^\n]/g, "")).length;        // lines of clipboard
      var sl = (s.Text.replace(/[^\n]/g, "")).length;   // lines of selection
    
      var ce = (/\n$/.test(c));                         // true if clipboard is newline-terminated
    
      var eb = (s.Text.replace(/\n/g, "") === "");      // true if it is empty-box
      var nb = (sl !== (by - ty + 1));                  // true if it is non-box
      var ab = (s.Text.length === 1);                   // true if it is atom-box (1x1 box)
    
      Redraw = false;
      if (c === "" || (c.replace(/[\r\n]/g, "")) === "") {
        quit();
      }
    
      if ((sl === 0 && s.Text.length > 0) || (sl > 0 && nb)) {  // selection within a line, or non-box
        sl++;
      }
      var rn = Math.ceil(sl / (ce ? cl : ++cl));
      var cr = (ce ? c : (c + "\r\n")).repeat(rn);    // to multiply including OVERFLOW
      var crl = cl * rn;
    
      if (crl > sl) {
        var re = new RegExp("(?:[^\\n]*\\n){" + (crl - sl) + "}$", "");
        cr = cr.replace(re, "");    // to adjust by truncating OVERFLOW
      }
      if (cr !== c) {               // when it is adjusted ...
        ClipboardData.SetData(cr);  // replace it for consecutive use of this macro
      }
    
      try {
        var ws = new ActiveXObject("WScript.Shell");
        ws.Run(mn, 0, 1);   // "cfRect.exe" at editor path
      }
      catch(e) {
        try {
          ws.Run(sn, 0, 1);   // "cfRect.exe" at script path
          mn = sn;            // for later use
        }
        catch(e) { ; }
      }
    
      if (!eb && !ab) {     // not empty-box and not atom(1x1)-box
        if (s.OverwriteMode && !nb) {           // not non-box
          var ca = (cr.replace(/\r\n$/, "")).split("\r\n");
          var sa = (s.Text.replace(/\n$/, "")).split("\n");
          var cas = 0;                          // length of padding spaces
          var sal = 0;                          // max length
          for (var i = 0; i < sa.length; i++) {
            if (sa[i].length > sal) {
              sal = sa[i].length;
            }
          }
          for (i = 0; i < ca.length; i++) {
            cas = sal - ca[i].length;
            if (cas > 0) {
              ca[i] += " ".repeat((cas));
            }
          }
          cr = (ca.join("\r\n")) + "\r\n";
          ClipboardData.SetData(cr);
          try {
            ws.Run(mn, 0, 1);   // "cfRect.exe"
          }
          catch(e) { ; }
        }
        s.Delete();                     // instead of override-paste
        s.Collapse(meCollapseStart);    // it is necessary to reset after delete
        tx = s.GetTopPointX(mePosView); // reset
        ty = s.GetTopPointY(mePosView); // reset
      }
      else {
        s.SetActivePoint(mePosView, tx , ty, false);    // avoid to paste into start of line
      }
      ws = null;
    
      s.Paste();
    
      s.SetActivePoint(mePosView, tx , ty, false);
      s = null;
      Redraw = true;
    })();
    

    お邪魔しました。野良のたわごとなので、どうぞ読み飛ばして下さい _o_

     |  虚inuuik  |  返信
  13. どうもシリルです。ちょっと質問があるんです

    // s.top < s.btm < top < btm  [今] [前] →1番
    if (s.btm < top) { sels.splice(i--,1); continue; }
    // s.top < top < s.btm < btm  [今 [前 今] 前] →2番
    // top < s.top < s.btm < btm  [前 [今] 前] →3番
    // s.top < top < btm < s.btm  [今 [前] 今] →4番
    // top < s.top < btm < s.btm  [前 [今 前] 今] →5番
    if (s.top < btm) { s.top = btm; }
    // top < btm < s.top < s.btm  [前] [今] →6番
    

    調整の箇所なんですが便宜的に番号を振らせていただきました

    1番はわかります。前回の範囲によって今回の範囲が完全に消失しているのでリストから削除し、fn()も実行するわけには行かないので次の周回へ

    4番5番もわかります。部分的に消失しているけど残った部分もあるから、補正して新しい範囲で頑張りましょう、fn()へ進んでください

    6番は元気ですね、完全に無事なのでなんの処置もなくfn()へ

    2番3番がわかりません。これらも完全消失じゃないかと思います。btmをs.topへ代入したところでその新しい範囲も重複しているわけですし

    なので私はこうだと思うんです

    // s.top < s.btm < top < btm  [今] [前] →1番→完全消失
    // s.top < top < s.btm < btm  [今 [前 今] 前] →2番→完全消失
    // top < s.top < s.btm < btm  [前 [今] 前] →3番→完全消失
    if (s.btm < btm) { sels.splice(i--,1); continue; }
    // s.top < top < btm < s.btm  [今 [前] 今] →4番→部分残り
    // top < s.top < btm < s.btm  [前 [今 前] 今] →5番→部分残り
    if (s.top < btm) { s.top = btm; }
    // top < btm < s.top < s.btm  [前] [今] →6番→無事
    

    コード的には1つ目の条件文のtopをbtmに差し替えです
    どうでしょうか?誰も指摘していないところを見ると、これまた例のごとく私が間違ってそうですごく怖いですけれど……ビビってます

     |  シリル  |  返信
  14. >> 虚inuuik さん

    > たいへんに失礼なことをしてしまいました。kurama さん、Kuro さん、ご免なさい。

    いいえー、もう 10 年以上前の出来事なので私も記憶が曖昧ですが、そもそも作った記憶がなかったので、あれれおかしいぞと思った次第でした ^^;

    > ということで、10年7か月ぶりの同コードベースのリメイクになりました。

    リメイク、いい響きですね。しかし当時のフォーラム、私のキャラが違いすぎて恥ずかしすぎますねコレ…

    > たぶん私の doMultiActionA.js の記述を意識して書かれたのではないかと思いますので、言い訳をします。

    改めて確認してみると確かにそのような気がしてきました。masme さん、失礼しました。

    > そこで、補助プログラムを使って、現在のクリップボードの内容か矩形コピーのフォーマットであることを、追加設定して、矩形貼り付けとなるようにしています。

    なるほど、矩形コピーの件、わかりました。

    まだ実装していませんが、今回、別件でクリップボードに行コピーの新たな形式、VisualStudioEditorOperationsLineCutCopyClipboardTag を搭載しようとしていまして、それに伴い ClipboardData.SetData() にも行コピー、矩形コピーなどの形式を引数で渡せるようにしようかな、などと思っていたのですが、それだとちょっと遠回りになってしまう感じですかね。

    AddFormat 案は面白いですが、Mery 以外のソフトのクリップボードデータにも影響を及ぼしてしまう恐れがあります。

    Windows のクリップボードの仕様としてはクリップボードを Open してから Close するまでの間に一連の処理を終えるような使い方が前提となっているように見受けられますので、すでにクリップボードに収まっているデータに対して形式を追加する、という処理はあまり好ましくないかもしれません。

    プログラム的には、形式を追加する場合でも再びクリップボードを Open する必要がありまして、これが一度 Open するとそれまでのクリップボードのデータは消えてしまうんです。(消えない方法があるのかもしれませんが…)

    なので、事前にクリップボードのデータをどこかに保存しておいて、Open して改めてそのデータをクリップボードに入れて、なおかつ形式を追加するといった煩雑な過程が必要になる気がするので、シンプルに ClipboardData.SetData() の時点で形式を指定してセットする方式でよければ実装できそうなのですが、いかがでしょうか?

    >> シリル さん

    ご協力ありがとうございます。

    恥ずかしながら、まだ範囲の調整部分についてはみなさんのコードを理解できていません…

    Mery Ver 3.0.2 のリリースとマクロリファレンスの最新版が書けたら、全力で参戦させていただきたいと思います!

     |  Kuro  |  返信
  15. >> シリルさん
    > 誰も指摘していないところを見ると、これまた例のごとく私が間違ってそうですごく怖いですけれど……ビビってます

    いえいえ、こちらとしてはツッコミ待ちの構えでした。
    私が提示したコードは重複判定を単純にすることが主眼だったので、調整処理については脇に置いてまして。
    どう調整するのがベストなのか、マクロや状況で変わるんじゃないか? と悩んだこともあって後回しにしたんです。
    [今 [前 今] 前] のようなコメントを書き残したのは、状況を把握しやすくして調整処理をカスタマイズできるように、という意図もあったので、思うようにカスタマイズしていただければ本望というものです。
    ひとつ補足するなら、[今 [前 今&前] のように位置が重なるケース (s.btm == btm) もあるので、(s.btm <= btm) としたほうがいいでしょうね。

    >> Kuro さん
    > Mery のバージョン判定方法も面白いですね。

    「文字カウント」マクロで使った方法を流用しました。
    Selection.Mode などの新参プロパティは、旧版には存在しないので undefined が返る。→ == null(null/undefined なら true)で判定できる、という理屈です。

    > 蛇足かもしれませんが矩形選択対応と Undo 対応を追加してみました。

    蛇足かも、なんてとんでもない! 特に Undo は気がかりな点だったので、ありがたいです。
    矩形選択時に Selection.Mode = meModeMulti とすると「選択範囲を行で分ける」に近い動作になる(※空行がある場合に差がある)と理解できたのも収穫です(これまではよく分かってなかった)。

    > 新メソッドの AddUndo() を宣伝してみるテストです (w

    AddUndo() の使い方がピンと来てなかったので、宣伝成功ですw
    UndoGroup との合わせ技で選択範囲も一手で戻せるのがいいですね。

    >> 虚inuuik さん
    > それよりぜひ、実際にいろいろな選択領域をうまく処理できる方法を、新しく編み出していただき、Kuro さん版を増強して代替できるものが現れることを、期待しております。

    いやぁ…私は、新しいものを生み出せるタイプじゃなさそうなので、ご期待に添えるかは…。
    マイナス・オフセット案とか思いつきもしませんでしたし。

    > 非常に手間のかかる矩形選択での処理を好んで行うのは、変り者の野良ユーザぐらいだろうと思います。というわけで、おそらく masme さんはこの部分にこだわりはないでしょう、きっと。

    そうですね。元のコードで s.Mode = meModeMulti; がコメントアウトされていた理由まで考えてませんでした。
    Selection.Mode の記憶/復元が行われているので合わせておこうか、くらいの気持ちでしたね。

    矩形選択をそのまま扱いたい場合は、ハナから分岐させたほうが早そうです。
    選択モードの復元は Kuro さんが仰るとおり fn の中身によっては難しいので、fn 側での対応を検討したほうがいいかもしれません。

    function doMultiAction(fn) {
      var Doc = Document, Sel = Doc.Selection;
      if (Sel.Mode == null) { fn(); return; } //◆Mery 2 以前
      if (Sel.Mode == meModeBox) { fn(); Sel.Mode = meModeBox; return; } //◆矩形選択時
    // 以下、後述の改訂版のコードと同じ
    

    改訂版
    ・Kuro さんの案を反映。ついでに UndoGroup の位置を見直し。
    ・【報告】マルチカーソル解除 https://www.haijin-boys.com/discussions/5366 を受けて Selection.Clear() を追加。
    ・重複判定 (s.top < btm) → (s.btm <= btm) に変更。

    function doMultiAction(fn) {
      var Doc = Document, Sel = Doc.Selection;
      if (Sel.Mode == null) { fn(); return; } //◆Mery 2 以前
      
      BeginUndoGroup();
      AddUndo();
      Sel.Mode = meModeMulti; // 矩形選択の場合、複数選択に
      var len = Doc.TextLength;
      var top, btm, anc, act;
      var sels = [];
      for (var i=0, n=Sel.Count||1; i<n; i++) {
        anc = Sel.GetAnchorPos(i), act = Sel.GetActivePos(i);
        sels.push({ anc:anc-len, act:act-len }); // 選択範囲をオフセットで記憶
      }
      Sel.Clear(); // 選択解除
      for (var i=0, s; s=sels[i]; i++) {
        var invert = (s.act < s.anc); // 選択方向が Bottom to Top か
        if (invert) { s.top = s.act; s.btm = s.anc; }
        else        { s.top = s.anc; s.btm = s.act; }
        s.top += len; s.btm += len;
        
        // 前(i-1)の処理が今(i)の範囲に影響する場合は調整
        // ※初回(i=0)は top,btm=undefined なので false になる
        // s.top < s.btm < top < btm  [今] [前]
        // s.top < top < s.btm < btm  [今 [前 今] 前]
        // top < s.top < s.btm < btm  [前 [今] 前]
        if (s.btm <= btm) { sels.splice(i--,1); continue; }
        // s.top < top < btm < s.btm  [今 [前] 今]
        // top < s.top < btm < s.btm  [前 [今 前] 今]
        if (s.top < btm) { s.top = btm; }
        // top < btm < s.top < s.btm  [前] [今]
        
        Sel.SetActivePos(invert? s.top: s.btm);
        Sel.SetAnchorPos(invert? s.btm: s.top); // シングル選択
        fn(); // 本処理
        anc = Sel.GetAnchorPos(), act = Sel.GetActivePos();
        sels[i] = { anc:anc, act:act }; // 選択範囲を更新
        len = Doc.TextLength;
        if (act<anc) { top = act; btm = anc; }
        else         { top = anc; btm = act; }
      }
      for (var i=0, s; s=sels[i]; i++) {
        Sel.AddPos(s.anc, s.act); // 選択範囲を復元
      }
      EndUndoGroup();
    };
    
     |  masme  |  返信
  16. 返信が遅くなってしまい申し訳ございません。

    まだ最新の doMultiAction のコードを読めておらず申し訳ない限りですが、このスレッドの流れを追っていくうえでマクロリファレンスがあると有効だと思いまして Mery Ver 3.0.0 以降用にマクロリファレンスの整備をして参りました。
    https://www.haijin-boys.com/wiki/マクロリファレンス:3

    Mery のソースコードを眺めながらゼロからすべて書き直したのでだいぶ時間がかかってしまいましたが、最新版かつ確実版です。

    > Selection.Mode などの新参プロパティは、旧版には存在しないので undefined が返る。→ == null(null/undefined なら true)で判定できる、という理屈です。

    このアイデアは思い浮かばなかったです。Web 系のアプリだと応用が利きそうなのでメモらせていただきました (w

    > 矩形選択時に Selection.Mode = meModeMulti とすると「選択範囲を行で分ける」に近い動作になる(※空行がある場合に差がある)と理解できたのも収穫です(これまではよく分かってなかった)。

    そうですね、矩形選択も複数選択に分割される感じです。

    [選択範囲を行に分ける] のほうは矩形選択に限らず複数行にわたる通常選択も論理行ごとに複数選択に分割できます。

    > AddUndo() の使い方がピンと来てなかったので、宣伝成功ですw
    > UndoGroup との合わせ技で選択範囲も一手で戻せるのがいいですね。

    ありがとうございます (w

    AddUndo() は他にも例えば、実行するたびに一つ外側の括弧まで選択範囲を広げていくマクロとかで [元に戻す] を使って選択範囲のみの変更を記録できるといった使い方もできますね。

    > 選択モードの復元は Kuro さんが仰るとおり fn の中身によっては難しいので、fn 側での対応を検討したほうがいいかもしれません。

    矩形選択のパターンを考慮すると難しいですね…

    なんだか前回の、虚inuuik さんへの私の返信、とんちんかんなこと言ってるような気がしてきました。出直してきます!

     |  Kuro  |  返信
  17. >> masmeさん
    改訂版、ありがとうございます、早速頂きました

    > ひとつ補足するなら、[今 [前 今&前] のように位置が重なるケース (s.btm == btm) もあるので、(s.btm <= btm) としたほうがいいでしょうね。

    おぉ確かに!流石はmasmeさんです

    > どう調整するのがベストなのか、マクロや状況で変わるんじゃないか?

    そうなんですよね、今回「そういう意図で調整するなら」という前提で質問させて頂き、取り入れても頂きまして嬉し恥ずかしなんですが、マクロによっては不十分な場合も多々あると思います

    私が以前投稿させて頂きました基数変換マクロはこの調整処理が合っても無くても、良い感じにマルチ動作します。(元々、複数の数値には対応してなかったマクロなので、今回のマルカーソル対応で棚ぼた的に、便利になったのでラッキーと思ってます)

    合っても無くても良いならまぁ大は小を兼ねる感じで良いのかな?とも思いますが、マクロによっては違和感を感じるかもしれないと思っていて、「選択範囲が衝突したらどうするか?どこに線を引くか?」という発想なので、必ず線が引かれてしまって、ふわっとドッキングするパターンが排除されてしまっているかなーと。

    じゃぁどうするのかと考えているとプシューと頭が回らなくなります

     |  シリル  |  返信
  18. ご返信がとても遅くなってすみません。Mery 3.0.3 更新ありがとうございます。

    > AddFormat 案は面白いですが、Mery 以外のソフトのクリップボードデータにも影響を及ぼしてしまう恐れがあります。

    了解です。エディタの作者さまに、クリップボードの機能を語ってしまって…100年早いと反省(^^;
    でも斜に見ると、他のアプリが独自形式を設定していても、それを温存できるとも言えます(笑)

    > Windows のクリップボードの仕様としてはクリップボードを Open してから Close するまでの間に一連の処理を終えるような使い方が前提となっているように見受けられますので、すでにクリップボードに収まっているデータに対して形式を追加する、という処理はあまり好ましくないかもしれません。

    4年前に作ったとき、私もそう思い、禁断の技なら秘匿しなければっ、とずっとこっそり使っていたのですが、強者ぞろいのオープンソースであるサクラさんのマクロにあるのを発見して「なんと、オッケーなのか」と考えを改めました。

    > プログラム的には、形式を追加する場合でも再びクリップボードを Open する必要がありまして、これが一度 Open するとそれまでのクリップボードのデータは消えてしまうんです。(消えない方法があるのかもしれませんが…)

    使うには Open しなければなりませんが、Empty にしない限り消えないと思います、実際に使っているので。

    > シンプルに ClipboardData.SetData() の時点で形式を指定してセットする方式でよければ実装できそうなのですが、いかがでしょうか?

    はい、お願いします。言うまでもなく機能デザインは作者さまの特権です。

    でもちなみにww、 サクラ○ディタさんには、形式指定の設定と追加の両方あります。
    --------------------------------------------------------------------------------
    https://sakura-editor.github.io/help/HLP000268.html#SetClipboardByFormat
    ■ function SetClipboardByFormat(str1 :String, str2 :String, int3 :Integer, int4:Integer) :Integer; [↑]
    引数
    str1 データ(文字列)
    str2 クリップボードフォーマット名
    int3 モード(文字コードセット)
    int4 終端モード
    戻り値
    1: 成功
    0: 失敗
    解説
    クリップボードに指定形式でデータを追加します。
    元からあるデータは削除しないので、ClipboardEmptyと併用してください。
    sakura:2.1.0.0以降
    --------------------------------------------------------------------------------
    https://sakura-editor.github.io/help/HLP000268.html#ClipboardEmpty
    ■ function ClipboardEmpty(); [↑]
    解説
    クリップボードを空にします。
    sakura:2.1.0.0以降
    --------------------------------------------------------------------------------
    https://sakura-editor.github.io/help/HLP000268.html#SetClipboard
    ■ function SetClipboard( int1 :Integer, str1 :String ) :Integer; [↑]
    引数
    int1 オプション
    str1 設定する文字列
    戻り値
    0
    解説
    クリップボードに文字列を設定します。
    オプションは以下の通りです。
    0x00 通常コピー
    0x01 矩形選択 (sakura:2.1.0.0以降)
    0x02 ラインモード (sakura:2.1.0.0以降)
    sakura:2.0.3.0以降
    --------------------------------------------------------------------------------
    けっして真似をしようと言っているわけではありません。あくまで「ご参考」に w

    退却します、と宣言したのに、しつこいですね(笑)

    さて、Kuro さんの doMultiAction の改造ですが、自分で置いたハードルが高過ぎて七転八倒しました。あれから大幅にややこしく長大になってしまったものの、あまり実用的にはならず、汎用性は得られませんでした。
    これはむずかしいです。ロースキルなコテコテの部品追加で、難読の極みだと思います。

    そこで Kuro さんと masme さんにお願いなのですが、すでにコンパクトな masme さん版ができているので、そちらをベースにしたこれからのお話は、どうかフォーラムに別のトピックを作り、そこに転記して、共感が得られる形でどんどん続けてください。

    ここに書いたのは、 Kuro さんにわずかでもご参考にしていただければ有難い、カケラだけです。

    ◎野良なので、ここの内容は MeryWiki での転載・引用などをお断りします。

    課題にしたのは、
    選択範囲が次の選択範囲を飛び超えて、包含ではなく、設定されること
    元の選択範囲の前と、新しい選択範囲の後に文字列が加除されること
    …などです。

    制限でわかっているのは、
    文字列の追加や削除と同時に、内容が書き換えられると、処理はしますがカーソル位置が復元できません。

    失礼ながら「対応する括弧に移動.js」 の改造版をふたたび公開しますので kurama さん、Kuro さん、ご免なさい。題材にするのと、エンバグしていたので修正してあります。

    オリジナル版から機能追加したのは、

    行を越えるときの位置ずれ回避
    エスケープ除外
    // コメント内外の対応除外
    引用符と二重引用符に対応
    …など

    以下のように設定項目で、
    範囲選択なし
    前に " (" 2 文字追加
    後ろに ")" 1 文字追加
    削除なし
    としたものを題材にします。
    ----------------------------------------

    // ↓ここから切換設定
    
    // シフトの状態(trueの場合は選択、falseの場合は移動)
    var shift = false;
    
    // 挿入の切換(trueの場合は前/後に挿入)
    var addOne = true;  // 前
    var addTwo = true;  // 後
    var acOne = " (";   // 前文字
    var acTwo = ")";   // 後文字
    
    // 削除の切換(trueの場合は削除、挿入と併用では置換)
    var remove = false;
    
    // ↑ここまで切換設定
    

    ----------------------------------------

    このソースの全体をコピーして新規文書に貼り付け、そこで実行して試して動作を確かめました。

    対応する括弧に移動補.js
    ----------------------------------------

    #include "doMultiActionA.js"
    try { (! doMultiAction) } catch (e) { function doMultiAction(fn) {fn(); return;}; }  // fail-safe
    doMultiAction(function fn() {
    // -----------------------------------------------------------------------------
    // 対応する括弧に移動補
    //
    // Copyright (c) Kuro. All Rights Reserved.
    // www:    http://www.haijin-boys.com/
    // Special Thanks for Kurama さん, Take さん
    // -----------------------------------------------------------------------------
    
    // ↓ここから切換設定
    
    // シフトの状態(trueの場合は選択、falseの場合は移動)
    var shift = true;
    
    // 挿入の切換(trueの場合は前/後に挿入)
    var addOne = false;  // 前
    var addTwo = false;  // 後
    var acOne = " (";   // 前文字
    var acTwo = ") ";   // 後文字
    
    // 削除の切換(trueの場合は削除、挿入と併用では置換)
    var remove = false;
    
    // ↑ここまで切換設定
    
    // -----------------------------------------------------------------------------
    // 括弧として認識する文字(エスケープ \ 前置き除外、同一文字組の入れ子なし)
    var lp = "(<[{「『【(《\"\'";
    var rp = ")>]}」』】)》\"\'";
    var ec = "\\";  // escape char
    var cc = "/";   // comment char
    // 描画停止
    Redraw = false;
    // ステータスバーを消去
    Status = "";
    with (document.selection) {
      // カーソル位置を保存
      var ap = GetAnchorPos();
      var cp = GetActivePos();
      // スクロール位置を保存
      var sx = ScrollX;
      var sy = ScrollY;
      // 右から左に探す
      var toLeft = (GetAnchorPos() < cp - 1);  // fail-safe
      // カーソル位置を復元
      SetAnchorPos(ap + 1 == cp ? ap : cp);
      SetActivePos(cp, true);
      // 単語を選択
      SelectWord();
      // 現在位置の括弧を取得
      var c1 = Text.charAt(0);
      // 選択範囲を解除
      var l = lp.indexOf(c1);
      var r = rp.indexOf(c1);
      if (l > -1 || r > -1) {
        Collapse(meCollapseStart);
        cp = GetActivePos();
      }
      else {
        SetActivePos(cp, false);
      }
      // 括弧がなければ直前の文字を調べる
      if (!(l > -1 || r > -1) && cp > 0) {
        CharLeft(true , 1);
        c1 = Text.charAt(0);
        Collapse(meCollapseEnd);
        l = lp.indexOf(c1);
        r = rp.indexOf(c1);
        if (l > -1 || r > -1) {
          cp--;
          toLeft = false;
        }
      }
      if (l > -1 && r < 0) { toLeft = false; }
      // 同一文字組の右か調べる
      if ((l > -1 && r > -1) && (rp.charAt(l) == lp.charAt(r)) && ! toLeft) {
        SetActivePos(cp, false);
        StartOfDocument(true);
        var s = Text.replace(/(?:^[\\t ]*(?:\/\/.*)?$\n)|(?:\/\/.*$)|(?:^[\t ]+)/gm, "");  // no comment
        SetActivePos(cp, false);
        toLeft = false;
        for (var i = s.length - 1; i >= 1; i--) {
          if (s.charAt(i) == c1) {
            if (s.charAt(i - 1) != ec) { toLeft = ! toLeft; }
            else if (i > 1 && s.charAt(i - 2) == ec) { toLeft = ! toLeft; }
          }
        }
        if (s.charAt(0) == c1) { toLeft = ! toLeft; }
      }
      var st = 0;
      if (l > -1 && !toLeft) {
        // 対応する括弧の種類を取得
        var c2 = rp.charAt(l);
        EndOfDocument(true);
        var s = Text;
        // カーソル位置を復元
        SetActivePos(cp, false);
        // スクロール位置を復元
        ScrollX = sx;
        ScrollY = sy;
        var p = cp + 1;
        if (addOne) {
          document.Write(acOne);
          cp += acOne.length;
          p += acOne.length;
        }
        if (remove) {
          CharRight(true, 1);
          Delete();
          p--;
        }
        var c0 = "", c9 = "", c8 = "";
        for (var i = 0; i < s.length; c8 = c9, c9 = c0, i++, p++) {
          c0 = s.charAt(i);
          if (c9 == ec && c8 != ec) { continue; }
          if (c9 == cc && c8 == cc) {  // comment remover
            c0 = c9 = c8 = "";
            p++;
            for (var j = ++i; ((s.charAt(j) != "\n") && (j < s.length)); j++, i++, p++) { ; }
            continue;
          }
          if (c0 == c1) {
            st++;
            if (c1 == c2) { c1 = ""; }
          }
          else if (c0 == c2) {
            st--;
          }
          if (st == 0) {
            if (addTwo) {
              SetActivePos(p, false);
              document.Write(acTwo);
            }
            if (remove) {
              SetActivePos(p - 1, false);
              CharRight(true, 1);
              Delete();
            }
            // カーソル位置を復元
            SetAnchorPos(cp);
            SetActivePos(p - 1, shift);
            // 左に戻る
            break;
          }
        }
      } else if (r > -1) {
        // 対応する括弧の種類を取得
        var c2 = lp.charAt(r);
        CharRight(false, 1);
        StartOfDocument(true);
        var s = Text;
        // カーソル位置を復元
        SetActivePos(cp, false);
        // スクロール位置を復元
        ScrollX = sx;
        ScrollY = sy;
        var p = cp;
        if (addTwo) {
          CharRight(false, 1);
          document.Write(acTwo);
          CharLeft(false, acTwo.length + 1);
        }
        if (remove) {
          CharRight(true, 1);
          Delete();
        }
        var c0 = "", c9 = "", c8 = "";
        for (var i = s.length - 1; i >= 0; c8 = c9, (i > 0 ? c9 = s.charAt(--i - 1) : ""), p--) {
          c0 = s.charAt(i);
          if (c9 == ec && c8 != ec) { continue; }
          if (c0 == "\n" && i > 1) {  // comment remover
            for (var j = i - 1; ((s.charAt(j) != "\n") && (j > 1)); j--) {
              if (s.charAt(j - 1) == cc && s.charAt(j) == cc) {
                p -= i - (j - 1);
                i = (j - 1);
                c0 = c9 = c8 = "";
                continue;
              }
            }
          }
          if (c0 == c2) {
            st--;
            if (c1 == c2) { c2 = ""; }
          }
          else if (c0 == c1) {
            st++;
          }
          if (st == 0) {
            if (addOne) {
              SetActivePos(p, false);
              document.Write(acOne);
              cp += acOne.length;
              p += acOne.length;
            }
            if (remove) {
              SetActivePos(p, false);
              CharRight(true, 1);
              Delete();
              cp--;
            }
            // カーソル位置を復元
            SetAnchorPos(cp);
            SetActivePos(p, shift);
            // 右に進む
            break;
          }
        }
      } else {
        // 括弧が無い場合は元の位置に戻す
        SetActivePos(cp, false);
        Status = "カーソル位置に括弧が見つかりませんでした";
      }
      if (st != 0) {
        SetActivePos(cp, false);
        Status = "対応する括弧が見つかりませんでした";
      }
    }
    // 描画開始
    Redraw = true;
    });
    

    ----------------------------------------
    ----------------------------------------

    これも題材にしました。

    前の語削除(Ctrl+BkSp).js
    ----------------------------------------

    #include "doMultiActionA.js"
    try { (! doMultiAction) } catch (e) { function doMultiAction(fn) {fn(); return;}; }  // fail-safe
    doMultiAction(function fn() {
    // ---------------------------------------------- Copyright(C)2020-2020 inuuik -
    // 前の語削除(Ctrl+BkSp).js
    //   
    //   半角空白またはタブの並びと、前の語を削除
    //
    //   メモ帳 October 2018 Update の Ctrl+BkSp 「前の語削除」に相当
    //   Mery 「単語左削除」 MEID_EDIT_DELETE_LEFT_WORD = 2224 の機能補完
    //
    //   00A0;NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;NON-BREAKING SPACE;;;;( )
    //   2000;EN QUAD;Zs;0;WS;2002;;;;N;;;;;( )
    //   2001;EM QUAD;Zs;0;WS;2003;;;;N;;;;;( )
    //   2002;EN SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;( )
    //   2003;EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;( )
    //   3000;IDEOGRAPHIC SPACE;Zs;0;WS;<wide> 0020;;;;N;;;;;( )
    //
    //   テスト例:
    //   aa  		            bb              cc
    //   aa  		            bb              
    //
    // revised inuuik  2020-02-29 作成
    // revised inuuik  2020-03-02 上記の空白文字の連続を削除
    // revised inuuik  2020-03-11 判定変更
    // revised inuuik  2020-03-31 doMultiActionA.js
    // - ------------------------ --------------------------------------------------
    {
      Redraw = false;
      do {
        var cp = document.selection.GetActivePos();
        if (cp && (/[\t \u00a0\u2000-\u2003\u3000]/.test(document.Text.charAt(cp - 1)))) {
          editor.ExecuteCommandByID(2224);  // MEID_EDIT_DELETE_LEFT_WORD
        }
        else { break; }
      } while (1);
      editor.ExecuteCommandByID(2224);  // MEID_EDIT_DELETE_LEFT_WORD
      Redraw = true;
    }
    });
    

    ----------------------------------------
    ----------------------------------------

    これが doMultiAction.js の改造版 doMultiActionA.js です。

    マイナス・オフセットは、前から順に編集を進めるのに使えますが、順序が前後すると対応できませんでした。選択範囲とカーソル位置が、次のカーソルを飛び越えるマクロの動作にもある程度は追従するようにしました。
    その他も変更点が多過ぎ… (^^;;;

    底辺レベルでコーディングは下手なので、スタイルを大幅に書き換えられた版を見ても、確実に理解できません。ヒゲの海賊の樽に剣を刺されるのと同じ思いですので、どうぞお手柔らかに。

    doMultiActionA.js
    ----------------------------------------

    // -----------------------------------------------------------------------------
    //  doMultiActionA.js  by inuuik
    // -----------------------------------------------------------------------------
    // -----------------------------------------------------------------------------
    //  doMultiAction.js
    //
    //  https://www.haijin-boys.com/discussions/5238#discussion-5289
    //  2020年3月19日 21:55  Kuro
    //
    //  [ 呼出し側 ]
    //  #include "doMultiAction.js"
    //  doMultiAction(function() {
    //    
    //    // ここは既存のマクロの内容そのまま
    //    
    //  });
    // -----------------------------------------------------------------------------
    function doMultiAction(fn) {
        var d = document, s = d.selection;
        var tp, bp;
        try { var m = s.Mode; tp = s.GetAnchorPos(-1); AddUndo(); } catch (e) {  // gate keeper
          fn();
          return;
        }
        var l = d.TextLength;
        var l9, lx;  // previous l, gap of l
        var ap, cp;
        var sp, ep, tp9, tp0;
        var sx, ex;
        var t, t0;
    
        // ① まず、GetActivePos と GetAnchorPos で複数選択のリストを作成します
        var selections = [{ s: (tp = s.GetAnchorPos() - l), e: (bp = s.GetActivePos() - l), t: tp, b: bp, s0: s.GetAnchorPos(), e0: s.GetActivePos() }];
        if (tp > bp) {
            selections.t = tp = bp;
            selections.b = bp = selections.s;
        }
        if (s.Count > 0) {
            selections = [];
            for (var i = 0; i < s.Count; i++) {
                selections.push({ s: (tp = s.GetAnchorPos(i) - l), e: (bp = s.GetActivePos(i) - l), t: tp, b: bp, s0: s.GetAnchorPos(), e0: s.GetActivePos() });
                if (tp > bp) {
                    selections[i].t = tp = bp;
                    selections[i].b = bp = selections[i].s;
                }
            }
        }
        //
        s.Clear();
        // 
        for (var i = 0; i < selections.length; i++) {
            // 処理後に重複した選択範囲の調整または削除
            if (i > 0) {
                if (selections[i].s <= selections[i].e) {
                    if (tp <= (selections[i].s + l)) {
                        if (bp > (selections[i].s + l)) {
                            if (bp >= (selections[i].e + l)) {
                                selections.splice(i--, 1);
                                continue;
                            }
                            else {
                                selections[i].s = bp - l;
                            }
                        }
                    }
                    else if (bp <= (selections[i].e + l)) {
                        selections[i].e = bp - l;
                    }
                }
                else {
                    if (tp <= (selections[i].e + l)) {
                        if (bp > (selections[i].e + l)) {
                            if (bp >= (selections[i].s + l)) {
                                selections.splice(i--, 1);
                                continue;
                            }
                            else {
                                selections[i].e = bp - l;
                            }
                        }
                    }
                    else if (bp <= (selections[i].s + l)) {
                        selections[i].s = bp - l;
                    }
                }
            }
            // ② そのリストを上から順に SetActivePos で、「シングルカーソル」で範囲選択します。この段階で複数選択は解除され、通常のマクロ操作が可能となります
            sp = selections[i].s + l;
            ep = selections[i].e + l;
            tp9 = selections[i].t + l;   // top position for gap detection
            // 
            s.SetAnchorPos(sp);
            s.SetActivePos(ep, true);
            // 
            AddUndo();
            BeginUndoGroup();
            // ③ <ここで通常のマクロの機能を使って普通に編集などを行います>
            fn();
            // 
            ap = tp = s.GetAnchorPos();
            cp = bp = s.GetActivePos();
            if (tp > bp) {
                tp = bp;  // top(leftside)
                bp = ap;  // bottom(rightside)
            }
            tp0 = tp;     // after
            // 
            EndUndoGroup();   // nest stopper
            EndUndoGroup();
            AddUndo();
            // 
            l9 = l;
            l = d.TextLength;
            lx = l - l9;
            // reordering of jumped over selections
            if ((i < (selections.length - 1)) && ((selections[i + 1].s + l) <= tp0)) {
                // [i] から tp0 まで、[i] は sp ep
                postponeAction();
                // shifter to adjust before
                shiftBefore();
                //
                continue;
            }
            // ④ 処理を行った後の選択範囲をリストに反映し、これをリストの最後まで繰り返します
            selections[i] = { s: ap, e: cp, t: tp, b: bp, s0: ap, e0: cp };
            // shifter to adjust before
            shiftBefore();
        }
    //    var t = "";
        // ⑤ そのリストを上から順に document.selection.Add(StartPos, EndPos) で複数選択として復元します
        for (var i = 0; i < selections.length; i++) {
            s.AddPos(selections[i].s, selections[i].e);
    //        t += "[" + i + "]: " + selections[i].s + " " + selections[i].e + "\n";
        }
    //    alert(t);
        // 
        s.Mode = m;
        AddUndo();
    
        // shifter to adjust before
        function shiftBefore() {
            var tv = tp;
            var bv = bp;
    
            for (var j = 0; j < selections.length; j++) {
                if (selections[j].s >= tv || selections[j].e >= tv) {   // at end of beforehand
                    break;
                }
                if (j == i && tv > 0) {   // at turn-over position
                    tv = tp - l;
                    bv = bp - l;
                    continue;
                }
                if (selections[j].s < selections[j].e) {
                    if (tv <= selections[j].s) {
                        if (bv >= selections[j].e) {
                            selections.splice(j--, 1);
                            i--;
                            continue;
                        }
                        else {
                            selections[j].s = selections[j].s + lx;
                        }
                    }
                    if (bv <= selections[j].e) {
                        selections[j].e = selections[j].e + lx;
                    }
                }
                else {
                    if (tv <= selections[j].e) {
                        if (bv >= selections[j].s) {
                            selections.splice(j--, 1);
                            i--;
                            continue;
                        }
                        else {
                            selections[j].e = selections[j].e + lx;
                        }
                    }
                    if (bv <= selections[j].s) {
                        selections[j].s = selections[j].s + lx;
                    }
                }
                if (lx < 0) {
                    if (selections[j].s < 0) {
                        if (selections[j].e < 0) {
                            selections.splice(j--, 1);
                            i--;
                            continue;
                        }
                        else {
                            selections[j].s = 0;
                        }
                    }
                    else if (selections[j].e < 0) {
                        selections[j].e = 0;
                    }
                }
                if (selections[j].s > selections[j].e) {
                  selections[j].t = selections[j].e;
                  selections[j].b = selections[j].s;
                }
                else {
                  selections[j].t = selections[j].s;
                  selections[j].b = selections[j].e;
                }
            }
        }
        // end of shiftBefore
    
        // reordering of jumped over selections
        // [i] から tp0 まで、[i] は sp ep
        function postponeAction() {
            var sx, ex;
            var sp_r, ep_r, tp9_r, tp0_r;
            var i0, i1;
    
            // detection of before-after gaps (sx ex lx)
            BeginUndoGroup();
            s.SetAnchorPos(tp9);
            s.SetActivePos(bp, true);
            t = s.Text;   // after
            EndUndoGroup();
            // 
            d.Undo();     // EndUndoGroup (SetAnchor)
            d.Undo();     // AddUndo
            d.Undo();     // EndUndoGroup (fn())
            // 
            l = l9;       // rollback
            // 
            s.SetAnchorPos(tp9);
            s.SetActivePos((selections[i + 1].s + l), true);
            t0 = s.Text;  // before
            // calculate gap
            sx = t.indexOf(t0);     // start position gap
            if (sx < 0) {           // -1 (not-found) treatment
                if (t0.length > 2) {
                    var sz = Math.ceil(t0.length / 2);
                    var s0 = t0.length - sz;
                    t0 = t0.slice(s0);
                    sx = t.indexOf(t0) - s0;
    // alert(sx + '\n' + t0 + '\n\n' + t);
                }
                if (sx < 0) {
                    sx = 0;
                }
            }
            ex = lx - sx;           // end position gap
            // 
            sp_r = sp;
            ep_r = ep;
            tp9_r = tp9;
            // loop in this depth level
            for (i0 = i++; ((i < selections.length) && (selections[i].s + l) <= tp0); i++) {
                s.SetAnchorPos(sp = (selections[i].s + l));
                s.SetActivePos(ep = (selections[i].e + l), true);
                tp9 = selections[i].t + l;   // top position for gap detection
                AddUndo();
                BeginUndoGroup();
                fn();
                //
                tp = s.GetAnchorPos();
                bp = s.GetActivePos();
                if (tp > bp) {
                    tp = bp;
                    bp = s.GetAnchorPos();
                }
                tp1 = tp;
                //
                EndUndoGroup();
                AddUndo();
                l9 = l;
                l = d.TextLength;
                lx = l - l9;
                // further depth level
                if ((i < (selections.length - 1)) && ((selections[i + 1].s + l) <= tp1)) {
                    // 
                    i1 = i;
                    // 
                    tp0_r = tp0;
                    tp0 = tp1;
                    postponeAction();
                    tp1 = tp0
                    tp0 = tp0_r;
                    // 
                    for (var j = i1; j <= i; j++) {
                        selections[j - 1] = selections[j];
                    }
                }
                // selections back-and-forth
                else {
                    selections[i - 1] = { s: (tp = s.GetAnchorPos() - l), e: (bp = s.GetActivePos() - l), t: tp, b: bp, s0: s.GetAnchorPos(), e0: s.GetActivePos() };
                    if (tp > bp) {
                        selections[i - 1].t = tp = bp;
                        selections[i - 1].b = bp = selections[i - 1].s;
                    }
                }
            }
            if (i == (selections.length - 1)) {
              lastAction();
              selections[i - 1] = selections[i];
              i++;
            }
            sp = sp_r;
            ep = ep_r;
            tp9 = tp9_r;
            // 
            i--;
            // outer-most at last
            s.SetAnchorPos(sp);
            s.SetActivePos(ep, true);
            AddUndo();
            BeginUndoGroup();
            fn();
            EndUndoGroup();
            AddUndo();
            // 
            l9 = l;
            l = d.TextLength;
            lx = l - l9;
            // 
            selections[i] = { s: (tp = s.GetAnchorPos()), e: (bp = s.GetActivePos()), t: tp, b: bp, s0: tp, e0: bp };
            if (tp > bp) {
                selections[i].t = tp = bp;
                selections[i].b = bp = selections[i].s;
            }
            // loop in this depth level
            for (var j = i0; j < i; j++) {
                selections[j].s = selections[j].s0;
                selections[j].e = selections[j].e0;
                // 
                if (selections[i].e >= selections[j].e) {
                    selections[j].e += sx;
                    selections[j].e0 += sx;
                }
                if (selections[i].e >= selections[j].s) {
                    selections[j].s += sx;
                    selections[j].s0 += sx;
                }
                if (selections[i].e >= selections[j].b) {
                    selections[j].b += sx;
                }
                if (selections[i].e >= selections[j].t) {
                    selections[j].t += sx;
                }
            }
            // 
            for (var j = i + 1; j < selections.length; j++) {
                if (selections[i].e >= selections[j].e + l) {
                    selections[j].e += lx;
                    selections[j].e0 += lx;
                }
                if (selections[i].e >= selections[j].s + l) {
                    selections[j].s += lx;
                    selections[j].s0 += lx;
                }
                if (selections[i].e >= selections[j].b + l) {
                    selections[j].b += lx;
                }
                if (selections[i].e >= selections[j].t + l) {
                    selections[j].t += lx;
                }
            }
    
            // 
            // remaining very last within postponeAction
            function lastAction() {
                var sx, ex;
    
                // 
                tp9 = selections[i].t + l;   // top position for gap detection
                // 
                s.SetAnchorPos(tp9);
                s.SetActivePos(tp0, true);
                t0 = s.Text;  // before
                // 
                s.SetAnchorPos(selections[i].s + l);
                s.SetActivePos(selections[i].e + l, true);
                tp9 = selections[i].t + l;   // top position for gap detection
                // 
                AddUndo();
                BeginUndoGroup();
                fn();
                //
                tp = s.GetAnchorPos();
                bp = s.GetActivePos();
                if (tp > bp) {
                    tp = bp;
                    bp = s.GetAnchorPos();
                }
                //
                EndUndoGroup();
                AddUndo();
                l9 = l;
                l = d.TextLength;
                lx = l - l9;
                // 
                BeginUndoGroup();
                s.SetAnchorPos(tp9);
                s.SetActivePos(bp, true);
                t = s.Text;   // after
                EndUndoGroup();
                // calculate gap
                sx = t.indexOf(t0);     // start position gap
                if (sx < 0) {           // -1 (not-found) treatment
                    if (t0.length > 2) {
                        var sz = Math.ceil(t0.length / 2);
                        var s0 = t0.length - sz;
                        t0 = t0.slice(s0);
                        sx = t.indexOf(t0) - s0;
    // alert(sx + '\n' + t0 + '\n\n' + t);
                    }
                    if (sx < 0) {
                        sx = 0;
                    }
                }
                ex = lx - sx;           // end position gap
                // 
                selections[i] = { s: tp, e: bp, t: tp, b: bp, s0: tp, e0: bp };
                if (tp > bp) {
                    selections[i].t = tp = bp;
                    selections[i].b = bp = selections[i].s;
                }
                // 
                for (var j = i0; j < i; j++) {
                    if (selections[i].e >= selections[j].e) {
                        selections[j].e += sx;
                        selections[j].e0 += sx;
                    }
                    if (selections[i].e >= selections[j].s) {
                        selections[j].s += sx;
                        selections[j].s0 += sx;
                    }
                    if (selections[i].e >= selections[j].b) {
                        selections[j].b += sx;
                    }
                    if (selections[i].e >= selections[j].t) {
                        selections[j].t += sx;
                    }
                }
            }
            // end of lastAction
        }
        // end of postponeAction
    }
    
    

    ----------------------------------------
    ----------------------------------------

    面倒な野良なので、どうぞスルーして下さい。お騒がせしてすみません。

     |  虚inuuik  |  返信
  19. 正規表現の
    a(b|)+d

    abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcd
    に対して検索すると Mery が「応答なし」となることについて
    ----------------------------------------

    起きていることは、「正規表現エンジンの内部で多くの試行が続き、不一致の結果に到達するまで長時間を要している」です。無限ループや異常停止ではありません。処理が終了すれば制御が戻ります。

    検索対象の文字列を
    abbbbbbbcd
    とすると短縮すると、時間はかかっても処理が戻ることがわかります。

    このとき検索後に Mery エディタの操作全体が遅くなることも異常ではなく、検索ハイライトのために同じ重い検索が内部で繰り返されていることによるもので、検索ハイライトを解除すれば、動作速度は復旧します。

    これは次の正規表現エンジンで発生します。

    Onigmo 6.2.0 release 版 (最新版)まで
    (Onigmo を使用する bregonig.dll も含みます)

    Oniguruma 6.9.4 release 版まで

    Onigmo は Oniguruma からの派生版ですが、現在公式には Oniguruma の更新と同期しない、として独自仕様となっています。例外はあるらしく、一部だけには oniguruma の更新を取り込んだ内容もあります。
    その例として、Onigmo 6.2.0 で解決された「戻り読みで ss などの文字列と一致できない」ことの Unicode case-folding 抑止は Oniguruma の対応方法が取り込まれました。

    この長時間動作の挙動は Oniguruma で実装されている空文字列のキャプチャ(捕捉)に関わる処理に起因します。派生版の Onigmo は派生の時点からこの挙動を継承していたため、同じ動作になります。

    正規表現
    a(b|)+d

    a から始まり
    b または空文字列 のグループをキャプチャして
    これを欲張りに1回以上繰り返して
    d と一致

    する文字列を検索することになります。

    空文字列のキャプチャが繰り返されると、Oniguruma は冗長な試行を行います。すべての位置に一致する空文字列の扱いを確実にするためではないかと思います。

    いくつかの条件が揃った正規表現のとき、その不一致判定動作がとても遅くなるのは、鬼車 oniguruma の弱点でした。正規表現を作る上で経験的にわかるものなので、正規表現の書き方を変更することで、これに対処してきました。

    条件は…、

    グループ内で | を使って空文字列判定している (b|)
    そのグループをキャプチャしている (b|)
    greedy (欲張り) または reluctant (無欲) でキャプチャの繰り返しをする (b|)+

    の3つです。

    この条件を満たす正規表現の、不一致な文字列への検索では、バックトラック(退行試行)が発生します。

    このバックトラックだけでも処理が遅くなりますが、制御が長時間戻らないほどではありません。
    他の正規表現エンジンで同じ条件の検索をすると、バックトラックで遅くはなっても、正常に不一致判定がされます。Web 版の正規表現チェッカーなどで試すと、バックトラックがわかりやすく表示されます。

    Oniguruma と Onigmo では空文字列のキャプチャが含まれると、このバックトラックがさらに拡大された範囲で繰り返されるため、実用的でない処理量の増大が発生してしまいます。

    【この2つは条件に一致するので、遅くなってしまいます。】
    a(b|)+d greedy (欲張り) な繰り返し

    a(b|)+?d reluctant (無欲) な繰り返し

    【これらは条件に一致しないので、遅くなりません。】
    a(?:b|)+d キャプチャしません

    a(b|)++d possessive (強欲) な繰り返し

    a(?>(b|)+)d 繰り返しのバックトラック抑止

    a(b|e)+d 空文字列を判定しない
    a[b]+d 空文字列を判定しない

    検索対象の文字列を abbbbbbbcd として正規表現の不一致判定をすると、内部処理のステップ数は

    a(b|)+d 5092
    a(b|)+?d 5096
    a(?:b|)+d/ 134
    a(b|)++d 71
    a(?>(b|)+)d 79
    a(b|e)+d 61
    a[b]+d 30

    のようになります。

    また、本来の正規表現の目的を推測すると、
    a(b|)+d

    b または別の1文字、たとえば c の判定であると考えられるので、
    a[bc]+d
    とする文字クラスの使用がより適しています。

    ここまで「小文字と大文字を区別する」(ignore case ではない) 設定での動作が前提で、区別しないときにはさらに大幅に処理ステップ数が増え、遅くなります。

    正規表現を変更する、ということが Onigmo を使う場合には唯一の対処方法です。

    ところで、2019-08 以降の一連の更新により、Oniguruma ではこの課題が解決されています。次の Oniguruma 6.9.5 release 版では a(b|)+d は通常の処理速度で検索されます。

    実は Oniguruma のテストプログラム test_utf8.c には、正常に動作する例として、この正規表現とブログ記事が参照されています。
    --------------------------------------------------------------------------------
    Branch: master

    Commits on Jan 28, 2020

    add a test case

    K.Kosako
    f8da06a
    --------------------------------------------------------------------------------

    ただし、Mery が使用している Onigmo は先に書いたように、新機能を主としてすでに鬼車とは異なる独自仕様となっており、またこの一連の更新が広い範囲にわたる最適化の一部であることから、最近は更新頻度が少ない Onigmo がこの課題を解決した版をいつ実現するかは、まったく予測できません。

    主題から少しはずれて、恐縮ながら、ご提案とお願いになりますが…、

    また、さらに、Oniguruma 6.9.5 release 版では「可変長の戻り読み」に対応しています。ほかにも、数多くの機能強化がされています。現在 6.9.5-rc2 で、4 月中には release が予想されます。

    Oniguruma をビルドできるのなら、Mery は 32 bit 版にかぎって、ビルドした onig.dll を onigmo.dll と名前変更すると正規表現エンジンとして鬼車を使うことができます。
    ただし 64 bit 版では、エラーなく読み込めますが oniguruma.h の不整合のためか検索できません。以前のようにビルド自体が 64 bit に対応不足であったことはなく、現在の版は 64 bit ビルドには問題からありません。

    Mery のマクロには Onigmo の仕様に依存した正規表現を含むものがあるため、後方互換性を考慮すると、再び Oniguruma に切り換えることは望ましくないと思います。

    しかし、仕様の差異を理解した上で使い分けるならば、Oniguruma の強化された機能の多くは、きわめて有用ですので、正規表現エンジンを、鬼雲と鬼車で選択できる仕組みを Mery が持つことを、ぜひおすすめします。

    動的に変更できる必要はなく静的に Mery.ini の設定で切り替えることができて、onig.dll の名前のままで、そして 64 bit でも使えることになれば、とても素晴らしいです。

    面倒な野良なので、どうぞスルーして下さい。お騒がせしてすみません。

     |  虚inuuik  |  返信
  20. 情報ありがとうございます、大変参考になりました。

    6.9.5 Release Candidate 2 で試してみたら、確かにフリーズせず動作しました。

    > 実は Oniguruma のテストプログラム test_utf8.c には、正常に動作する例として、この正規表現とブログ記事が参照されています。

    うわー、ほんとですね… (*ノωノ)

    Mery のようなマイナーなソフトを意識してくださっているとは。Oniguruma に浮気したくなってしまいます (w

    > ただし 64 bit 版では、エラーなく読み込めますが oniguruma.h の不整合のためか検索できません。以前のようにビルド自体が 64 bit に対応不足であったことはなく、現在の版は 64 bit ビルドには問題からありません。

    C++ は良く分からないので以下は妄想です。間違っていたらスミマセン… ^^;

    気になったので調べてみましたが、Onigmo と Oniguruma で 64 ビット対応の状況が異なるためエラーが発生しているようです。

    具体的には OnigRegion (re_registers) の beg、end ですが、Onigmo は ptrdiff_t 型で定義されており 64 ビットに対応していますが、Oniguruma は int 型で定義されており 32 ビットのままとなっています。

    oniguruma.h

    struct re_registers {
      int  allocated;
      int  num_regs;
      int* beg;
      int* end;
      /* extended */
      OnigCaptureTreeNode* history_root;  /* capture history tree root */
    };
    

    onigmo.h

    struct re_registers {
      int  allocated;
      int  num_regs;
      OnigPosition* beg;
      OnigPosition* end;
      /* extended */
      OnigCaptureTreeNode* history_root;  /* capture history tree root */
    };
    

    (OnigPosition は ptrdiff_t 型なので、64 ビットでビルドすると 64 ビットの値になると思います、たぶん)

    試しに oniguruma.h の定義を以下のようにしてみると、Mery でエラーなく使えましたが、他への影響は分かりません。

    struct re_registers {
      int  allocated;
      int  num_regs;
      ptrdiff_t* beg;
      ptrdiff_t* end;
      /* extended */
      OnigCaptureTreeNode* history_root;  /* capture history tree root */
    };
    

    Mery 側で対応するなら、上記の部分の定義を Mery 側が int 型に合わせれば Oniguruma が使えるようにはなりますが、今度は Onigmo が使えなくなります。

    Oniguruma と Onigmo でそこの仕様を合わせていただければ助かりますが、現状、Mery 側で対応するなら Oniguruma 向けの Mery を個別にリリースするしかないですね。

    > 正規表現エンジンを、鬼雲と鬼車で選択できる仕組みを Mery が持つことを、ぜひおすすめします。

    Mery 側で切り替える仕組みを作るとなれば oniguruma.h と onigmo.h の両方を内蔵するかたちになるのであまり美しくないですし、今後、Oniguruma が 64 ビット対応を進めていくうえで int 型が変更になる可能性があるなら、それらの仕組みはすべて台無しですね。

    と、ここまでは妄想で、実際のところ ptrdiff_t って 64 ビットでビルドすると 64 ビットの値になるのか、良く分かっていません。

    脳がショートしてきたので、とりあえず現状報告でした。

    クリップボードの件はご意見を参考にさせていただき改めて検討してみたいと思います。

     |  Kuro  |  返信
  21. ご返信ありがとうございます。

    > 6.9.5 Release Candidate 2 で試してみたら、確かにフリーズせず動作しました。

    お手数をおかけしました。でもスゴイですよね。Oniguruma 6.9.5 Release が楽しみです。

    > 具体的には OnigRegion (re_registers) の beg、end ですが、Onigmo は ptrdiff_t 型で定義されており 64 ビットに対応していますが、Oniguruma は int 型で定義されており 32 ビットのままとなっています。

    ソースコードの確認までしていただいて、本当にありがとうございます。
    該当部分の相違については、ご指摘ご説明の通りだと思います。この部分の書き換えでエラーなく動作する、とのご確認までいただき、重ねて感謝です。

    また Mery の内部で onigmo.h を兼用しているのかどうか、がわからなかったので、そこがわかって、とても助かりました。

    何年か前、自分の勉強に 64 bit 対応を試みたときも ptrdiff_t への置き換えは大量でした。
    でも、oniguruma.h の API まで書き換えてしまっていいのか悩んだものです。

    ここが int のままでもアドレス空間はどこにでも移動できますが、対象の文字領域長が 2GB 以内に限られてしまいます。本来の 64 bit 対応ではやるべきだと思いますが、Mery 自体も内部で 32 bit の制約があるらしいことを知って、oniguruma のエコシステムとして、ここが 32 bit のままというのもアリなのか、とも考えて保留していました。

    逆に、もし、これまでの oniguruma.h をすでに持っているアプリには、変更したら不整合で使えなくなるわけなので、Mery がどちらなのかをずっと知りたかったのです。

    この oniguruma.h の部分の変更は、鬼車の作者様のご判断に委ねて、待とうと考えていました。そこで、今のままの oniguruma.h と onigmo.h を共に持って、読み込み時に切り替える、という意味での「選択できる仕組み」のご提案でしたが、…やはりむずかしいですね。API の変更は極力避けないとアプリ側の後方互換性の影響が大きくて。

    でも強引かとは思っても、それを代償にしてもなお、新しく実装された「可変長の戻り読み」の価値は大きいと考えました。

    クリップボードについては、もし少しでも美意識が許す範囲で可能でしたら、よろしくお願いいたします。
    ご配慮いただき、ありがとうございました。

     |  虚inuuik  |  返信
  22. ご返信ありがとうございます。

    > でもスゴイですよね。Oniguruma 6.9.5 Release が楽しみです。

    再び Oniguruma の時代が来るかもしれませんね。Mery としては、あんまり更新されないライブラリのほうが助かりますが… (w

    > また Mery の内部で onigmo.h を兼用しているのかどうか、がわからなかったので、そこがわかって、とても助かりました。

    Mery は Oniguruma の Delphi 用ヘッダを Onigmo 用にすべて書き直しました。

    > ここが int のままでもアドレス空間はどこにでも移動できますが、対象の文字領域長が 2GB 以内に限られてしまいます。本来の 64 bit 対応ではやるべきだと思いますが、Mery 自体も内部で 32 bit の制約があるらしいことを知って、oniguruma のエコシステムとして、ここが 32 bit のままというのもアリなのか、とも考えて保留していました。

    Mery は内部は 32 ビットですね。Onigmo のヘッダの部分だけは 64 ビット型を使っていますが、そもそも 32 ビット以上の位置には移動できませんし、そもそもそんなファイルは開いた時点で落ちると思います (w

    Oniguruma があえて 32 ビットを採用しているということでしたら、それはそれでアリですね。

    心配だったのは、現状の Oniguruma がどういう状態なのか知らなかったのですが、当時はまだ Oniguruma が 64 ビットに対応されていない状態だったので、今もまだ 64 ビットに対応している途中の段階とかですと、そのうち 64 ビットの型に変更されるかも?という点でした。

    > 今のままの oniguruma.h と onigmo.h を共に持って、読み込み時に切り替える、という意味での「選択できる仕組み」のご提案でしたが、…やはりむずかしいですね。API の変更は極力避けないとアプリ側の後方互換性の影響が大きくて。

    正規表現エンジンを切り替えられるエディターもありますから、そういう仕組みを導入することは悪くないと思いますし可変長の戻り読みは魅力的ですが、技術的好奇心よりも、若干、ライセンス表示とメンテナンスと動作検証とサポートが面倒くさいが勝ちますね (w

    Onigmo から Oniguruma へ乗り換えということでしたら面白そうなのでアリだと思いますが、両方の更新を追いかけていくのは私にはちょっと荷が重すぎます ^^;

    Oniguruma へ乗り換えるタイミングがありましたら、ご助言をいただければ幸いです。

     |  Kuro  |  返信
  23. 重ねてご返信ありがとうございます。

    > … Mery としては、あんまり更新されないライブラリのほうが助かりますが… (w

    更新 (commit) は活発ですが、昨年の Release は3回でした、その前の年の半数です。

    【2019】
    Release 6.9.4
    ... 29 Nov 2019

    Release 6.9.3 (security fix release)
    ... 6 Aug 2019

    Release 6.9.2
    ... 7 May 2019

    > 心配だったのは、現状の Oniguruma がどういう状態なのか知らなかったのですが、当時はまだ Oniguruma が 64 ビットに対応されていない状態だったので、今もまだ 64 ビットに対応している途中の段階とかですと、そのうち 64 ビットの型に変更されるかも?という点でした。

    これはたぶん無いだろうと思います。Onigmo のように全体を 64bit に書き換えという感じではないので…。
    64bit 環境でビルドして、64bit 環境で動作する状況ですが、内部処理がすべて変わったわけではありません。

    ご指摘いただいた region->beg region->end を oniguruma.h regexec.c で変更して、警告なしでビルドしたところ Mery 64bit で動作しました。ありがとうございます。
    無変更の 32bit も合わせて動かしてみると、どちらも Onigmo に比べて検索速度が遅くなりますね。220万行テキストの終端から 75万行目にある文字列を探すと、待ち時間が体感で長くなっているのがわかります。

    > … 技術的好奇心よりも、若干、ライセンス表示とメンテナンスと動作検証とサポートが面倒くさいが勝ちますね (w

    ごもっとも、おっしゃる通りですね。今でも大変なのに…、やがてくるだろう質問が目に浮かびます。

    > Oniguruma へ乗り換えるタイミングがありましたら、ご助言をいただければ幸いです。

    ワッ!これは浮気ではなく本気、しかも復縁…(笑)
    私は、円満な関係に水を差す、トンデモな悪い奴になってしまいます www

    ましてや、振っている相手は「もいすん」として長々と鬼車の押し売りをして、そそっと「ご自分のブログで…」と言われてしまい途中で止めた、鬼車正規表現フリークですよ、火に油でしょう(笑)

    とはいえ、実際のところ、今、乗り換えを検討する価値は十分にありそうです。

    (?<=[ \t]+)Mery のような「可変長の戻り読み」は、「先読み」と同じ自由度での記述を実現できます。このインパクトは、飛躍的だけれどマニアックだった部分式の導入をはるかに超える、広く親しみやすいもので、過去10年以上を見ても最強の機能追加だと思います。

    これは ECMAScript (Node.js) や .NET で、新しい Perl でもそれぞれに工夫した実装が進んでいます。

    ただ先に書いた速度の問題などもあり、100点満点での移行ではないと思いますので、
    oniguruma 6.9.5 Release が公開されたら、現行の Mery で置き換えできる onigmo.dll バイナリを用意して、使い比べをするのはどうでしょうか。またはお手数ですがテスト専用版の Mery でも…

    本当に良い所が不便さを上回るのか、6 週間くらい人柱さんにお願いして試してみてから、乗り換えを判断するのが無難な検討方法ではないかと思います。

    同時にこの期間に、Onigmo との差異を Oniguruma で実現するための技を探す、というのもいいですね。
    また Onigmo が設定しているオプションで、Oniguruma にも適用したほうが良いものを考えることもできます。

    呼出しアプリ側にとっては CALLOUTS や regset などの面白そうな技術もあります。

    すぐに決定するのではなく慎重に、でもしっかりといろいろ評価して、ご判断をお願いします。

     |  虚inuuik  |  返信
  24. Shift+Ctrl+Alt+F on Windows 10 1909
    ----------------------------------------

    --------------------------------------------------------------------------------
    Key combination Shift+Ctrl+Alt+F is reserved exclusively by Windows 10 since 1903 Update.

    There is a workaround for this problem, but still annoying.

    Shift+Ctrl+Alt+[Windows Logo]+F (5 keys) is recognized as Shift+Ctrl+Alt+F by Windows.

    Then Shift+Ctrl+Alt+Windows+F can be an alternative keyboard shortcut of Shift+Ctrl+Alt+F for some applications.
    It is effective on Windows XP through most recent Windows 10 1909.
    --------------------------------------------------------------------------------

    Shift+Ctrl+Alt+F のショートカット・キー・コンビネーションは Windows 10 1903 から Windows が占有しており、アプリケーションで使えません。

    Mery の ツール - オプション - キーボード の「新しいキー」でも Shift+Ctrl+Alt+F は反応がありません。
    同じシフトキーでも F だけの反応がなく、ほかの文字は使えます。

    これを回避する1つの方法として…、

    Shift+Ctrl+Alt+[Windows ロゴ]+F キー (5本指)を押すと Windows はこれを Shift+Ctrl+Alt+F キーとして認識します。

    Mery の「新しいキー」のフィールドで Shift+Ctrl+Alt+Windows+F を押すと、Shift+Ctrl+Alt+F と表示され、そのまま [割り当て] できます。
    そして登録だけでなく、使う時も同じようにこの5本指を押すことになります…、とても面倒。

    でも AutoHotkey などのツールを使えば対応できても、何もないときは、指1本プラスの工夫です。
    --------------------------------------------------------------------------------

    ◎野良なので、ここの内容は MeryWiki での転載・引用などをお断りします。

    「無題」について、フォーラムではすでに回答がでていますが、また別の代用案です。

    これは並行作成されるファイル名の衝突回避に焦点をあてた内容なので、より一般的で応用範囲が広い「Untitled=%date%」などの「新規作成時のファイル名のカスタマイズ」機能と競合する意図はまったくありません。カスタマイズ機能の需要は根強いと思います。ご気分を害されてしまったら申しわけありません。

    ファイル名の衝突回避をするために、ファイル名の生成のときに先行して空ファイルを作成する方法です。

    まず、無題のファイルを置いておく場所、ローカルでもネットワークでもどこでも、を決めてから、

    そこに

    Untitled

    というフォルダを作成します。この「Untitled」フォルダの中に無題のファイルが収まります。

    この「Untitled」フォルダに「無題の時.js」マクロを保存します。

    Mery を起動してから、無題をいくつか 3 ~ 5 個のタブに作っておきます。これは衝突回避の動作をわかり易く見るためなので今回だけです。

    用意した「Untitled」フォルダの中の「無題の時.js」を Mery で開き、
    「マクロ」-「これを選択」と操作します。
    続いて、「マクロ」-「カスタマイズ」で、マクロのカスタマイズ のダイアログを表示します。

    マクロの中から「 ☑ 無題の時.js」を選び、「☑イベントで実行」とします。次に [イベント] をクリック。

    イベント

    ☑ アクティブな文書が変更された時

    OK


    マクロのカスタマイズ

    OK

    これで、マクロがイベントで動作するようになりました。

    あらかじめ作っておいた「無題-」タブの名前が、すべて変更されているはずです。
    例えば、「無題-20200417_182725_000」から「無題-20200417_182725_003」までなど…

    ほとんどのとき、「無題-年月日_時分秒_000」なので、名前はユニークになりますが、
    この名前の最後の 3 桁の数値は、名前が衝突すると連番で増えてゆき、別名が確保されます。
    新規にマクロが動作したとき、複数の無題があったので、同時刻で名前衝突が発生して作動しました。
    これなら同時に複数で同じ場所に書き込みをしても上書きを避けることができます。

    ここからは、「新規作成(Ctrl+N)」されるたびに、すべての「無題」に新しいファイル名を生成します。

    このマクロでは「無題」ファイル名の生成の時点で、空ファイルを作成しますが、ファイル名があるファイルの編集された内容の保存はしません。
    そのため、上書き保存、自動保存、そして、閉じるときの保存確認 などと、かならず併用します。

    「最近のファイル表示数」を消費しますので、標準の 8 よりずっと大きくしておかないとたぶん困ります。

    また、違う場所の同じ名前のマクロを複数、Meryに設定することができてしまいますが、イベントは最初の1つだけに反応しますので、同時に複数の場所に保存することはできません。前に「選択」したマクロの場所に保存しないようにするには、前のマクロを無効ではなく削除する必要があります。

    無題の時.js
    ----------------------------------------

    // - 糸くす~------------------------------------- Copyright(C)2016-2020 inuuik -
    // 無題の時.js
    //
    //   すべての無題の文書(未保存)を「無題-yyyymmdd_hhmmss_000」の形式で
    //   名前を付けて保存
    //   
    //     ファイル名の例: 無題-20160428_153434_000
    //     ファイル名の例: Unt-20200415_170536_000
    //   
    //     拡張子はつけない
    //   
    //     保存先は、実行時にアクティブな文書と同じパス
    //     ↓
    //     ※変更 2017-07-01※
    //     既定の保存先は、このマクロ・スクリプトと同じパス
    //     
    //     コード最終部分が (); または (0); のとき
    //     UntitledTime() または UntitiledTime(0) での呼び出しで
    //     マクロ・スクリプトと同じパス
    //     
    //     コード最終部分が (1); のとき、
    //     UntitledTime(1) での呼び出しとなり、アクティブな文書と同じパス
    //     
    //     コード最終部分が (2); のとき、
    //     UntitledTime(2) での呼び出しとなり、マクロ・スクリプトと同じパス
    //     無題のアクティブな文書だけを保存
    //    
    //     ※注意※  アクティブな文書にパスがなければ Mery の実行ファイルのパス
    //     になるので、保護機能により保存できない場合がある
    //       ↓
    //       ※変更 2017-07-01※ スクリプトにパスがないときはアクティブな文書のパス
    //       さらにアクティブな文書にもパスがなければ Mery の実行ファイルのパス
    //   
    //   無題の文書(未保存)がなければ何もしない
    //
    //   ※「イベントで実行」を使用する場合※  Mery 2.3.0.5095 以降専用
    //     マクロ → カストマイズ
    //     ↓
    //     ☑無題の時.js を選択
    //        ↓
    //        ☑イベントで実行
    //          ↓
    //          イベント
    //          ☑アクティブな文書が変更された時
    //          ↓
    //          OK → OK
    //       
    //       ※注意 2020-04-15※
    //       起動時の無題から未保存すべてにファイル名を設定した空ファイルを生成
    //       内容の保存は行わない
    //   
    //   ストリームで読み込む使用も想定
    //   スクリプト名が「無題の時.js」でないと、eval では UntitledTime() の定義のみ
    //   実行は、別スクリプトからの UntitledTime(0); と UntitledTime(1); による
    //
    // revised inuuik  2016-01-27 すべての無題を時刻を付加したファイル名で保存
    // revised inuuik  2016-04-28 ミリ秒を連番に変更
    // revised inuuik  2017-07-01 保存先、関数 Untitled_Time() を(0)で実行
    // revised inuuik  2017-07-04 関数 UntitledTime() を()で実行
    // revised inuuik  2017-07-05 UntitledTime(2) アクティブ文書のみ
    // revised inuuik  2017-07-07 Saved更新なし→元に戻す
    // revised inuuik  2020-04-13 同名ファイル回避
    // revised inuuik  2020-04-14 同名ファイル回避、Untitled_
    // revised inuuik  2020-04-15 同名ファイル回避、Unt_、空ファイル
    // revised inuuik  2020-04-16 無題- Unt-
    // - ------------------------ --------------------------------------------------
    (function UntitledTime(p) {
      var dateNow = new Date();  // 実行開始の現在日時を取得
      dateNow.setMilliseconds(0);  // ミリ秒は連番として使うため 0 に設定
      var dateNumNow = dateNow.getTime();  // 日時連番のミリ秒表現
      var StandAloneName = "無題の時.js";
      var PrefixJP = true;     // false: Unt- , true: 無題-
    
      /*
        現在日時の日付オブジェクトからミリ秒表現を取得。(JS 1.5 の Date.now 代替)
      */
      if (!Date.now) {
        Date.now = function now() {
          return new Date().getTime();
        };
      }
      /*
        文字列を N 回繰り返す文字列を生成、-N 回では逆順文字列を繰り返す
      */
      if (!String.prototype.repeat) {
        String.prototype.repeat = function(n) {
          var rep = "";
          var src = this.toString();
          if (n === undefined) { n = 2; }
          if (n < 0) {
            n = - n;
            var rev = "";
            var p = src.length;
            while (--p >= 0) { rev += src.charAt(p); }
            src = rev;
          }
          while (--n >= 0) { rep += src; }
          return rep;
        };
      }
      if (!String.prototype.reverse) {
        String.prototype.reverse = function() { return this.repeat(-1); };
      }
      /*
        文字列を左ゼロ詰めで N 桁にする
        文字列の左側を、長いときは切り捨て、短かければ 0(ゼロ)を埋める
        -N 桁のときは右ゼロ詰、長ければ右側を切り捨て
        桁とコンマで区切って文字があれば、それで埋める文字を変更
        桁が省略されるか 0(ゼロ)のときは元の文字列を返す
      */
      if (!String.prototype.pad_zero) {
        String.prototype.pad_zero = function(n, c) {
          var pad = "";
          var src = this.toString();
          if (n === undefined) { n = 0; }
          if (c === undefined) { c = "0"; }
          if (n < 0) {
            n = - n;
            pad = src + c.repeat(n);
            return pad.substr(0, n);
          }
          pad = c.repeat(n) + src;
          return pad.substr(n > 0 ? (pad.length - n) : 0);
        };
      }
      /*
        現在日時 dateNow 日付オブジェクトから連続する日時文字列を生成。
      */
      function makeTimeString() {
        var dateStrNow;
    
        dateNow.setTime(dateNumNow);
        with (dateNow) {
          dateStrNow = (getFullYear() + "").pad_zero(4);
          dateStrNow += (getMonth() + 1 + "").pad_zero(2);
          dateStrNow += (getDate() + "").pad_zero(2);
          dateStrNow += "_";
          dateStrNow += (getHours() + "").pad_zero(2);
          dateStrNow += (getMinutes() + "").pad_zero(2);
          dateStrNow += (getSeconds() + "").pad_zero(2);
          dateStrNow += "_";
          dateStrNow += (getMilliseconds() + "").pad_zero(3);  // 内容は 000 ~ 999 の連番に置換
        }
        dateNumNow++;  // 999 を超えれば秒が進む
        return dateStrNow;
      }
    
      var d0 = editor.ActiveDocument;  // this document
      var dp = d0.Path;  // path of this document
    
      if (p === undefined || p === 0 || p === 2) {
        var mp = ScriptFullName.replace(ScriptName, "");  // Macro script Path
        if (mp !== "") { dp = mp; }
      }
    
      var fn = dp + (PrefixJP ? "無題-" : "Unt-");   // path an prefix for as-name
    
      if (ScriptName !== StandAloneName && p === undefined) {   // eval within other script
        return;
      }
    
      if (p === 2) {
        with (d0) {   // only for active document
          if (Saved === false && Name === "") {
            Save(fn + makeTimeString());
            Saved = true;
          }
        }
        return;
      }
    
      // Save with new names for untitled files  無題の選択
      var d1;             // current untitled document
      var timeString;     // generated time-string
      var nos = editor.Documents.Count;   // number of documents
      for (var i = 0; i < nos; i++) {
        with (editor.Documents.Item(i)) {
    //      if (Saved === false) {
            if (Name === "") {
              Activate();
              d1 = editor.ActiveDocument;
              timeString = makeTimeString();
              while (1) {
                editor.OpenFile(fn + timeString, meEncodingNone, meOpenAllowNewWindow);
                if (d1 === editor.ActiveDocument) {   // not found
                  break;
                }
                if (editor.Documents.Count > nos) {   // newly opened
                  editor.ActiveDocument.Close();
                }
                Activate();
                timeString = makeTimeString();
              }
              Save(fn + timeString);
              Saved = true;
            }
    //      }
        }
      }
      d0.Activate();
    })();
    // - ------------------------ --------------------------------------------------
    

    ----------------------------------------
    ----------------------------------------

    面倒な野良なので、どうぞスルーして下さい。お騒がせしてすみません。

     |  虚inuuik  |  返信
  25. ご返信ありがとうございます。

    > 更新 (commit) は活発ですが、昨年の Release は3回でした、その前の年の半数です。

    そのようですね、「ご自分のブログで…」はどういう経緯か忘れましたが、言った気はします (w

    鬼車が活発に更新されていた頃じゃなかったでしたっけ。悪意はなくて、非常に詳しい内容で記事としても価値のあるものになりそうだと思ったので、ぜひご自身のブログで掲載されてみては?という気持ちですよー (たぶん ^^;

    > 64bit 環境でビルドして、64bit 環境で動作する状況ですが、内部処理がすべて変わったわけではありません。

    私もその後、改めて検討してみまして、鬼雲と鬼車の切り替え方式の実装ができるか調べてみているのですが、今のところヘッダの OnigRegion の beg、end と、Oniguruma だと OnigLen (unsigned int [32 ビット])、Onigmo だと OnigDistance (size_t [64 ビット]) ぐらいかなといった感じです。

    当時は鬼車の更新頻度が活発だったように見受けられたので、まだ実践投入は早いかなと思っていましたが最近は良い感じになって来ているようですね。

    > ごもっとも、おっしゃる通りですね。今でも大変なのに…、やがてくるだろう質問が目に浮かびます。

    はい、でも一番大きいのはライセンスが良く分からないところですね。

    Oniguruma と Onigumo を両方同梱した場合、Oniguruma は BSD ライセンスなので BSD ライセンス条文を同梱することになりますが、Onigmo は Oniguruma の BSD ライセンスを継承しているので BSD ライセンスになっていて…。

    BSD ライセンス条文を 2 つ含めればいいってこと?それとも "Onigmo (Oniguruma-mod) LICENSE" には "Oniguruma LICENSE" が含まれているので、"Onigmo (Oniguruma-mod) LICENSE" だけ同梱すれば "Oniguruma LICENSE" は不要なのかも…?

    といったあたりを調べていて二つ同梱するのめんどくせーってなりました (w

    > ワッ!これは浮気ではなく本気、しかも復縁…(笑)

    私はもともと鬼車派ですからねw Mery の 64 ビット対応のときに鬼車はまだ 64 ビット対応が進んでいなかったので、鬼雲に浮気してしまったかたちになりましたが ^^;

    > oniguruma 6.9.5 Release が公開されたら、現行の Mery で置き換えできる onigmo.dll バイナリを用意して、使い比べをするのはどうでしょうか。またはお手数ですがテスト専用版の Mery でも…

    そうですね、鬼車の正式版がリリースされたら何らかの形で Mery ユーザーさんにも試していただけるような環境が用意できたら面白そうだと思います。(私は正規表現はニワカなので、マニアックな先生がたにおまかせするかたちになりますが…w)

    > 同時にこの期間に、Onigmo との差異を Oniguruma で実現するための技を探す、というのもいいですね。

    確かに。Mery は bregonig を使っていないので直接 onig の性能を試せる道具としてお役に立てることがあるかもしれませんから、一時的なベータ版みたいなことにはなるかもしれませんが、切り替え方式もアリかもしれないなと、少し思い始めました。

    と、若干、技術的好奇心がメンテとサポートの面倒くささを上回るときもあるぐらいの案件ではあります。

    以上は面倒な野良アプリを作っている人の妄想なので、どうぞスルーしてくださいませ。

     |  Kuro  |  返信
  26. ご返信ありがとうございます。間があきまして失礼しました。

    oniguruma 6.9.5 Release ですが、
    64bit は oniguruma.h と regexec.c を変更、32bit (x86) はそのままで試用してます。

    > … ヘッダの OnigRegion の beg、end と、Oniguruma だと OnigLen (unsigned int [32 ビット])、Onigmo だと OnigDistance (size_t [64 ビット]) ぐらいかな …

    OnigLen を size_t にして、OnigPos として ptrdiff_t にしてます。
    regexec.c と regtrav.c の beg / end を int から OnigPos にしました。
    これでメモリ違反が起きるかどうか…(^^;;;

    やはり標準添付の onigmo よりずいぶん遅いですね、そして 64bit はさらにゆっくり。
    試しましたが 2013-03 の Mery 2.018 32bit までは速いのですが、それ以降はどれも同じような感じです。

    トレードオフとはいえ…、も少しなんとかしないと。何かを間違っているような気も、、、(笑)

    > BSD ライセンス条文を 2 つ含めればいいってこと?それとも "Onigmo (Oniguruma-mod) LICENSE" には "Oniguruma LICENSE" が含まれているので、"Onigmo (Oniguruma-mod) LICENSE" だけ同梱すれば "Oniguruma LICENSE" は不要なのかも…?
    >
    > といったあたりを調べていて二つ同梱するのめんどくせーってなりました (w

    同梱するなら、やはり2つ並べて
    onigmo_COPYING.txt
    oniguruma_COPYING.txt
    としないとマズイと思います。

    しかし、片方はオプションということで同梱しない、ということもありますね。
    でも cmigemo のようにバイナリがすでにあるわけではないので、どこかには用意しないと、、
    仮に作りますか、どうしましょうw (^^/

    さて…

    鬼車と鬼雲の仕様上の大きな違いの1つは、\s \d \w のデフォルト範囲です。

    [抜粋に加筆]
    --------------------------------------------------------------------------------
    【RE.ja  onigmo 6.2.0  2017-07-25】  ※デフォルトで ASCII に限定、ただし /p{...} は除く※
    --------------------------------------------------------------------------------
      \s       空白文字
    
               Unicode以外の場合:
                 \t, \n, \v, \f, \r, \x20
    
               Unicodeの場合:
                 0009, 000A, 000B, 000C, 000D, 0085(NEL),
                 General_Category -- Line_Separator
                                  -- Paragraph_Separator
                                  -- Space_Separator
    
               ASCII外の文字を含むかどうかは ONIG_OPTION_ASCII_RANGE オプションに
               依存する。
    
      \S       非空白文字
    
      \d       10進数字
    
               Unicodeの場合: General_Category -- Decimal_Number
    
               ASCII外の文字を含むかどうかは ONIG_OPTION_ASCII_RANGE オプションに
               依存する。
    
      \D       非10進数字
    --------------------------------------------------------------------------------
        * \p{property-name}
        * \p{^property-name}    (negative)
        * \P{property-name}     (negative)
    
        property-name:
    
         + 全てのエンコーディングで有効
           Alnum, Alpha, Blank, Cntrl, Digit, Graph, Lower,
           Print, Punct, Space, Upper, XDigit, Word, ASCII,
    
         + EUC-JP, Shift_JIS, CP932で有効
           Hiragana, Katakana, Han, Latin, Greek, Cyrillic
    
         + UTF-8, UTF-16, UTF-32で有効
           UnicodeProps.txt 参照
    
        \p{Punct} は、Unicodeの場合とそれ以外で少し異なった動作をする。Unicode
        以外の場合は、"$+<=>^`|~" の9個の文字にマッチするが(これは [[:punct:]] と
        同じである)、Unicodeの場合はマッチしない。
        Unicodeの場合、\p{XPosixPunct} はこの9個の文字にマッチする。
    --------------------------------------------------------------------------------
      (?imxdau-imx)     孤立オプション
                          i: 大文字小文字照合
                          m: 複数行
                          x: 拡張形式
    
                         文字集合オプション (文字範囲オプション)
                          d: デフォルト (Ruby 1.9.3 互換)                 ←**重要**
                             \w, \d, \s は、非ASCII文字にマッチしない。   ←**重要**
                             \b, \B, POSIXブラケットは、各エンコーディングの
                             ルールに従う。
                          a: ASCII
                             ONIG_OPTION_ASCII_RANGEオプションがオンになる。
                             \w, \d, \s, POSIXブラケットは、非ASCII文字に   ←※対象外 \p{...}※
                             マッチしない。
                             \b, \B は、ASCIIのルールに従う。
                          u: Unicode
                             ONIG_OPTION_ASCII_RANGEオプションがオフになる。
                             \w (\W), \d (\D), \s (\S), \b (\B), POSIXブラケット  ←※対象外 \p{...}※
                             は、各エンコーディングのルールに従う。
    
      (?imxdau-imx:式)  式オプション
    --------------------------------------------------------------------------------
    

    これに対して…

    [抜粋に加筆]
    --------------------------------------------------------------------------------
    【RE.ja  oniguruma 6.9.5  2020-04-13】  ※デフォルトで ASCII 限定なし※
    --------------------------------------------------------------------------------
      \s       空白文字
    
               Unicode以外の場合:
                 \t, \n, \v, \f, \r, \x20
    
               Unicodeの場合:
                 U+0009, U+000A, U+000B, U+000C, U+000D, U+0085(NEL), 
                 General_Category -- Line_Separator
                                  -- Paragraph_Separator
                                  -- Space_Separator
    
      \S       非空白文字
    
      \d       10進数字
    
               Unicodeの場合: General_Category -- Decimal_Number
    
      \D       非10進数字
    --------------------------------------------------------------------------------
        * \p{property-name}
        * \p{^property-name}    (negative)
        * \P{property-name}     (negative)
    
        property-name:
    
         + 全てのエンコーディングで有効
           Alnum, Alpha, Blank, Cntrl, Digit, Graph, Lower,
           Print, Punct, Space, Upper, XDigit, Word, ASCII,
    
         + EUC-JP, Shift_JISで有効
           Hiragana, Katakana
    
         + UTF8, UTF16, UTF32で有効
           doc/UNICODE_PROPERTIES参照
    --------------------------------------------------------------------------------
      (?imxWDSPy-imxWDSP:式)   式オプション
    
                                i: 大文字小文字照合
                                m: 複数行
                                x: 拡張形式
                                W: wordがASCIIのみ (\w, \p{Word}, [[:word:]])
                                   word境界がASCIIのみ (\b)
                                D: digitがASCIIのみ (\d, \p{Digit}, [[:digit:]])
                                S: spaceがASCIIのみ (\s, \p{Space}, [[:space:]])
                                P: POSIXプロパティがASCIIのみ (W,D,Sを全て含んでいる)
                                   (alnum, alpha, blank, cntrl, digit, graph,
                                    lower, print, punct, space, upper, xdigit, word)
    
                                y{?}: 文章区分状態
                                   このオプションは\X, \y, \Yの意味を変更する。
                                   現在このオプションはUnicodeでしかサポートしていない
                                   y{g}: 拡張書記素房-状態 (デフォルト)
                                   y{w}: 単語-状態
                                         参照 [Unicode Standard Annex #29]
    
      (?imxWDSPy-imxWDSP)  孤立オプション
    
                          * これは次の')'またはパターンの終わりまでのグループを形成する
                            /ab(?i)c|def|gh/ == /ab(?i:c|def|gh)/
    --------------------------------------------------------------------------------
    

    \s や \d は影響を大きく受けます。

    たとえば、\s が [ \t\n] の前提だと、\x{3000} 全角空白 などへの考慮が必要になります。

    オプションの書式も (?imx) は同じですが、それ以外はオプション指定が異なります。
    --------------------------------------------------------------------------------

    「可変長の戻り読み」は編集モードの強調文字列だと、

    今までは JavaScript で

    色 3 (赤)
    (?<=function\s|class\s|module\s|interface\s)(?!\()[^\s{\(]++

    としていて、空白・タブ1桁だけだったものを、

    色 3 (赤)
    (?<=(?:function|class|module|interface)\s++)(?!\()[^\s{\(]++

    のように複数桁の空白・タブにも反応できます。
    正規表現ではイケても強調表示なので改行を越えると表示しないのはご愛敬です(笑)

      function	abc_def() { alert("a"); }
    
      function  abc_def() {
        alert("a");
      }
    
      function 
      abc_def() { alert("a"); }
    
      abc_def();
    

    これまで部分式の入れ子は無制限でしたが、デフォルトでは 20 階層で止まるようになりました。
    制限のない onigmo と動作を比較すると違いがわかります。

    括弧 () 入れ子 選択 (再帰による階層深さ制限)

    これはペアになっていないカッコからは、ファイル終端まで選択します。
    再帰が 20 レベルに制限されているときは、その限界までの選択になりますので、少しわかりづらいです。

      ※カッコ、終端含む※
    (?<a>(?:\((?:(?>[^()\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^()\\]|(?:\\[\s\S]))*)*+)\)?))
      ※中カッコ※
    (?<a>(?:\{(?:(?>[^{}\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^{}\\]|(?:\\[\s\S]))*)*+)\}?))
    
      ↓
      ※エスケープ除外※
    (?<!(?<!\\)\\)(?<a>(?:\((?:(?>[^()\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^()\\]|(?:\\[\s\S]))*)*+)\)?))
      ※中カッコ※
    (?<!(?<!\\)\\)(?<a>(?:\{(?:(?>[^{}\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^{}\\]|(?:\\[\s\S]))*)*+)\}?))
    
      ↓
      ※コメント除外※ 可変長の戻り読み
    (?<!(?<!\\)\\)(?<!(?<=\s)//.*+)(?<a>(?:\((?:(?>[^()\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^()\\]|(?:\\[\s\S])|(?:(?<!/)//.*+$))*)*+)\)?))
      ※中カッコ※ 可変長の戻り読み
    (?<!(?<!\\)\\)(?<!(?<=\s)//.*+)(?<a>(?:\{(?:(?>[^{}\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^{}\\]|(?:\\[\s\S])|(?:(?<!/)//.*+$))*)*+)\}?))
    

    これはペアになっていないカッコを飛ばして、ペアのカッコの開始点に移動して選択します。

      ※カッコ、開始移動※
    (?<a>(?:\((?:(?>[^()\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^()\\]|(?:\\[\s\S]))*)*+)\)))
      ※中カッコ※
    (?<a>(?:\{(?:(?>[^{}\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^{}\\]|(?:\\[\s\S]))*)*+)\}))
      ↓
      ※エスケープ除外※
    (?<!(?<!\\)\\)(?<a>(?:\((?:(?>[^()\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^()\\]|(?:\\[\s\S]))*)*+)\)))
      ※中カッコ※
    (?<!(?<!\\)\\)(?<a>(?:\{(?:(?>[^{}\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^{}\\]|(?:\\[\s\S]))*)*+)\}))
      ↓
      ※コメント除外※ 可変長の戻り読み
    (?<!(?<!\\)\\)(?<!(?<!/)//.*+)(?<a>(?:\((?:(?>[^()\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^()\\]|(?:\\[\s\S]))*)*+)\)))
      ※中カッコ※ 可変長の戻り読み
    (?<!(?<!\\)\\)(?<!(?<!/)//.*+)(?<a>(?:\{(?:(?>[^{}\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^{}\\]|(?:\\[\s\S]))*)*+)\}))
    

    この正規表現で置換をすると、1回では、最も外側のカッコだけが置換されます。
    それをマクロにして繰り返すと、ゆっくりと、外側から内側に置換が進む様子が見えます。

      ※カッコ、開始移動の応用※
      ※前後括弧の置換※
    
    検索する文字列
    (?<a>(?:\((?<z>(?>[^()\\]|(?:\\[\s\S]))*(?:\g<a>(?>[^()\\]|(?:\\[\s\S]))*)*+)\)))
    
    置換後の文字列 … z は多重定義なので最後の内容、つまり最も外側
    {\k<z>}
    
      ↓
      ☑選択した範囲のみ
      ↓
      すべて置換 を 一致する限り 繰り返す
      ※一度の「すべて置換」では、最も外側のみの置換※
      次の繰り返しでは1つ内側が置換
      ↓
      置換では、置換後の文字列 の後から 次の検索
    

    なにも対応するペアの順にしなくても、( を { に すべて置換、) を } に すべて置換 の2手順で済みますので、これはひとつの動作サンプルです。

      ※ゆっくりと内向きに置換が進む…マクロ例※
      var re = "(?<a>(?:\\((?<z>(?>[^()\\\\]|(?:\\\\[\\s\\S]))*(?:\\g<a>(?>[^()\\\\]|(?:\\\\[\\s\\S]))*)*+)\\)))";
      var te = "{\\k'z'}";
      var fl = (meFindNext | meFindReplaceCase | meFindReplaceRegExp | meReplaceSelOnly | meReplaceAll);
      while(document.selection.Replace(re, te, fl)) { ; }
    

    テスト対象テキスト 2階層目から先は F3 で進む

    //  { ( as(d {s}d 
        //// } ) as)d {s}d 
    
    カッコ
    
    (50
    (49(48(47(46(45(44(43(42(41(40(39(38(37(36(35(34(33(32(31(30(29(28(27(26(25(24(23(22(21(20(19(18(17(16(15(14(13(12(11(10(9(8(7(6(5(4(3(2(1( 0 )1)2)3)4)5)6)7)8)9)10)11)12)13)14)15)16)17)18)19)20)21)22)23)24)25)26)27)28)29)30)31)32)33)34)35)36)37)38)39)40)41)42)43)44)45)46)47)48)49)
    50)
    
    アンバランス
    (51
    (50
    (49(48(47(46(45(44(43(42(41(40(39(38(37(36(35(34(33(32(31(30(29(28(27(26(25(24(23(22(21(20(19(18(17(16(15(14(13(12(11(10(9(8(7(6(5(4(3(2(1( 0 )1)2)3)4)5)6)7)8)9)10)11)12)13)14)15)16)17)18)19)20)21)22)23)24)25)26)27)28)29)30)31)32)33)34)35)36)37)38)39)40)41)42)43)44)45)46)47)48)49)
    50)
    
    (19(18(17(16(15(14(13(12(11(10(9(8(7(6(5(4(3(2(1( 0 )1)2)3)4)5)6)7)8)9)10)11)12)13)14)15)16)17)18)19)
    
    アンバランス
    (19(18(17(16(15(14(13(12(11(10(9(8(7(6(5(4(3(2(1( 0 )1)2)3)4)5)6)7)8)9)10)11)12)13)14)15)16)17)18)
    
    中カッコ
    
    {50
    {49{48{47{46{45{44{43{42{41{40{39{38{37{36{35{34{33{32{31{30{29{28{27{26{25{24{23{22{21{20{19{18{17{16{15{14{13{12{11{10{9{8{7{6{5{4{3{2{1{ 0 }1}2}3}4}5}6}7}8}9}10}11}12}13}14}15}16}17}18}19}20}21}22}23}24}25}26}27}28}29}30}31}32}33}34}35}36}37}38}39}40}41}42}43}44}45}46}47}48}49}
    50}
    
    アンバランス
    {51
    {50
    {49{48{47{46{45{44{43{42{41{40{39{38{37{36{35{34{33{32{31{30{29{28{27{26{25{24{23{22{21{20{19{18{17{16{15{14{13{12{11{10{9{8{7{6{5{4{3{2{1{ 0 }1}2}3}4}5}6}7}8}9}10}11}12}13}14}15}16}17}18}19}20}21}22}23}24}25}26}27}28}29}30}31}32}33}34}35}36}37}38}39}40}41}42}43}44}45}46}47}48}49}
    50}
    
     |  虚inuuik  |  返信
  27. ご返信ありがとうございます。いえいえ、特に急いでいませんのでごゆっくりで構いませんよ。

    > OnigLen を size_t にして、OnigPos として ptrdiff_t にしてます。
    > regexec.c と regtrav.c の beg / end を int から OnigPos にしました。
    > これでメモリ違反が起きるかどうか…(^^;;;

    私も上記のカスタマイズで 64 ビット版の onig.dll をビルドしてみました。

    > 試しましたが 2013-03 の Mery 2.018 32bit までは速いのですが、それ以降はどれも同じような感じです。
    > トレードオフとはいえ…、も少しなんとかしないと。何かを間違っているような気も、、、(笑)

    私の環境ですと速度はそれほど変わらないようですから、何か設定が関係しているのかもしれません。

    > 同梱するなら、やはり2つ並べて
    > onigmo_COPYING.txt
    > oniguruma_COPYING.txt
    > としないとマズイと思います。

    やはりそうですか。うーん、ライセンス関係は苦手です… ^^; 同梱しないという手もアリですね。

    > 鬼車と鬼雲の仕様上の大きな違いの1つは、\s \d \w のデフォルト範囲です。
    > たとえば、\s が [ \t\n] の前提だと、\x{3000} 全角空白 などへの考慮が必要になります。

    詳細な情報ありがとうございます。ほんとだ…、試してみたら \w とか挙動が変わってますね。\d が全角数字にもヒットするのは面白いですね。

    あぁ、確かに \s が全角空白にもヒットするようになるのは好ましくないというかたもいそうな気はします。

    > 「可変長の戻り読み」は編集モードの強調文字列

    これは面白いですね。せっかくの正規表現ですが、色分けが改行を越えられないのは残念です。

    と言っても Mery 側で改行を越えさせないように制限をかけているだけなので、動作速度の低下を気にしなければ 1 行ぐらいまでなら越えられるようにしても良いかもしれませんが。

    > 括弧 () 入れ子 選択 (再帰による階層深さ制限)

    サンプルのマクロ、ありがとうございます。これで動作速度の検証をさせていただきました。

    ゆっくりと外側から内側に置換が進む様子が見えるとのことですが、私の環境ですと鬼雲とほぼ同じ速度で動作していますので、検証した内容を記載しておきますね。

    環境: Windows 10 1909 64 ビット, CPU: Core2Duo E8500 3.16GHz, RAM 4.00 GB

    マクロはミリ秒単位で速度を計測できるように以下のようにしてみました。

    var n = Date.now();
    var re = "(?<a>(?:\\((?<z>(?>[^()\\\\]|(?:\\\\[\\s\\S]))*(?:\\g<a>(?>[^()\\\\]|(?:\\\\[\\s\\S]))*)*+)\\)))";
    var te = "{\\k'z'}";
    var fl = (meFindNext | meFindReplaceCase | meFindReplaceRegExp | meReplaceSelOnly | meReplaceAll);
    while(document.selection.Replace(re, te, fl)) { ; }
    alert(Date.now() - n);
    

    Mery はポータブル版でデフォルト設定のまま使用。Oniguruma Release 6.9.5 を、32 ビット版はそのままビルド、64 ビット版はご提示いただいたカスタマイズでビルドしました。

    いただいたテスト対象テキストで一番上の「カッコ」の項目の内容を選択してマクロを実行。実行速度はミリ秒単位で、3 回の平均値。

    Mery Ver 3.0.3 x86 + onigmo 6.2.0
    → 295 ms

    Mery Ver 3.0.3 x86 + oniguruma 6.9.5
    → 297 ms

    Mery Ver 3.0.3 x64 + onigmo 6.2.0
    → 371 ms

    Mery Ver 3.0.3 x64 + oniguruma 6.9.5 (ご提示いただいたカスタマイズで 64 ビット版をビルド)
    → 385 ms

    Mery Ver 3.0.4 x64 (仮) + oniguruma 6.9.5 (鬼車のソースを変更せずにビルド、Mery 側で int 対応)
    → 379 ms

    Mery Ver 2.0.18.4293 x86 + oniguruma 6.9.5
    → jscript9 未対応なので Date が使えず計測はしていませんが、この組み合わせだと置換されていく様子が見えるレベルでゆっくりでした。

    32 ビット版より 64 ビット版のほうが遅いですが、鬼車のソースの変更の有無はそれほど関係がない模様です。

    というわけで鬼雲と速度の違いは分からないレベルでしたが、検証の方法が間違ってるかも…。

    明らかにおかしいレベルでの動作速度の低下が発生するようであれば調査しやすいのですが、適当に触っている感じだとそれほど遅さを感じないです。

    > 220万行テキストの終端から 75万行目にある文字列を探すと、待ち時間が体感で長くなっているのがわかります。

    この場合は速度が全然違いますね、oniguruma だとフリーズしたかと思ってしまうレベルでした。

    もしかして動作速度が遅いというのは「カッコ」の選択のお話ではなく、こちらのケースのことだったりしますか?

    この場合は、Mery Ver 2.0.18 + oniguruma 6.9.5 だと速かったです。

    正確には Ver 2.0.18 が境目ではなさそうですが、当時、64 ビット対応のときに鬼雲にするか bregonig.dll にするかで悩んでいて、いつでも切り替えできるように、逆方向検索を bregonig.dll 向けに遠回りな実装にしていたことが原因っぽいです。

    シンプルに oniguruma の逆方向検索機能を使う実装 (Mery Ver 2.0.18 あたりの実装) に戻してみたところ、oniguruma でも逆方向検索が高速化されました。

    ちなみに onigmo ならもっと速くなるのかと思いきや、これはそうでもなくて、oniguruma と onigmo でほとんど同じ速度でした。

     |  Kuro  |  返信
  28. たいへんにお手間をとっていただき、ありがとうございました。
    計時は根気ですが…、とても参考になります。

    > 私の環境ですと速度はそれほど変わらないようですから、何か設定が関係しているのかもしれません。

    おっしゃる通りでした。状況説明も足らず、申し訳ないです。
    体感で遅く感じたのは、逆方向の検索を多用していたためでした。

    また、日頃の習慣で、少しでもリアルな状況の動作を見るために、テスト用テキストは、その前後を含めて、いくつも連続して貼り付け、全体として 1 ~ 2 千行になるくらい、水増ししてました。
    さらに eval によるマクロ実行も併用していたり、いろいろストレスをかけていたためだと思います。
    検索結果強調表示やスクロールバーマーカーがあるので、これだと動作速度は大幅に低下します。

    MeryPortable 3.0.3 での動作では、逆方向検索のほかは、今のところ、目立って遅いところは見つけていません。

    oniguruma 6.9.5 Release のバグ修正版 6.9.5_rev1 が公開されました。

    2020/04/20: Version 6.9.5
    2020/04/26: Version 6.9.5 revised 1

    vscode 用 oniguruma の WASM (WebAssembly) bindings である Microsoft スタッフの vscode-oniguruma 関連でバグ報告された、戻り読みの問題点に対応した修正です。

    Sublime Text では検索/置換は別の正規表現エンジンですが、シンタックス・ハイライトの TextMate Grammar (TM Grammar) に鬼車が使われています。Atom や VS Code も似た状況です。

    > あぁ、確かに \s が全角空白にもヒットするようになるのは好ましくないというかたもいそうな気はします。

    これは onigmo の Ruby 文法の仕様を主に扱う方にとっては都合がよいのでしょう。
    Unicode 対応ができる正規表現なら、デフォルトは限定されていても良いのですが、面倒なのは、それを解除するオプション表記が違うため、同じ正規表現を共用できず、書き換えないとならないことですね。

    それと鬼車は Ruby 文法ではなく、新機能が増えたので oniguruma 文法が標準になっています。

    > > 「可変長の戻り読み」は編集モードの強調文字列
    >
    > これは面白いですね。せっかくの正規表現ですが、色分けが改行を越えられないのは残念です。
    >
    > と言っても Mery 側で改行を越えさせないように制限をかけているだけなので、動作速度の低下を気にしなければ 1 行ぐらいまでなら越えられるようにしても良いかもしれませんが。

    動作があぶないとはわかっていても、その景色を見てみたい気持ちは、やはりあります。
    処理速度はやがて速い機種になれば問題が軽くなりますが、制限事項はいつまでもずっとそのままですからね。

    > サンプルのマクロ、ありがとうございます。これで動作速度の検証をさせていただきました。

    これは小さいわりに重いので、意外にも計時には都合が良いものだったかもしれないですね…
    前に書いた通り、こちらでも、鬼車は鬼雲とほぼ同じか、やや速く動作しています。

    だいたい同じような構成で試しました。
    環境: Windows 10 1909 64 ビット, CPU: Core2 Quad Q9550 2.83GHz, RAM 4.00 GB GPU なし(^^;

    polyfill を加えて、JScript でも測れるようにしました。

    JScript9 環境での JScript はこちら

    #language = "JScript"
    // -----------------------------------------------------------------------------
    // 計時カッコ置換1.js
    // 
    if (!Date.now) { Date.now = function now() { return new Date().getTime(); }; }  // polyfill
    var n = Date.now();
    var re = "(?<a>(?:\\((?<z>(?>[^()\\\\]|(?:\\\\[\\s\\S]))*(?:\\g<a>(?>[^()\\\\]|(?:\\\\[\\s\\S]))*)*+)\\)))";
    var te = "{\\k'z'}";
    var fl = (meFindNext | meFindReplaceCase | meFindReplaceRegExp | meReplaceSelOnly | meReplaceAll);
    while(document.selection.Replace(re, te, fl)) { ; }
    alert(Date.now() - n);
    // -----------------------------------------------------------------------------
    

    通常の JScript9 と JScript はこちら

    // -----------------------------------------------------------------------------
    // 計時カッコ置換.js
    // 
    if (!Date.now) { Date.now = function now() { return new Date().getTime(); }; }  // polyfill
    var n = Date.now();
    var re = "(?<a>(?:\\((?<z>(?>[^()\\\\]|(?:\\\\[\\s\\S]))*(?:\\g<a>(?>[^()\\\\]|(?:\\\\[\\s\\S]))*)*+)\\)))";
    var te = "{\\k'z'}";
    var fl = (meFindNext | meFindReplaceCase | meFindReplaceRegExp | meReplaceSelOnly | meReplaceAll);
    while(document.selection.Replace(re, te, fl)) { ; }
    alert(Date.now() - n);
    // -----------------------------------------------------------------------------
    

    > Mery はポータブル版でデフォルト設定のまま使用。Oniguruma Release 6.9.5 を、32 ビット版はそのままビルド、64 ビット版はご提示いただいたカスタマイズでビルドしました。

    こちらでは VS2017 と LLVM 10.0.0 で 6.9.5_rev1 をそれぞれビルドしています。

    > いただいたテスト対象テキストで一番上の「カッコ」の項目の内容を選択してマクロを実行。実行速度はミリ秒単位で、3 回の平均値。

    同じ条件にしました。
    ストレスの積み増しはしてません。

    > Mery Ver 3.0.3 x86 + onigmo 6.2.0
    > → 295 ms

    JScript9
    → 213 ms

    JScript
    → 218 ms

    > Mery Ver 3.0.3 x86 + oniguruma 6.9.5
    > → 297 ms

    LLVM
    JScript9
    → 191 ms

    LLVM
    JScript
    → 187 ms

    JScript9
    → 191 ms

    JScript
    → 187 ms

    > Mery Ver 3.0.3 x64 + onigmo 6.2.0
    > → 371 ms

    JScript9
    → 192 ms

    JScript
    → 188 ms

    > Mery Ver 3.0.3 x64 + oniguruma 6.9.5 (ご提示いただいたカスタマイズで 64 ビット版をビルド)
    > → 385 ms

    LLVM
    JScript9
    → 175 ms

    LLVM
    JScript
    → 187 ms

    JScript9
    → 189 ms

    JScript
    → 182 ms

    > Mery Ver 2.0.18.4293 x86 + oniguruma 6.9.5

    LLVM
    JScript
    → 2067 ms

    JScript
    → 1984 ms

    > 32 ビット版より 64 ビット版のほうが遅いですが、鬼車のソースの変更の有無はそれほど関係がない模様です。

    ここでは 32 ビットと 64 ビットの差はあまり目立ちませんでしたが、変更の影響については同感です。

    ということで 64bit と 32bit の差は小さく、さらに LLVM は 64bit の JScript9 に効果があるが、32bit ではなくてもほぼ同じ、という概況でした。ずいぶん環境によって違うような気がします…

    > > 220万行テキストの終端から 75万行目にある文字列を探すと、待ち時間が体感で長くなっているのがわかります。

    > この場合は速度が全然違いますね、oniguruma だとフリーズしたかと思ってしまうレベルでした。

    > もしかして動作速度が遅いというのは「カッコ」の選択のお話ではなく、こちらのケースのことだったりしますか?

    カッコもそうでした(思い込み)が、こちらが大いに関係していました。

    > この場合は、Mery Ver 2.0.18 + oniguruma 6.9.5 だと速かったです。

    > シンプルに oniguruma の逆方向検索機能を使う実装 (Mery Ver 2.0.18 あたりの実装) に戻してみたところ、oniguruma でも逆方向検索が高速化されました。

    > ちなみに onigmo ならもっと速くなるのかと思いきや、これはそうでもなくて、oniguruma と onigmo でほとんど同じ速度でした。

    もしこれが oniguruma で改善されるのなら、本当にありがたいです。
    長いこと気付かなかった…

    逆方向検索は、正規表現エンジン作成者の方たちが、それぞれに技を詰め込んでいるところなので、動作条件が微妙なのかもしれませんね。

    この調子だと、なんとなく、行けるかも…、という感じですかね ww

     |  虚inuuik  |  返信
スポンサーリンク