マルチカーソル用:「昇順/降順」または「逆順」で並べ替え

提供: MeryWiki
ナビゲーションに移動 検索に移動

昇順/降順 トグル」マクロの マルチカーソル/複数選択範囲 (Mery ver 3.0.1 以降) 対応版です (+逆順で並べ替え機能を追加)。

  • 連続で実行すると  昇順 → 降順 → 昇順 → 降順 → ...  でトグルします。
  • もとの並び順に戻す場合は Undo(Ctrl+Z)してください


あらかじめ、設定項目 multiSelMode で複数選択範囲の並べ替えのパターンを指定してください。
※ 設定項目 var reverseMode = true; にすると「昇順/降順」ではなく「逆順で並べ替え」になります。

  • var multiSelMode = 0;
それぞれの選択範囲内の行を並べ替えます(昇順/降順 または 逆順)。
「昇順/降順」モード (var reverseMode = false;) の場合、それぞれの選択範囲内の行の並び順にたいして個別に「昇順」または「降順」を適用するので、すべての選択範囲にたいして選択的・統一的に「昇順」または「降順」を適用することはできません。
基本的に複数行でない選択範囲は並べ替えの対象になりません(単語だけを複数選択しても並べ替えませんが、選択範囲が2つ でいずれも改行を含んでいない場合は「選択範囲の文字列を入れ替え」をします ← 2020/07/08 の変更)

Mery 昇順/降順マクロ(0) 20200704.png


  • var multiSelMode = 1;
すべての選択範囲を行に分割してから一括で並べ替えます(昇順/降順 または 逆順)
単語だけを複数選択した場合も並べ替えをおこないます。

Mery 昇順/降順マクロ(1) 20200704.png


c.f. 各選択範囲内のテキストは並べ替えず、選択範囲の配置だけを並べ替えたい場合は

⇒ 「マルチカーソル選択範囲の並べ替え」マクロ
単語だけを複数選択した場合も並べ替えをおこないます。

Mery 選択範囲の並べ替え 20200704.png

※ 行番号(論理行)をひとつずつ Ctrl+クリック して複数選択状態にしてから実行した場合、「マルチカーソル選択範囲の並べ替え」マクロの「昇順/降順」モード multiSelMode = 0 で並べ替えた結果と「マルチカーソル用:『昇順/降順』または『逆順』で並び替え」マクロの「昇順/降順」モード multiSelMode = 1 で並べ替えた結果はおなじになります。

ダウンロード[編集]

ファイル:昇順/降順 または 逆順 で並べ替え(マルチカーソル).zip」(アイコン入り)

※ 外部実行ファイル「GetKeyState.exe」を導入している場合、設定項目 gksEnable の値を変更すると Ctrl キーの押し下げ判定により動作モードの切り替えができます。


ソースコード[編集]

#title = "昇順/降順 トグル"
#tooltip = "昇順で並べ替え/降順で並べ替え"
#icon = "sort_by_alpha_a_to_z.ico"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",145

// #title = "逆順で並べ替え トグル"
// #tooltip = "逆順で並べ替え"
// #icon = "arrows_swap_vertical[7].ico"

/**
 * --------------------------------------------------------
 * 「昇順/降順 または 逆順 で並べ替え」
 * マルチカーソル対応版
 * sukemaru, 2020/07/04 - 2020/07/08
 * --------------------------------------------------------
 * 論理行単位で「昇順/降順」または「逆順」で並べ替え
 * 
 * ※ reverseMode = true で「逆順で並べ替え (並び順を反転)」
 * 
 * 複数選択範囲から実行した場合は、
 * ・それぞれの選択範囲内を並べ替え               (multiSelMode = 0)
 * ・すべての選択範囲を行に分割して一括で並べ替え (multiSelMode = 1)
 * ※ いずれの場合も、選択範囲が2つでいずれも改行を含んでいないなら「入れ替え」
 * 
 * 【仕様上の制限】
 * ・動作要件:Mery ver 3.0.1 以降
 * ・基本的に、選択範囲末尾が改行記号(末尾位置が論理行の行頭)のときは
 *   末尾改行より前の部分だけを並べ替えします。
 * ・元々の並び順が昇順または降順でなかった場合
 *   連続実行で「昇順/降順トグル」しても元の並び順にはなりません。
 *   ⇒ 元々の並び順に戻す場合は「元に戻す (Ctrl+Z)」を連打
 * ・矩形選択範囲は行に分けてから並べ替えます。 ⇒ 実行後は複数選択状態
 * (※ 元の矩形選択のかたちが歪だった場合に、矩形で復帰できないため)
 * ・複数行選択状態から実行した場合、並べ替え後(選択範囲復帰後)の
 *   各キャレット位置は各選択範囲の先頭になります。
 * (※ 行単位で並べ替えたときに、キャレットが末尾だと分かりづらいため)
 * ・ブックマークを考慮しません。
 * ・並べ替え処理中に選択範囲(ゼロ幅も含めて)の数が変わってしまったり
 *   文書全体の文字数が変わってしまう場合は、並べ替えの処理ををキャンセルします。
 * (※ 選択範囲が結合されてしまう場合もキャンセル
 *   ※ 失敗した試行の結果は「やり直し (Ctrl+Y)」×2回すると確認できます)
 */


