「選択範囲を広げる」の版間の差分

提供: MeryWiki
ナビゲーションに移動 検索に移動
Yuko (トーク | 投稿記録)
編集の要約なし
MSY-07 (トーク | 投稿記録)
SyntaxHighlightにcopyの追加
 
(2人の利用者による、間の21版が非表示)
1行目: 1行目:
== 概要 ==
選択範囲を徐々に広げます。VSCode や IntelliJ っぽいやつです。
選択範囲を徐々に広げます。VSCode や IntelliJ っぽいやつです。


私は Sublime Text に倣い、Ctrl+Shift+Space に割り当てて使っています。
私は Sublime Text に倣い、Ctrl+Shift+Space に割り当てて使っています。


未選択状態での動作:
== 動作 ==
=== 未選択状態での動作 ===
* 選択範囲が空の場合、カーソル位置の単語を選択します。
* カーソル位置が括弧だった場合は、括弧の中身も選択します。
* ドットを検出した場合は、ドットの1つ右の単語も選択します。
* ハイフンを検出した場合は、ハイフンで区切られた1つ右の単語も選択します。


*カーソルの左、もしくは右に括弧記号がある場合は括弧の中全体を選択
=== 選択状態での動作 ===
*それ以外はカーソル位置の単語を選択
* 選択範囲がある場合、選択範囲の前後にある括弧を検出し、その中身を選択します。
* ドットを検出した場合は、ドットの1つ左または右の単語まで拡張します。
* ハイフンを検出した場合は、ハイフンで区切られた1つ右の単語まで拡張します。
* これらの記号を検出しなかった場合は、単純な選択範囲の拡張を行います。


選択状態での動作:
== ソースコード ==
<syntaxhighlight lang="javascript" copy>
#title = "選択範囲を広げる"
/**
* 選択範囲を広げるためのマクロです。
*
* 選択範囲が空の場合、カーソル位置の単語を選択します。
* カーソル位置が括弧だった場合は、括弧の中身も選択します。
* ドットを検出した場合は、ドットの1つ右の単語も選択します。
* ハイフンを検出した場合は、ハイフンで区切られた1つ右の単語も選択します。
*
* 選択範囲がある場合、選択範囲の前後にある括弧を検出し、その中身を選択します。
* ドットを検出した場合は、ドットの1つ左または右の単語まで拡張します。
* ハイフンを検出した場合は、ハイフンで区切られた1つ右の単語まで拡張します。
* これらの記号を検出しなかった場合は、単純な選択範囲の拡張を行います。
*/


*選択状態によって選択範囲を広げる
**選択範囲の開始位置に括弧の開始記号がある場合は、括弧全体に選択範囲を広げる
**上記の条件に当てはまらず、選択範囲末尾の右側に括弧の終了記号がある場合は、括弧全体に選択範囲を広げる
**上記の条件に当てはまらず、選択範囲末尾の右側にドットがある場合には、そのドット連なりの一番右側の単語まで選択範囲を広げる
**上記の条件に当てはまらず、選択範囲末尾の右側に括弧の開始記号がある場合には、その括弧を全て洗濯する
**上記の動作によって選択範囲が広がらなかった場合には、選択範囲を左右それぞれ1単語ずつ広げる
<source lang="javascript">
#title="選択範囲を広げる"
BeginUndoGroup();
BeginUndoGroup();
Redraw = false;
Redraw = false;


//■括弧の定義(0+2n:開き/1+2n:閉じ)
//■括弧の定義(0+2n:開き/1+2n:閉じ)
var BRACKET = '()<>[]{}「」『』【】()';
var BRACKET = '()<>[]{}「」『』【】()[]{}〈〉《》〔〕〘〙〚〛〖〗‘’“”';


var doc = document;
var sel = document.selection;
var sel = document.selection;
var txt = Document.Text;
var txt = document.Text;
 
