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

提供:MeryWiki
ナビゲーションに移動 検索に移動
(<source>タグを<syntaxhighlight>タグに置き換える)
21行目: 21行目:
=== ソースコード: ===
=== ソースコード: ===


<source lang="javascript">
<syntaxhighlight lang="javascript">
#title="選択範囲を広げる"
#title="選択範囲を広げる"
BeginUndoGroup();
BeginUndoGroup();
254行目: 254行目:
}
}


</source>
</syntaxhighlight>


=== Mery Ver.3.0.1 以降に対応するマルチカーソル対応実装 (試験実装メソッドを利用): ===
=== Mery Ver.3.0.1 以降に対応するマルチカーソル対応実装 (試験実装メソッドを利用): ===


<source lang="javascript">
<syntaxhighlight lang="javascript">
#title="選択範囲を広げる"
#title="選択範囲を広げる"
BeginUndoGroup();
BeginUndoGroup();
523行目: 523行目:
}
}


</source>
</syntaxhighlight>


== 更新履歴 ==
== 更新履歴 ==

2023年5月16日 (火) 23:09時点における版

選択範囲を広げる

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

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

未選択状態での動作:

  • カーソルの左、もしくは右に括弧記号がある場合は括弧の中全体を選択
  • それ以外はカーソル位置の単語を選択

選択状態での動作:

  • 選択状態によって選択範囲を広げる
    • 選択範囲の開始位置に括弧の開始記号がある場合は、括弧全体に選択範囲を広げる
    • 上記の条件に当てはまらず、選択範囲末尾の右側に括弧の終了記号がある場合は、括弧全体に選択範囲を広げる
    • 上記の条件に当てはまらず、選択範囲末尾の右側にドットがある場合には、そのドット連なりの一番右側の単語まで選択範囲を広げる
    • 上記の条件に当てはまらず、選択範囲末尾の右側に括弧の開始記号がある場合には、その括弧を全て選択する
    • 上記の動作によって選択範囲が広がらなかった場合には、選択範囲を左右それぞれ1単語ずつ広げる

ソースコード:

#title="選択範囲を広げる"
BeginUndoGroup();
Redraw = false;

//■括弧の定義(0+2n:開き/1+2n:閉じ)
var BRACKET = '()<>[]{}「」『』【】()';

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

if (sel.Mode === meModeMulti) {
  // マルチカーソル動作中の場合は SelectWord しか行わない
  sel.SelectWord();
} else if (sel.IsEmpty) {
  var selectStartPos = selectBottomPos = sel.GetActivePos();
  if (leftCharIsStartBracket(selectStartPos)
      || rightCharIsEndBracket(selectBottomPos)) {
    // カーソルの左右いずれかに開き括弧があった場合
    moveToBracketEnd(selectStartPos, selectBottomPos);
  } else {
    // 単語を選択
    sel.SelectWord();
  }
} else {
  expandSelection();
}

function expandSelection() {
  var EXTEND_ON = true;
  var EXTEND_OFF = false;

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

  // 選択終了位置のポジション取得
  sel.SetActivePoint(mePosLogical, bottomX, bottomY, EXTEND_OFF);
  var selectBottomPos = sel.GetActivePos();

  // 選択開始位置に移動
  sel.SetActivePoint(mePosLogical, topX, topY, EXTEND_OFF);
  var selectTopPos = sel.GetActivePos();

  var selectStartPos, selectEndPos;
  if (selectTopPos <= selectBottomPos) {
    selectStartPos = selectTopPos;
    selectEndPos = selectBottomPos;
  } else {
    selectStartPos = selectBottomPos;
    selectEndPos = selectTopPos;
  }

  if (bothSideCharIsBracket(selectStartPos, selectEndPos)) {
    // 両脇が括弧だった場合
    sel.CharLeft(EXTEND_OFF);
    sel.SetActivePos(selectEndPos, EXTEND_ON);
    sel.CharRight(EXTEND_ON);
  } else if (txt.charAt(selectEndPos) === '.') {
    // 選択範囲末尾右側がドットだった場合
    selectRightSideWordsOfDot(selectStartPos, selectEndPos);
  } else if (leftCharIsStartBracket(selectEndPos + 1)) {
    // 選択範囲右側が開始括弧だった場合
    selectRightSideWordsOfBracket(selectStartPos, selectEndPos);
  } else if (
    leftCharIsStartBracket(selectStartPos) ||
    rightCharIsEndBracket(selectEndPos)
  ) {
    // 左右どちらかが括弧だった場合
    moveToBracketEnd(selectStartPos, selectEndPos);
  } else {
    // 選択範囲を拡大
    sel.WordLeft(EXTEND_OFF);
    sel.SetActivePos(selectEndPos, EXTEND_ON);
    sel.WordRight(EXTEND_ON);
  }

  // 選択範囲拡大後の選択範囲取得
  var expandedTopX = sel.GetTopPointX(mePosLogical);
  var expandedTopY = sel.GetTopPointY(mePosLogical);
  var expandedBottomX = sel.GetBottomPointX(mePosLogical);
  var expandedBottomY = sel.GetBottomPointY(mePosLogical);

  // 選択範囲が変わっていない場合は、左右1単語ずつ拡大する
  if (
    topX === expandedTopX &&
    topY === expandedTopY &&
    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) !== '.') {
    return;
  }

  var EXTEND_ON = true;

  sel.SetActivePos(selectStartPos);
  sel.SetActivePos(selectEndPos, EXTEND_ON);
  sel.CharRight(EXTEND_ON);
  sel.WordRight(EXTEND_ON);

  selectRightSideWordsOfDot(selectStartPos, sel.GetActivePos());
}