/* ---------- ▼ 設定項目 ▼ ---------- */

// ■ 複数選択範囲での動作モード
var multiSelMode = 0;
    /**
     * multiSelMode = 0: 各選択範囲ごとに「行並べ替え」
     * multiSelMode = 1: すべての選択範囲を行に分割して「行並べ替え」
     * 
     * ※ 0 のばあい、それぞれの選択範囲ごとに
     *    各選択範囲内を「昇順/降順」で並べ替える(選択範囲が1行内なら並べ替えなし)
     *    すべての選択範囲にたいして選択的・統一的に「昇順」または「降順」を
     *    適用することはできない
     * ※ 1 のばあい、各選択範囲から末尾改行を除去してから行に分割して
     *    すべての選択範囲を「昇順/降順」または「逆順」で並べ替える
     * ※ いずれの場合も、各選択範囲の「論理行全体」への自動拡張はしない
     * ※ いずれの場合も、選択範囲が2つでいずれも改行を含んでいないなら「入れ替え」
     */

// ■「昇順/降順」ではなく「逆順で並べ替え (並び順を反転)」する
var reverseMode = false;
  /**
   * true:  逆順で並べ替え (並び順を反転)
   * false: 昇順/降順 で並べ替え
   */

// ■ 半角数字を数値で評価して「昇順/降順」で並べ替える
// (各選択範囲内の最初の半角数字部分を数値として扱う ...たぶん小数も可)
var sortByNum = false;
  /**
   * true: 数値順
   * 	1  <  002  <  3  <  10  <  020
   * 	-2.5  <  -2  <  -1  <  0  <  1  <  1.2  <  1.2.30  <  1.2.4
   * 
   * false: 文字順 @ unicode
   * 	002  <  020  <  1  <  10  <  3
   * 	-1  <  -2  <  -2.5  <  0  <  1  <  1.2  <  1.2.30  <  1.2.4
   * 
   * ※ true の場合でも「1.2.30」や「1.2.4」などは
   *    「1.2」までを数値(小数)として評価し
   *    それ以降の「.30」や「.4」は文字列として評価するので
   *    ビルド番号や IP アドレスなどは数値順にならない
   *    また「ほげ 1 ふが 2 ぴよ 3」のように2ヵ所以上数字が出現する場合
   *    2つめ以降の数字はすべて文字列として評価する
   */

/* ------------------------------ */

// ● Ctrl キーで動作モードを切り替え(要:GetKeyState.exe)
var gksEnable = 0;
    /**
     * 0 なら、動作モードの切り替えなし
     * 1 なら、multiSelMode を切り替え
     * 2 なら、reverseMode を切り替え
     * 3 なら、sortByNum を切り替え
     */

// ■ GetKeyState.exe のフルパスを指定する場合
var gksPath = "";
    /**
     * 未指定 "" なら、Mery インストールフォルダの Macros\GetKeyState.exe
     * ※ GetKeyState.exe なしのときも "" にすること。
     * ※ パス指定での区切り文字の \ 記号はふたつがさね「\\」で記述すること。
     */

/* ---------- ▲ 設定項目 ▲ ---------- */