function main() {
 
    if (sel.IsEmpty) {
        // 未選択の場合
        var beforePos = sel.GetActivePos();
        sel.SelectWord();


if (sel.IsEmpty) {
        var selChar = sel.Text;
  var selectStartPos = selectBottomPos = sel.GetActivePos();
        var selCharIndex = BRACKET.indexOf(selChar);
  if (leftCharIsStartBracket(selectStartPos)
        if (selChar.length === 1 && selCharIndex !== -1) {
      || rightCharIsEndBracket(selectBottomPos)) {
            // 選択文字が括弧記号1文字の場合、括弧の中身を選択
    // カーソルの左右いずれかに開き括弧があった場合
 
    moveToBracketEnd(selectStartPos, selectBottomPos);
            if (selCharIndex % 2 === 1) {
  } else {
                // 閉じ括弧の場合
    // 単語を選択
                sel.CharLeft();
    sel.SelectWord();
                var topX = sel.GetTopPointX(mePosLogical);
  }
                var topY = sel.GetTopPointY(mePosLogical);
} else {
                var bottomX = sel.GetBottomPointX(mePosLogical);
  expandSelection();
                var bottomY = sel.GetBottomPointY(mePosLogical);
}
                selectToBracketStart(topX, topY, bottomX, bottomY, selChar);
            } else if (selCharIndex % 2 === 0) {
                // 開き括弧の場合
                var topX = sel.GetTopPointX(mePosLogical);
                var topY = sel.GetTopPointY(mePosLogical);
                var bottomX = sel.GetBottomPointX(mePosLogical);
                var bottomY = sel.GetBottomPointY(mePosLogical);
                var activePos = sel.GetActivePos();
                selectToBracketEnd(topX, topY, bottomX, bottomY, selChar);
                if (beforePos === activePos) {
                    // 行末で先頭方向に向かって選択していた場合は、先頭の開き括弧の選択を外す
                    sel.SetAnchorPos(beforePos);
                } else if (activePos !== sel.GetActivePos()) {
                    // 括弧の中身が選択された場合、閉じ括弧も選択する
                    sel.SetActivePos(sel.GetActivePos() + 1, true);
                }
            }
        } else if (selChar === ".") {
            // ドットの場合
            // ドットの1つ右の単語まで拡張
            selectFuncNameToChild(sel.GetActivePos() - 1);
        } else if (selChar === "-") {
            // ドットの場合
            // ドットの1つ右の単語まで拡張
            selectHyphenSeparatedWord(sel.GetActivePos() - 1);
        }
    } else {
        // 選択済みの場合


function expandSelection() {
        // 現在の選択範囲を取得
  var EXTEND_ON = true;
        var topX = sel.GetTopPointX(mePosLogical);
  var EXTEND_OFF = false;
        var topY = sel.GetTopPointY(mePosLogical);
        var bottomX = sel.GetBottomPointX(mePosLogical);
        var bottomY = sel.GetBottomPointY(mePosLogical);


  // 選択範囲取得
        // 選択範囲の左右の文字を取得
  var topX = sel.GetTopPointX(mePosLogical);
        var leftChar = "dummy";
  var topY = sel.GetTopPointY(mePosLogical);
        if (topX - 2 >= 0) leftChar = doc.GetLine(topY, meGetLineWithNewLines).charAt(topX - 2);
  var bottomX = sel.GetBottomPointX(mePosLogical);
        var rightChar = doc.GetLine(bottomY, meGetLineWithNewLines).charAt(bottomX - 1);
  var bottomY = sel.GetBottomPointY(mePosLogical);
        var rightIndex = BRACKET.indexOf(rightChar);
        var leftIndex = BRACKET.indexOf(leftChar);


  // 選択終了位置のポジション取得
        if (rightChar === ".") {
  sel.SetActivePoint(mePosLogical, bottomX, bottomY, EXTEND_OFF);
            // 選択範囲の右にドットがある場合
  var selectBottomPos = sel.GetActivePos();
            // 末尾方向にドットを検索
            sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
            selectFuncNameToChild(sel.GetActivePos());
            sel.SetAnchorPoint(mePosLogical, topX, topY);
        } else if (rightChar === "-") {
            // 選択範囲の右にハイフンがある場合
            sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
            selectHyphenSeparatedWord(sel.GetActivePos());
            sel.SetAnchorPoint(mePosLogical, topX, topY);
        } else if (rightIndex % 2 === 0) {
            // 右位置に開き括弧があるかをチェック
            // 選択範囲を広げる
            selectToBracketEnd(topX, topY, bottomX + 1, bottomY, rightChar);
            // 閉じ括弧も選択する
            sel.CharRight(true);
        } else if (leftChar === ".") {
            // 選択範囲の左にドットがある場合
            // 先頭方向にドットを検索
            sel.SetActivePoint(mePosLogical, topX, topY, false);
            selectFuncNameToParent(sel.GetActivePos() - 2);
            sel.SetAnchorPoint(mePosLogical, bottomX, bottomY);
        } else if (rightIndex % 2 === 1) {
            // 右位置に閉じ括弧があるかをチェック
            // 選択範囲を広げる
            selectToBracketStart(topX, topY, bottomX, bottomY, rightChar);
        } else if (leftIndex % 2 === 0) {
            // 左位置に括弧があるかをチェック
            // 選択範囲を広げる
            selectToBracketEnd(topX, topY, bottomX, bottomY, leftChar);
        } else {
            // いずれでもない場合は、選択開始位置と終了位置の両方で単語選択を行う
            simpleExpand(topX, topY, bottomX, bottomY, 0);
        }


  // 選択開始位置に移動
        var afterTopX = sel.GetTopPointX(mePosLogical);
  sel.SetActivePoint(mePosLogical, topX, topY, EXTEND_OFF);
        var afterTopY = sel.GetTopPointY(mePosLogical);
  var selectTopPos = sel.GetActivePos();
        var afterBottomX = sel.GetBottomPointX(mePosLogical);
        var afterBottomY = sel.GetBottomPointY(mePosLogical);


  var selectStartPos, selectEndPos;
        // 選択範囲を広げる処理の後も同じ位置の場合、通常の選択範囲を広げる処理を行う
  if (selectTopPos <= selectBottomPos) {
        if (topX === afterTopX
    selectStartPos = selectTopPos;
            && topY === afterTopY
    selectEndPos = selectBottomPos;
            && bottomX === afterBottomX
  } else {
            && bottomY === afterBottomY) {
    selectStartPos = selectBottomPos;
            simpleExpand(afterTopX, afterTopY, afterBottomX, afterBottomY, 1);
     selectEndPos = selectTopPos;
        }
  }
     }
}


  if (bothSideCharIsBracket(selectStartPos, selectEndPos)) {
function selectFuncNameToChild(pos) {
     // 両脇が括弧だった場合
     // "$" はEOFへの対応
     sel.CharLeft(EXTEND_OFF);
     var movePos = txt.slice(pos + 1).search(/[^a-z0-9_-]|$/i) + pos + 1;
    sel.SetActivePos(selectEndPos, EXTEND_ON);
     sel.SetActivePos(movePos, true);
    sel.CharRight(EXTEND_ON);
}
  } else if (
    leftCharIsStartBracket(selectStartPos) ||
    rightCharIsEndBracket(selectEndPos)
  ) {
    // 左右どちらかが括弧だった場合
    moveToBracketEnd(selectStartPos, selectEndPos);
  } else if (txt.charAt(selectEndPos) === '.') {
    // 選択範囲末尾右側がドットだった場合
    selectRightSideWordsOfDot(selectStartPos, selectEndPos);
  } else if (leftCharIsStartBracket(selectEndPos + 1)) {
    // 選択範囲右側が開始括弧だった場合
    selectRightSideWordsOfBracket(selectStartPos, selectEndPos);
  } else {
    // 選択範囲を拡大
    sel.WordLeft(EXTEND_OFF);
     sel.SetActivePos(selectEndPos, EXTEND_ON);
    sel.WordRight(EXTEND_ON);
  }


  // 選択範囲拡大後の選択範囲取得
function selectFuncNameToParent(pos) {
  var expandedTopX = sel.GetTopPointX(mePosLogical);
    var toSeparator1 = txt.lastIndexOf(".", pos);
  var expandedTopY = sel.GetTopPointY(mePosLogical);
    var toSeparator2 = txt.lastIndexOf("\n", pos);
  var expandedBottomX = sel.GetBottomPointX(mePosLogical);
    var toSeparator3 = txt.lastIndexOf(" ", pos);
  var expandedBottomY = sel.GetBottomPointY(mePosLogical);
    var toSeparator4 = txt.lastIndexOf("\t", pos);
    var toSeparator5 = txt.lastIndexOf(" ", pos);
    var maxIndex = Math.max(toSeparator1, toSeparator2, toSeparator3, toSeparator4, toSeparator5);
    sel.SetActivePos(maxIndex + 1, true);
}


  // 選択範囲が変わっていない場合は、左右1単語ずつ拡大する
function selectHyphenSeparatedWord(pos) {
  if (
     // "$" はEOFへの対応
     topX === expandedTopX &&
     var movePos = txt.slice(pos + 1).search(/[^a-z0-9_]|$/i) + pos + 1;
    topY === expandedTopY &&
     sel.SetActivePos(movePos, true);
     bottomX === expandedBottomX &&
    bottomY === expandedBottomY
  ) {
    sel.SetActivePos(selectStartPos);
    sel.WordLeft(EXTEND_OFF);
     sel.SetActivePos(selectEndPos, EXTEND_ON);
    sel.WordRight(EXTEND_ON);
  }
}
}