function selectRightSideWordsOfBracket(selectStartPos, selectEndPos) {
  if (!leftCharIsStartBracket(selectEndPos + 1)) {
    return;
  }

  moveToBracketEnd(selectEndPos + 1, selectEndPos + 1);
  var movedEndPos = sel.GetActivePos() + 1;

  var EXTEND_ON = true;

  sel.SetActivePos(selectStartPos);
  sel.SetActivePos(movedEndPos, EXTEND_ON);
}

function leftCharIsStartBracket(selectStartPos) {
  var startPos;
  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) {
  var endPos;
  if (selectEndPos) {
    endPos = selectEndPos;
  } else {
    endPos = Document.Selection.GetActivePos();
  }
  var endChar = txt.charAt(endPos); //カーソル右側の文字を取得
  var endBrcIdx = BRACKET.indexOf(endChar);
  return endBrcIdx !== -1 && endChar !== '' && endBrcIdx % 2 === 1;
}

function bothSideCharIsBracket(selectStartPos, selectEndPos) {
  var txt = Document.Text;
  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:しない)
  var SHIFT = true;

  quit: {
    var startPos = selectStartPos;
    var endPos = selectEndPos;

    var startChar = txt.charAt(startPos - 1); //選択範囲開始左側の文字を取得
    var startBrcIdx = BRACKET.indexOf(startChar);

    var endChar = txt.charAt(selectEndPos); //選択範囲末尾右側の文字を取得
    var endBrcIdx = BRACKET.indexOf(endChar);

    var nest = 1;
    var s;
    var e;
    var pairChar;
    // if (startBrcIdx !== -1 && startChar !== '' && startBrcIdx % 2 === 0) {
    if (leftCharIsStartBracket(selectStartPos)) {
      //◆開き括弧の場合、末尾方向へ探す
      pairChar = BRACKET.charAt(startBrcIdx + 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;
        }
      }
    // } 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(endPos, SHIFT);
  }
}

Mery Ver.3.0.1 以降に対応するマルチカーソル対応実装 (試験実装メソッドを利用):

#title="選択範囲を広げる"
BeginUndoGroup();
Redraw = false;

//■括弧の定義(0+2n:開き/1+2n:閉じ)
var BRACKET = '()<>[]{}「」『』【】()';

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

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


function main() {
  if (sel.IsEmpty) {
    var selectStartPos = selectBottomPos = sel.GetActivePos();
    if (leftCharIsStartBracket(selectStartPos)
        || rightCharIsEndBracket(selectBottomPos)) {
      // カーソルの左右いずれかに開き括弧があった場合
      moveToBracketEnd(selectStartPos, selectBottomPos);
    } else {
      // 単語を選択
      sel.SelectWord();
    }
  } else {
    expandSelection();
  }
}