( function Main() {
  /* ▼ 前処理 ▼ */
  var d = editor.ActiveDocument,  s = d.selection;
  var ty = s.GetTopPointY( mePosLogical );
  var by = s.GetBottomPointY( mePosLogical );
  var sMode  = s.Mode || 1;	// マルチカーソル/複数選択に対応

  if ( d.ReadOnly ) {
    Status = " このドキュメントは書き換え禁止です。";
    return;
  }
  if ( s.IsEmpty || ! /[^\n]/.test( s.Text ) ) {
    Status = " 選択範囲がありません。";
    return;
  }
  else if ( ( sMode == 2 && ty === by ) 
  || ( sMode == 1 && ty === by )
  ) {
    Status = " 並べ替えられる選択範囲がありません。";
    return;
  }

  /* GetKeyState.exe による修飾キー判定 */
  var gks = gksPath
  || editor.FullName.replace( /[^\\]+$/, "Macros\\GetKeyState.exe" );
  gksEnable = new ActiveXObject( "Scripting.FileSystemObject" ).FileExists( gks )
            ? gksEnable : 0;
  // ● Ctrl キーで動作モードを切り替え
  if ( ( ( gksEnable === 1 && sMode == 3 ) || gksEnable === 2 || gksEnable === 3 )
  && new ActiveXObject( "WScript.Shell" ).Run( '"' + gks + '" ctrl', 0, true ) === 1 ) {
    if      ( gksEnable === 1 ) { multiSelMode = ! multiSelMode; }
    else if ( gksEnable === 2 ) { reverseMode = ! reverseMode; }
    else if ( gksEnable === 3 ) { sortByNum = ! sortByNum; }
  }

  /* ▼ 本処理 ▼ */
  var start = new Date();	// 所要時間計測(開始)
  editor.ExecuteCommandByID( MEID_WINDOW_ACTIVE_PANE = 2189 );	// アクティブなペイン
  var sx = ScrollX,  sy = ScrollY;

  /* マルチカーソル/複数選択に対応 */
  var arg = [ sortByNum, reverseMode ];

  // ● 選択範囲が1つで矩形選択ではないとき
  if ( sMode == 1 ) {
    SortToggle_Main( arg );
  }

  // ● 矩形選択 ⇒ 複数選択で返す
  else if ( sMode == 2 ) {
    SortByBlock( sortByNum, reverseMode, false );
  }

  // 選択範囲が2つでいずれも改行を含んでいないなら「入れ替え」
  else if ( ! multiSelMode && s.Count === 2
  && s.Count === ( s.Text.match( /\n/g ) || [] ).length
  && s.GetActivePos( 0 ) !== s.GetAnchorPos( 0 )
  && s.GetActivePos( 1 ) !== s.GetAnchorPos( 1 ) ) {
    SortByBlock( sortByNum, true, false );
  }

  // ● 複数選択 ×「すべての選択範囲を行に分割して並べ替え」のとき
  else if ( sMode == 3 && multiSelMode ) {
    SortByBlock( sortByNum, reverseMode, false, true );
  }

  // ● 複数選択 ×「各選択範囲ごとに行並べ替え」のとき
  else {
    var dt = d.Text;
    BeginUndoGroup();  AddUndo();
    MultiFunction( SortToggle_Main, arg );
    Status = ( reverseMode ) ? Status : " 昇順/降順 で並べ替え";
    Status += "(複数選択範囲)";
    EndUndoGroup();

    /* ▼ 後処理 ▼ */
    if ( d.Text === dt ) {
      d.Undo();
      Status = " 並べ替えなし";
    }
  }

  ScrollX = sx;  ScrollY = sy;
  var elapsedSec = ( ( new Date() - start ) / 1000 ).toFixed( 3 );
  Status += " [ " + elapsedSec.replace( /\./, ". " ) + " 秒 ]";
} )();	// Main

/**
 * 関数 SortToggle_Main( [ sortByNum, reverseMode ] )
 * 「昇順で並べ替え/降順で並べ替え」+「逆順で並べ替え」マクロ
 */