function selectRightSideWordsOfDot(selectStartPos, selectEndPos) {
// 単純な選択範囲の拡大
  if (txt.charAt(selectEndPos) !== '.') {
function simpleExpand(topX, topY, bottomX, bottomY, offset) {
     return;
     // 拡大範囲は行を跨がないようにする
  }


  var EXTEND_ON = true;
    // 左側の拡大後の位置を取得
    var simpleExpandTopX = sel.GetTopPointX(mePosLogical);
    var simpleExpandTopY = sel.GetTopPointY(mePosLogical);
    sel.SetActivePoint(mePosLogical, topX, topY, false);
    var leftChar = txt.charAt(sel.GetActivePos() - 1);
    if (leftChar !== "\n" && leftChar !== "") {
        // 行頭以外のときだけ、左側を拡大
        sel.CharLeft(false, 1 + offset);
        sel.SelectWord();
        simpleExpandTopX = sel.GetTopPointX(mePosLogical);
        simpleExpandTopY = sel.GetTopPointY(mePosLogical);
    }


  sel.SetActivePos(selectStartPos);
    // 右側の拡大後の位置を取得
  sel.SetActivePos(selectEndPos, EXTEND_ON);
    sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
  sel.CharRight(EXTEND_ON);
    var simpleExpandBottomX = sel.GetBottomPointX(mePosLogical);
  sel.WordRight(EXTEND_ON);
    var simpleExpandBottomY = sel.GetBottomPointY(mePosLogical);
    var rightChar = txt.charAt(sel.GetActivePos());
    if (rightChar !== "\n" && rightChar !== "") {
        // 行末以外のときだけ、右側を拡大
        if (offset > 0) {
            sel.CharRight(false, offset);
        }
        sel.SelectWord();
        simpleExpandBottomX = sel.GetBottomPointX(mePosLogical);
        simpleExpandBottomY = sel.GetBottomPointY(mePosLogical);
    }


  selectRightSideWordsOfDot(selectStartPos, sel.GetActivePos());
    // 選択範囲を広げる
    sel.SetActivePoint(mePosLogical, simpleExpandTopX, simpleExpandTopY, false);
    sel.SetActivePoint(mePosLogical, simpleExpandBottomX, simpleExpandBottomY, true);
}
}