function expandSelection() {
  var EXTEND_ON = true;
  var EXTEND_OFF = false;

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

  // 選択終了位置のポジション取得
  sel.SetActivePoint(mePosLogical, bottomX, bottomY, EXTEND_OFF);
  var selectBottomPos = sel.GetActivePos();

  // 選択開始位置に移動
  sel.SetActivePoint(mePosLogical, topX, topY, EXTEND_OFF);
  var selectTopPos = sel.GetActivePos();

  var selectStartPos, selectEndPos;
  if (selectTopPos <= selectBottomPos) {
    selectStartPos = selectTopPos;
    selectEndPos = selectBottomPos;
  } else {
    selectStartPos = selectBottomPos;
    selectEndPos = selectTopPos;
  }

  if (bothSideCharIsBracket(selectStartPos, selectEndPos)) {
    // 両脇が括弧だった場合
    sel.CharLeft(EXTEND_OFF);
    sel.SetActivePos(selectEndPos, EXTEND_ON);
    sel.CharRight(EXTEND_ON);
  } else if (txt.charAt(selectEndPos) === '.') {
    // 選択範囲末尾右側がドットだった場合
    selectRightSideWordsOfDot(selectStartPos, selectEndPos);
  } else if (leftCharIsStartBracket(selectEndPos + 1)) {
    // 選択範囲右側が開始括弧だった場合
    selectRightSideWordsOfBracket(selectStartPos, selectEndPos);
  } else if (
    leftCharIsStartBracket(selectStartPos) ||
    rightCharIsEndBracket(selectEndPos)
  ) {
    // 左右どちらかが括弧だった場合
    moveToBracketEnd(selectStartPos, selectEndPos);
  } else {
    // 選択範囲を拡大
    sel.WordLeft(EXTEND_OFF);
    sel.SetActivePos(selectEndPos, EXTEND_ON);
    sel.WordRight(EXTEND_ON);
  }

  // 選択範囲拡大後の選択範囲取得
  var expandedTopX = sel.GetTopPointX(mePosLogical);
  var expandedTopY = sel.GetTopPointY(mePosLogical);
  var expandedBottomX = sel.GetBottomPointX(mePosLogical);
  var expandedBottomY = sel.GetBottomPointY(mePosLogical);

  // 選択範囲が変わっていない場合は、左右1単語ずつ拡大する
  if (
    topX === expandedTopX &&
    topY === expandedTopY &&
    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) !== '.') {
    return;
  }

  var EXTEND_ON = true;

  sel.SetActivePos(selectStartPos);
  sel.SetActivePos(selectEndPos, EXTEND_ON);
  sel.CharRight(EXTEND_ON);
  sel.WordRight(EXTEND_ON);

  selectRightSideWordsOfDot(selectStartPos, sel.GetActivePos());
}

function selectRightSideWordsOfBracket(selectStartPos, selectEndPos) {
  if (!leftCharIsStartBracket(selectEndPos + 1)) {
    return;
  }

  moveToBracketEnd(selectEndPos + 1, selectEndPos + 1);
  var movedEndPos = sel.GetActivePos() + 1;

  var EXTEND_ON = true;

  sel.SetActivePos(selectStartPos);
  sel.SetActivePos(movedEndPos, EXTEND_ON);
}

function leftCharIsStartBracket(selectStartPos) {
  var startPos;
  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) {
  var endPos;
  if (selectEndPos) {
    endPos = selectEndPos;
  } else {
    endPos = Document.Selection.GetActivePos();
  }
  var endChar = txt.charAt(endPos); //カーソル右側の文字を取得
  var endBrcIdx = BRACKET.indexOf(endChar);
  return endBrcIdx !== -1 && endChar !== '' && endBrcIdx % 2 === 1;
}

function bothSideCharIsBracket(selectStartPos, selectEndPos) {
  var txt = Document.Text;
  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:しない)
  var SHIFT = true;

  quit: {
    var startPos = selectStartPos;
    var endPos = selectEndPos;

    var startChar = txt.charAt(startPos - 1); //選択範囲開始左側の文字を取得
    var startBrcIdx = BRACKET.indexOf(startChar);

    var endChar = txt.charAt(selectEndPos); //選択範囲末尾右側の文字を取得
    var endBrcIdx = BRACKET.indexOf(endChar);

    var nest = 1;
    var s;
    var e;
    var pairChar;
    if (leftCharIsStartBracket(selectStartPos)) {
      //◆開き括弧の場合、末尾方向へ探す
      pairChar = BRACKET.charAt(startBrcIdx + 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;
        }
      }
    } 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(endPos, SHIFT);
  }
}

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);
}

更新履歴

2020/03/15 マルチカーソル時の動作を考慮し処理を追加
2020/03/22 Mery Ver.3.0.1 マルチカーソル対応化の試験実装メソッドを使った実装を追加

スポンサーリンク