function SortToggle_Main( arg ) {
  var sortByNum   = arg[0];
  var reverseMode = arg[1];

  var d = editor.ActiveDocument,  s = d.selection;
  var st = s.Text;
  var reg1 = /\n?$/,  n = st.match( reg1 );
  st = st.replace( reg1, "" );
  if ( st.indexOf( "\n" ) === -1 ) { return; }
  st = st.split( "\n" );
  var tmp = st.toString();
  var $status = "";

  // 逆順で並べ替え
  if ( reverseMode ) {
    $status = " 逆順で並べ替え";
    st.reverse();
  }

  else {
    // 昇順で並べ替え
    $status = " 昇順で並べ替え";
    st.sort( Sort );

    // すでに昇順だった場合は降順で並べ替え(反転)
    if ( st.toString() === tmp ) {
      $status = " 降順で並べ替え";
      st.reverse();
    }
  }

  // 並べ替えを適用
  if ( st.toString() !== tmp ) {
    var act = s.GetActivePos();
    var anc = s.GetAnchorPos();
    s.Text = st.join( "\n" ) + n;
    // 選択範囲を復帰
    s.SetActivePos( anc );
    s.SetActivePos( act, true );
    Status = $status;
  }

  // 並べ替え なし
  else {
    Status = " 並べ替えなし";
  }
}

/**
 * 関数 Sort( a, b )
 * 昇順で並べ替え
 * 半角数字部分は「数値」または「文字列」で評価
 * ※ sortByNum はグローバルスコープの設定変数
 */
function Sort( a, b ) {
  var reg = /^(.*?)(-?\d+(?:\.\d+)?)/;
  var aa = reg.exec( a ),  a1 = aa ? aa[1] : "";
  var bb = reg.exec( b ),  b1 = bb ? bb[1] : "";
  var a2 = a,  b2 = b;
  if ( sortByNum && aa && bb && a1 === b1 ) {
    a2 = Number( aa[2] );
    b2 = Number( bb[2] );
  }
  return ( a2 !== a && a2 !== b2 ) ? a2 - b2
       : ( a < b ) ? -1
       : ( a > b ) ?  1
       :/* else */    0;
}

/**
 * 関数 PseudoChomp()
 * 選択範囲から末尾改行を除去する
 * ※ 選択範囲の先頭位置は変更しない
 */ 
function PseudoChomp() { 
  var d = editor.ActiveDocument;
  var s = d.selection;
  if ( s.Text.slice( -1 ) === "\n" ) {
    var ty = s.GetTopPointY( mePosLogical );
    var tx = s.GetTopPointX( mePosLogical );
    var by = s.GetBottomPointY( mePosLogical );
    by --;
    s.SetActivePoint( mePosLogical, tx, ty );
    s.SetAnchorPoint( mePosLogical, d.GetLine( by, 0 ).length + 1, by );
  }
}

/**
 * 関数 MultiFunction( Fn, arg )
 * マルチカーソル(複数選択範囲)に対応させる
 * 第1引数: Function; 選択範囲ごとに適用する処理の関数
 * 第2引数: Function に渡す引数をまとめた配列
 * 
 * ※ 並べ替えマクロ用カスタマイズ: 
 *    ⇒ キャレットは各選択範囲の先頭
 */
function MultiFunction( Fn, arg ) {
  var d = editor.ActiveDocument;
  var s = d.selection;

  // 矩形選択範囲は行に分ける
  s.Mode = 3;

  // 選択範囲の座標を取得
  var sCount = s.Count;
  var Sel = [];
  for ( var i = 0; i < sCount; i ++ ) {
    act = s.GetActivePos( i );
    anc = s.GetAnchorPos( i );
    Sel[i] = {
      tp: Math.min( act, anc ),
      bp: Math.max( act, anc )
    };
  }

  // 各選択範囲を処理
  for ( var i = 0, diff = 0, dl = d.TextLength;
  i < sCount; i ++ ) {
    // dl = d.TextLength;
    s.SetAnchorPos( Sel[i].bp + diff );
    s.SetActivePos( Sel[i].tp + diff, true );

    Fn( arg );	// SortToggle_Main() 関数で並べ替え

    // Fn() の残した選択範囲(またはキャレット位置)を回収
    act = s.GetActivePos();
    anc = s.GetAnchorPos();
    Sel[i] = {
      tp: Math.min( act, anc ),
      bp: Math.max( act, anc )
    };
    // diff += d.TextLength - dl;	// 文字数の増減量(累積)
    diff += ( - dl ) + ( dl = d.TextLength );
  }

  // マルチカーソル(複数選択範囲)を復帰
  s.SetAnchorPos( Sel[ sCount - 1 ].bp );
  s.SetActivePos( Sel[ sCount - 1 ].tp, true );
  for ( var i = 0; i < sCount; i ++ ) {
    // キャレットは各選択範囲の先頭
    s.AddPos( Sel[i].bp, Sel[i].tp );
  }
}