function selectRightSideWordsOfBracket(selectStartPos, selectEndPos) {
// 先頭方向の開き括弧まで選択範囲を広げる
  if (!leftCharIsStartBracket(selectEndPos + 1)) {
function selectToBracketStart(topX, topY, bottomX, bottomY, endChar) {
    return;
    var endBracketIndex = BRACKET.indexOf(endChar);
  }


  moveToBracketEnd(selectEndPos + 1, selectEndPos + 1);
    sel.SetActivePoint(mePosLogical, topX, topY, false);
  var movedEndPos = sel.GetActivePos() + 1;
    var startPos = sel.GetActivePos();


  var EXTEND_ON = true;
    sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
    var endPos = sel.GetActivePos();


  sel.SetActivePos(selectStartPos);
    var nest = 1;
  sel.SetActivePos(movedEndPos, EXTEND_ON);
    var s;
}
    var e;
    var pairChar;


function leftCharIsStartBracket(selectStartPos) {
     pairChar = BRACKET.charAt(endBracketIndex - 1); //対応する開き括弧を取得
  var startPos;
    e = txt.lastIndexOf(endChar, endPos);
  if (selectStartPos) {
     startPos = selectStartPos;
  } else {
    startPos = Document.Selection.GetActivePos();
  }
  var startChar = txt.charAt(startPos - 1); //カーソル左側の文字を取得
  var startBrcIdx = BRACKET.indexOf(startChar);
  return startBrcIdx !== -1 && startChar !== '' && startBrcIdx % 2 === 0;
}


function rightCharIsEndBracket(selectEndPos) {
    while (nest) {
  var endPos;
        s = txt.lastIndexOf(pairChar, e - 1);
  if (selectEndPos) {
        e = txt.lastIndexOf(endChar, e - 1);
    endPos = selectEndPos;
        if (s === -1 || startPos <= 0) break;
  } else {
        if (s < e && s !== -1) {
    endPos = Document.Selection.GetActivePos();
            nest++;
  }
            s = e;
  var endChar = txt.charAt(endPos); //カーソル右側の文字を取得
        } else {
  var endBrcIdx = BRACKET.indexOf(endChar);
            nest--;
  return endBrcIdx !== -1 && endChar !== '' && endBrcIdx % 2 === 1;
            e = s;
}
        }
        if (s !== -1 && startPos > s) {
            startPos = s + 1;
        }
    }


function bothSideCharIsBracket(selectStartPos, selectEndPos) {
    sel.SetActivePos(startPos);
  var txt = Document.Text;
    sel.SetActivePos(endPos, true);
  var sPos = selectStartPos; //カーソル始点
  var ePos = selectEndPos; //カーソル終点
  var sBrc = txt.charAt(sPos - 1); //カーソル始点左側の文字を取得
  var eBrc = txt.charAt(ePos); //カーソル終点右側の文字を取得
  var isBrc = BRACKET.indexOf(sBrc);
  var ieBrc = BRACKET.indexOf(eBrc);
  return (
    !(isBrc === -1 || sBrc === '') &&
    !(ieBrc === -1 || eBrc === '') &&
    ieBrc - isBrc === 1
  );
}
}


