古参の野良用法

  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さんです

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

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

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

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

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

     |  シリル  |  返信
スポンサーリンク