/**
 * 関数 SortByBlock()
 * マルチカーソル複数選択範囲の並べ替え
 * ※ マルチカーソル選択範囲を「昇順/降順」または「逆順」で並べ替え
 */
function SortByBlock( sortByNum, reverseMode, chompEnable, chompe0 ) {
  var d = editor.ActiveDocument;
  var s = d.selection;
  var ty = s.GetTopPointY( mePosLogical );
  var by = s.GetBottomPointY( mePosLogical );
  var sMode = s.Mode || 1;

  BeginUndoGroup();  AddUndo();

  /* 「昇順/降順」マクロ用: 「すべての選択範囲を行に分割して並べ替え」 */
  // 末尾改行の調整
  if ( chompe0 ) {
    MultiFunction( PseudoChomp );
    editor.ExecuteCommandByID( 2254 );	// 「選択範囲を行に分ける」
  }

  // 矩形選択を「行に分ける」(論理行)
  s.Mode = meModeMulti;

  // 各選択範囲の先頭/末尾の座標と文字列を取得
  var dt = d.Text;
  var sCount = s.Count;
  var Sel = [],  Str = [];
  var act, anc, tp, bp;
  for ( var i = 0; i < sCount; i ++ ) {
    act = s.GetActivePos( i );
    anc = s.GetAnchorPos( i );
    tp  = Math.min( act, anc );
    bp  = Math.max( act, anc );
    Sel[i] = { tp: tp,  bp: bp };
    Str[i] = dt.slice( tp, bp );
  }
  var tmp = Str.toString();

  // 逆順で並べ替え
  if ( sCount === 2 || reverseMode ) {
    $status = " 逆順で並べ替え";
    Str.reverse();
  }

  else {
    // 昇順で並べ替え
    $status = " 昇順で並べ替え";
    Str.sort( Sort );

    // すでに昇順だった場合は降順で並べ替え(反転)
    if ( Str.toString() === tmp ) {
      $status = " 降順で並べ替え";
      Str.reverse();
    }
  }

  // 変更なしなら UNDO 履歴を残さない
  if ( Str.toString() === tmp ) {
    EndUndoGroup();  d.Undo();
    Status = " 並べ替えなし";
    return;
  }

  // 各選択範囲を処理
  for ( var i = 0, diff = 0, dl = d.TextLength;
  i < sCount; i ++ ) {
    // dl = d.TextLength;
    s.SetActivePos( Sel[i].bp + diff );
    s.SetAnchorPos( Sel[i].tp + diff );

    // 並べ替えを適用
    s.Text = Str[i];

    // 復帰用選択範囲の先頭/末尾
    Sel[i].bp = s.GetActivePos();
    Sel[i].tp = Sel[i].bp - Str[i].length;
    // 文字数の増減量(累積)
    // diff += d.TextLength - dl;
    diff += ( - dl ) + ( dl = d.TextLength );
  }

  // マルチカーソル/複数選択範囲を復帰
  s.SetActivePos( Sel[ sCount - 1 ].tp, true );
  for ( var i = 0; i < sCount; i ++ ) {
    // キャレットは各選択範囲の先頭
    s.AddPos( Sel[i].bp, Sel[i].tp );
  }
  EndUndoGroup();

  // 選択範囲の数が変わってしまう場合(マージ)や
  // 文書全体の文字数が変わってしまう場合は失敗 
  if ( sMode == meModeMulti && s.Count !== sCount
  || diff ) {
    AddUndo();  d.Undo();  d.Undo();
    Status = " 並べ替え失敗";
  }
  else {
    Status = ( sCount === 2 ) ? " 選択範囲を入れ替え"
                              : $status;
  }
}
スポンサーリンク