function moveToBracketEnd(selectStartPos, selectEndPos) {
// 末尾方向の閉じ括弧まで選択範囲を広げる
  //■範囲選択(true:する/false:しない)
function selectToBracketEnd(topX, topY, bottomX, bottomY, startChar) {
  var SHIFT = true;
     var startBracketIndex = BRACKET.indexOf(startChar);
 
  quit: {
     var startPos = selectStartPos;
    var endPos = selectEndPos;


     var startChar = txt.charAt(startPos - 1); //選択範囲開始左側の文字を取得
     sel.SetActivePoint(mePosLogical, topX, topY, false);
     var startBrcIdx = BRACKET.indexOf(startChar);
     var startPos = sel.GetActivePos();


     var endChar = txt.charAt(selectEndPos); //選択範囲末尾右側の文字を取得
     sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
     var endBrcIdx = BRACKET.indexOf(endChar);
     var endPos = sel.GetActivePos();


     var nest = 1;
     var nest = 1;
199行目: 267行目:
     var e;
     var e;
     var pairChar;
     var pairChar;
    // if (startBrcIdx !== -1 && startChar !== '' && startBrcIdx % 2 === 0) {
 
     if (leftCharIsStartBracket(selectStartPos)) {
     pairChar = BRACKET.charAt(startBracketIndex + 1); //対応する閉じ括弧を取得
      //◆開き括弧の場合、末尾方向へ探す
    s = txt.indexOf(startChar, startPos - 1);
      pairChar = BRACKET.charAt(startBrcIdx + 1); //対応する閉じ括弧を取得
    while (nest) {
      s = txt.indexOf(startChar, startPos - 1);
      while (nest) {
         e = txt.indexOf(pairChar, s + 1);
         e = txt.indexOf(pairChar, s + 1);
         s = txt.indexOf(startChar, s + 1);
         s = txt.indexOf(startChar, s + 1);
         if (e === -1) break;
         if (e === -1) break;
         if (s < e && s !== -1) {
         if (s < e && s !== -1) {
          nest++;
            nest++;
          e = s;
            e = s;
         } else {
         } else {
          nest--;
            nest--;
          s = e;
            s = e;
         }
         }
         if (e !== -1 && endPos < e) {
         if (e !== -1 && endPos < e) {
          endPos = e;
            endPos = e;
        }
      }
    // } else if (endBrcIdx !== -1 && endChar !== '' && endBrcIdx % 2 === 1) {
    } else if (rightCharIsEndBracket(selectEndPos)) {
      //◆閉じ括弧の場合、先頭方向へ探す
      pairChar = BRACKET.charAt(endBrcIdx - 1); //対応する開き括弧を取得
      e = txt.lastIndexOf(endChar, endPos);
      while (nest) {
        s = txt.lastIndexOf(pairChar, e - 1);
        e = txt.lastIndexOf(endChar, e - 1);
        if (s === -1 || startPos <= 0) break;
        if (s < e && s !== -1) {
          nest++;
          s = e;
        } else {
          nest--;
          e = s;
         }
         }
        if (s !== -1 && startPos > s) {
          startPos = s + 1;
        }
      }
    } else {
      break quit;
     }
     }
     sel.SetActivePos(startPos);
     sel.SetActivePos(startPos);
     sel.SetActivePos(endPos, SHIFT);
     sel.SetActivePos(endPos, true);
  }
}
 
function doMultiAction(fn) {
    var d = document, s = d.selection;
    s.Mode = meModeMulti;
    // ① まず、GetActivePos と GetAnchorPos で複数選択のリストを作成します
    var selections = [{ s: s.GetAnchorPos(), e: s.GetActivePos() }];
    if (s.Count > 0) {
        selections = [];
        for (var i = 0; i < s.Count; i++)
            selections.push({ s: s.GetActivePos(i), e: s.GetAnchorPos(i) });
    }
    var p = 0;
    var q = 0;
    for (var i = 0; i < selections.length; i++) {
        // ② そのリストを上から順に SetActivePos で、「シングルカーソル」で範囲選択します。この段階で複数選択は解除され、通常のマクロ操作が可能となります
        s.SetActivePos(selections[i].s + p);
        s.SetActivePos(selections[i].e + p, true);
        q = d.TextLength;
        // ③ <ここで通常のマクロの機能を使って普通に編集などを行います>
        fn();
        p += d.TextLength - q;
        // ④ 処理を行った後の選択範囲をリストに反映し、これをリストの最後まで繰り返します
        selections[i] = { s: s.GetAnchorPos(), e: s.GetActivePos() };
    }
    // ⑤ そのリストを上から順に document.selection.AddPos(StartPos, EndPos) で複数選択として復元します
    for (var i = 0; i < selections.length; i++)
        s.AddPos(selections[i].s, selections[i].e);
}
 
if (sel.Mode === meModeMulti) {
    doMultiAction(main);
} else {
    main();
}
}
</syntaxhighlight>


</source>
== 変更履歴 ==
* 1.0.3 (2023-07-21)
** 処理を見直し、より適切な範囲に拡張するよう調整しました
* 1.0.2 (2020-03-22)
** Mery Ver.3.0.1 マルチカーソル対応化の試験実装メソッドを使った実装を追加しました
* 1.0.1 (2020-03-15)
** マルチカーソル時の動作を考慮し処理を追加しました
* 1.0.0 (2020-02-24)
** 初版

2025年6月30日 (月) 00:10時点における最新版

概要[編集]

選択範囲を徐々に広げます。VSCode や IntelliJ っぽいやつです。

私は Sublime Text に倣い、Ctrl+Shift+Space に割り当てて使っています。

動作[編集]

未選択状態での動作[編集]

  • 選択範囲が空の場合、カーソル位置の単語を選択します。
  • カーソル位置が括弧だった場合は、括弧の中身も選択します。
  • ドットを検出した場合は、ドットの1つ右の単語も選択します。
  • ハイフンを検出した場合は、ハイフンで区切られた1つ右の単語も選択します。

選択状態での動作[編集]

  • 選択範囲がある場合、選択範囲の前後にある括弧を検出し、その中身を選択します。
  • ドットを検出した場合は、ドットの1つ左または右の単語まで拡張します。
  • ハイフンを検出した場合は、ハイフンで区切られた1つ右の単語まで拡張します。
  • これらの記号を検出しなかった場合は、単純な選択範囲の拡張を行います。

ソースコード[編集]

#title = "選択範囲を広げる"
/**
 * 選択範囲を広げるためのマクロです。
 * 
 * 選択範囲が空の場合、カーソル位置の単語を選択します。
 * カーソル位置が括弧だった場合は、括弧の中身も選択します。
 * ドットを検出した場合は、ドットの1つ右の単語も選択します。
 * ハイフンを検出した場合は、ハイフンで区切られた1つ右の単語も選択します。
 * 
 * 選択範囲がある場合、選択範囲の前後にある括弧を検出し、その中身を選択します。
 * ドットを検出した場合は、ドットの1つ左または右の単語まで拡張します。
 * ハイフンを検出した場合は、ハイフンで区切られた1つ右の単語まで拡張します。
 * これらの記号を検出しなかった場合は、単純な選択範囲の拡張を行います。
 */

BeginUndoGroup();
Redraw = false;

//■括弧の定義(0+2n:開き/1+2n:閉じ)
var BRACKET = '()<>[]{}「」『』【】()[]{}〈〉《》〔〕〘〙〚〛〖〗‘’“”';

var doc = document;
var sel = document.selection;
var txt = document.Text;

function main() {

    if (sel.IsEmpty) {
        // 未選択の場合
        var beforePos = sel.GetActivePos();
        sel.SelectWord();

        var selChar = sel.Text;
        var selCharIndex = BRACKET.indexOf(selChar);
        if (selChar.length === 1 && selCharIndex !== -1) {
            // 選択文字が括弧記号1文字の場合、括弧の中身を選択

            if (selCharIndex % 2 === 1) {
                // 閉じ括弧の場合
                sel.CharLeft();
                var topX = sel.GetTopPointX(mePosLogical);
                var topY = sel.GetTopPointY(mePosLogical);
                var bottomX = sel.GetBottomPointX(mePosLogical);
                var bottomY = sel.GetBottomPointY(mePosLogical);
                selectToBracketStart(topX, topY, bottomX, bottomY, selChar);
            } else if (selCharIndex % 2 === 0) {
                // 開き括弧の場合
                var topX = sel.GetTopPointX(mePosLogical);
                var topY = sel.GetTopPointY(mePosLogical);
                var bottomX = sel.GetBottomPointX(mePosLogical);
                var bottomY = sel.GetBottomPointY(mePosLogical);
                var activePos = sel.GetActivePos();
                selectToBracketEnd(topX, topY, bottomX, bottomY, selChar);
                if (beforePos === activePos) {
                    // 行末で先頭方向に向かって選択していた場合は、先頭の開き括弧の選択を外す
                    sel.SetAnchorPos(beforePos);
                } else if (activePos !== sel.GetActivePos()) {
                    // 括弧の中身が選択された場合、閉じ括弧も選択する
                    sel.SetActivePos(sel.GetActivePos() + 1, true);
                }
            }
        } else if (selChar === ".") {
            // ドットの場合
            // ドットの1つ右の単語まで拡張
            selectFuncNameToChild(sel.GetActivePos() - 1);
        } else if (selChar === "-") {
            // ドットの場合
            // ドットの1つ右の単語まで拡張
            selectHyphenSeparatedWord(sel.GetActivePos() - 1);
        }
    } else {
        // 選択済みの場合

        // 現在の選択範囲を取得
        var topX = sel.GetTopPointX(mePosLogical);
        var topY = sel.GetTopPointY(mePosLogical);
        var bottomX = sel.GetBottomPointX(mePosLogical);
        var bottomY = sel.GetBottomPointY(mePosLogical);

        // 選択範囲の左右の文字を取得
        var leftChar = "dummy";
        if (topX - 2 >= 0) leftChar = doc.GetLine(topY, meGetLineWithNewLines).charAt(topX - 2);
        var rightChar = doc.GetLine(bottomY, meGetLineWithNewLines).charAt(bottomX - 1);
        var rightIndex = BRACKET.indexOf(rightChar);
        var leftIndex = BRACKET.indexOf(leftChar);

        if (rightChar === ".") {
            // 選択範囲の右にドットがある場合
            // 末尾方向にドットを検索
            sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
            selectFuncNameToChild(sel.GetActivePos());
            sel.SetAnchorPoint(mePosLogical, topX, topY);
        } else if (rightChar === "-") {
            // 選択範囲の右にハイフンがある場合
            sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
            selectHyphenSeparatedWord(sel.GetActivePos());
            sel.SetAnchorPoint(mePosLogical, topX, topY);
        } else if (rightIndex % 2 === 0) {
            // 右位置に開き括弧があるかをチェック
            // 選択範囲を広げる
            selectToBracketEnd(topX, topY, bottomX + 1, bottomY, rightChar);
            // 閉じ括弧も選択する
            sel.CharRight(true);
        } else if (leftChar === ".") {
            // 選択範囲の左にドットがある場合
            // 先頭方向にドットを検索
            sel.SetActivePoint(mePosLogical, topX, topY, false);
            selectFuncNameToParent(sel.GetActivePos() - 2);
            sel.SetAnchorPoint(mePosLogical, bottomX, bottomY);
        } else if (rightIndex % 2 === 1) {
            // 右位置に閉じ括弧があるかをチェック
            // 選択範囲を広げる
            selectToBracketStart(topX, topY, bottomX, bottomY, rightChar);
        } else if (leftIndex % 2 === 0) {
            // 左位置に括弧があるかをチェック
            // 選択範囲を広げる
            selectToBracketEnd(topX, topY, bottomX, bottomY, leftChar);
        } else {
            // いずれでもない場合は、選択開始位置と終了位置の両方で単語選択を行う
            simpleExpand(topX, topY, bottomX, bottomY, 0);
        }

        var afterTopX = sel.GetTopPointX(mePosLogical);
        var afterTopY = sel.GetTopPointY(mePosLogical);
        var afterBottomX = sel.GetBottomPointX(mePosLogical);
        var afterBottomY = sel.GetBottomPointY(mePosLogical);

        // 選択範囲を広げる処理の後も同じ位置の場合、通常の選択範囲を広げる処理を行う
        if (topX === afterTopX
            && topY === afterTopY
            && bottomX === afterBottomX
            && bottomY === afterBottomY) {
            simpleExpand(afterTopX, afterTopY, afterBottomX, afterBottomY, 1);
        }
    }
}

function selectFuncNameToChild(pos) {
    // "$" はEOFへの対応
    var movePos = txt.slice(pos + 1).search(/[^a-z0-9_-]|$/i) + pos + 1;
    sel.SetActivePos(movePos, true);
}

function selectFuncNameToParent(pos) {
    var toSeparator1 = txt.lastIndexOf(".", pos);
    var toSeparator2 = txt.lastIndexOf("\n", pos);
    var toSeparator3 = txt.lastIndexOf(" ", pos);
    var toSeparator4 = txt.lastIndexOf("\t", pos);
    var toSeparator5 = txt.lastIndexOf(" ", pos);
    var maxIndex = Math.max(toSeparator1, toSeparator2, toSeparator3, toSeparator4, toSeparator5);
    sel.SetActivePos(maxIndex + 1, true);
}

function selectHyphenSeparatedWord(pos) {
    // "$" はEOFへの対応
    var movePos = txt.slice(pos + 1).search(/[^a-z0-9_]|$/i) + pos + 1;
    sel.SetActivePos(movePos, true);
}

// 単純な選択範囲の拡大
function simpleExpand(topX, topY, bottomX, bottomY, offset) {
    // 拡大範囲は行を跨がないようにする

    // 左側の拡大後の位置を取得
    var simpleExpandTopX = sel.GetTopPointX(mePosLogical);
    var simpleExpandTopY = sel.GetTopPointY(mePosLogical);
    sel.SetActivePoint(mePosLogical, topX, topY, false);
    var leftChar = txt.charAt(sel.GetActivePos() - 1);
    if (leftChar !== "\n" && leftChar !== "") {
        // 行頭以外のときだけ、左側を拡大
        sel.CharLeft(false, 1 + offset);
        sel.SelectWord();
        simpleExpandTopX = sel.GetTopPointX(mePosLogical);
        simpleExpandTopY = sel.GetTopPointY(mePosLogical);
    }

    // 右側の拡大後の位置を取得
    sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
    var simpleExpandBottomX = sel.GetBottomPointX(mePosLogical);
    var simpleExpandBottomY = sel.GetBottomPointY(mePosLogical);
    var rightChar = txt.charAt(sel.GetActivePos());
    if (rightChar !== "\n" && rightChar !== "") {
        // 行末以外のときだけ、右側を拡大
        if (offset > 0) {
            sel.CharRight(false, offset);
        }
        sel.SelectWord();
        simpleExpandBottomX = sel.GetBottomPointX(mePosLogical);
        simpleExpandBottomY = sel.GetBottomPointY(mePosLogical);
    }

    // 選択範囲を広げる
    sel.SetActivePoint(mePosLogical, simpleExpandTopX, simpleExpandTopY, false);
    sel.SetActivePoint(mePosLogical, simpleExpandBottomX, simpleExpandBottomY, true);
}

// 先頭方向の開き括弧まで選択範囲を広げる
function selectToBracketStart(topX, topY, bottomX, bottomY, endChar) {
    var endBracketIndex = BRACKET.indexOf(endChar);

    sel.SetActivePoint(mePosLogical, topX, topY, false);
    var startPos = sel.GetActivePos();

    sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
    var endPos = sel.GetActivePos();

    var nest = 1;
    var s;
    var e;
    var pairChar;

    pairChar = BRACKET.charAt(endBracketIndex - 1); //対応する開き括弧を取得
    e = txt.lastIndexOf(endChar, endPos);

    while (nest) {
        s = txt.lastIndexOf(pairChar, e - 1);
        e = txt.lastIndexOf(endChar, e - 1);
        if (s === -1 || startPos <= 0) break;
        if (s < e && s !== -1) {
            nest++;
            s = e;
        } else {
            nest--;
            e = s;
        }
        if (s !== -1 && startPos > s) {
            startPos = s + 1;
        }
    }

    sel.SetActivePos(startPos);
    sel.SetActivePos(endPos, true);
}

// 末尾方向の閉じ括弧まで選択範囲を広げる
function selectToBracketEnd(topX, topY, bottomX, bottomY, startChar) {
    var startBracketIndex = BRACKET.indexOf(startChar);

    sel.SetActivePoint(mePosLogical, topX, topY, false);
    var startPos = sel.GetActivePos();

    sel.SetActivePoint(mePosLogical, bottomX, bottomY, false);
    var endPos = sel.GetActivePos();

    var nest = 1;
    var s;
    var e;
    var pairChar;

    pairChar = BRACKET.charAt(startBracketIndex + 1); //対応する閉じ括弧を取得
    s = txt.indexOf(startChar, startPos - 1);
    while (nest) {
        e = txt.indexOf(pairChar, s + 1);
        s = txt.indexOf(startChar, s + 1);
        if (e === -1) break;
        if (s < e && s !== -1) {
            nest++;
            e = s;
        } else {
            nest--;
            s = e;
        }
        if (e !== -1 && endPos < e) {
            endPos = e;
        }
    }
    sel.SetActivePos(startPos);
    sel.SetActivePos(endPos, true);
}

function doMultiAction(fn) {
    var d = document, s = d.selection;
    s.Mode = meModeMulti;
    // ① まず、GetActivePos と GetAnchorPos で複数選択のリストを作成します
    var selections = [{ s: s.GetAnchorPos(), e: s.GetActivePos() }];
    if (s.Count > 0) {
        selections = [];
        for (var i = 0; i < s.Count; i++)
            selections.push({ s: s.GetActivePos(i), e: s.GetAnchorPos(i) });
    }
    var p = 0;
    var q = 0;
    for (var i = 0; i < selections.length; i++) {
        // ② そのリストを上から順に SetActivePos で、「シングルカーソル」で範囲選択します。この段階で複数選択は解除され、通常のマクロ操作が可能となります
        s.SetActivePos(selections[i].s + p);
        s.SetActivePos(selections[i].e + p, true);
        q = d.TextLength;
        // ③ <ここで通常のマクロの機能を使って普通に編集などを行います>
        fn();
        p += d.TextLength - q;
        // ④ 処理を行った後の選択範囲をリストに反映し、これをリストの最後まで繰り返します
        selections[i] = { s: s.GetAnchorPos(), e: s.GetActivePos() };
    }
    // ⑤ そのリストを上から順に document.selection.AddPos(StartPos, EndPos) で複数選択として復元します
    for (var i = 0; i < selections.length; i++)
        s.AddPos(selections[i].s, selections[i].e);
}

if (sel.Mode === meModeMulti) {
    doMultiAction(main);
} else {
    main();
}

変更履歴[編集]

  • 1.0.3 (2023-07-21)
    • 処理を見直し、より適切な範囲に拡張するよう調整しました
  • 1.0.2 (2020-03-22)
    • Mery Ver.3.0.1 マルチカーソル対応化の試験実装メソッドを使った実装を追加しました
  • 1.0.1 (2020-03-15)
    • マルチカーソル時の動作を考慮し処理を追加しました
  • 1.0.0 (2020-02-24)
    • 初版
スポンサーリンク