引用符/コメント

提供:MeryWiki
2020年6月23日 (火) 21:56時点におけるSukemaru (トーク | 投稿記録)による版 (マルチカーソルに対応。ZIP 書庫を更新)
ナビゲーションに移動 検索に移動


画像は 2019/04/07 版のものです。
2020/06/23 版では、サブメニューに削除系コマンドをまとめてあります。

引用符/コメント ポップアップメニュー.png


引用符/コメント

Kuro 氏の「引用の追加」マクロのコードを改変しました。

  • ポップアップメニューから任意の 引用マーク箇条書きの行頭記号行コメントの記号 などの種類を選択して、選択範囲をふくむの各行の先頭に追加します。
    選択範囲がないばあいはカーソル行の先頭に挿入します。
  • 行番号のドラッグでの複数行選択やトリプルクリックでの行選択などで末尾改行が含まれているばあい、さいごの改行を無視します。
  • 行頭に挿入する文字列として クリップボード の文字列データや 入力ダイアログ で指定した文字列を使用することもできます。
・「クリップボード」の機能は「行の先頭に貼り付け」マクロとおなじものです。

・「任意の文字列」は改行コードやタブ文字も記述できますが、それぞれ入力ダイアログで「\\\n」と「\\\t」で入力されたもの (注:¥記号3つ) を改行コードとタブ文字に置換するようにしてあります。
※「\n」や「\t」と記述したばあい、そのままの文字列として「\n」や「\t」が返されます。


挿入/削除できる引用マークやコメントマーク

  • 各行の先頭にメタ記号を追加
ポップアップメニュー内に表示するラベル(注釈)や並び順をカスタマイズすると、Markdown 用のマークアップなどに活用しやすくなります。


以下は、公開・配布しているソースコードの初期状態で利用できる引用マークやコメントマークです。
  • がついたものは、半角スペースつきで記号を挿入します。
  • 「1つ削除」/「すべて削除」では、記号のうしろの半角スペースの有無を区別しません。
  • 」中黒 の半角差分の 「」半角カナの中黒(U+FF65)と、「·」欧文用ユニコード文字のビュレット(U+00B7) を削除対象として追加してあります。
> ␣ 		(メール 引用マーク)
>> 		(BBS アンカー記号)

 		(箇条書き 全角中黒)
␣ * ␣ 		(箇条書き アスタリスク)
* ␣ 		(箇条書き アスタリスク)
- ␣ 		(箇条書き 半角ハイフンマイナス)
 		(注意書き ※印)

␣ 		(半角スペース)
☐ 		(全角スペース)
› 		(TABコード)
⏎ 		(空行: 改行コード) ※削除コマンドからの削除不可

// ␣ 		(C系, JavaScript コメント)
# ␣ 		(Perl, Ruby, Python コメント)
; ␣ 		(INI コメント)
' ␣ 		(VB コメント)
-- ␣ 		(SQL コメント)
: ␣ 		(MS-DOS ラベル)
:: ␣ 		(BAT コメント)
REM ␣ 		(BAT コメント)


※以下の3種は、「1つ削除」/「すべて削除」ではアンコメントされません。
 ポップアップメニュー内にそれぞれ専用の "アンコメント" コマンドがあります)


  • 選択範囲の行全体をひとまとめでコメントアウト
<!-- (XML コメント) -->

/* (C系, JavaScript コメント) */
  • 選択範囲の行全体を複数行形式でコメントアウト(※字下げ位置でコメントアウトします)
/*
 *  (C系, JavaScript ブロックコメント)
 */


削除系コマンド


  • 1つ削除
選択範囲内の各行の行頭から、設定項目の「引用符の種類」に登録されている記号や文字列と一致するものを1つずつ削除します (※ 改行記号 "\n" は対象外 ⇒ 「空行を削除」コマンド)。


  • すべて削除
選択範囲内の各行の行頭から、設定項目の「引用符の種類」に登録されている記号や文字列と一致するものをすべて削除します (※ 改行記号 "\n" は対象外)。


  • 空行を削除
選択範囲内の空行を削除します。


  • 行頭の空白文字を削除
選択範囲内の各行の行頭から、全角/半角空白とタブ文字をすべて削除します。
※ このマクロでは、行末の空白文字列の削除はできません ( ⇒ 「カッコで囲う」マクロや「テキスト整形」マクロを利用してください)。


  • 行頭から任意の文字列を削除
選択範囲内の各行の行頭から、入力ダイアログで指定された文字列を削除します。


  • 行頭から任意の文字数を削除
選択範囲内の各行の行頭から、入力ダイアログで指定された文字数だけ削除します。


ポップアップメニューの既存の項目を減らしたいばあいは、ソースコード内の "m.Add( … );" の行を // でコメントアウトするとその項目をメニューから隠せます。
ただし、メニューから隠れるだけで、「1つ削除」/「すべて削除」の対象から除外されるわけではありません。


ダウンロード


ダウンロード: 「ファイル:引用符/コメント.zip」(アイコン入り)

  • 第四版: 2020/06/23
コマンドを追加: 「任意の文字数を削除」
マルチカーソル/複数選択範囲に対応
  • 第三版: 2019/04/07
コマンドを追加: 「空行の追加」、「任意の文字列を削除」
  • 第二版: 2018/10/30
  • 第一版: 2018/10/28


ソースコード

#title = "引用符を追加..."
#tooltip = "引用符/コメントマークを追加・削除"
#icon = "quote2[3].ico"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",96

/**
 * -----------------------------------------------------------------------------
 * 引用の追加		( => 2018/10/14 公開停止)
 * Orginal Copyright (c) Kuro. All Rights Reserved.
 * www:    http://www.haijin-boys.com/
 * -----------------------------------------------------------------------------
 * Modified by sukemaru	(2018/10/28 - 2020/06/23)
 * 「引用符を追加」または「引用符/コメント」
 * 選択範囲の行頭に引用符/コメントマークを追加・削除する
 * -----------------------------------------------------------------------------
 * ▼ Kuro 版「引用の追加」マクロからの変更点 ▼
 * 
 * ・ポップアップメニューの体裁を変更した。
 * ・引用符の種類を増やした(※ 基本的に「メタ文字+半角スペース」)。
 * ・「クリップボード」と「任意の文字列」を追加。
 * ・配列に削除用の要素(半角スペースなしのメタ文字のみの差分)も余分に追加した。
 *   ※「すべて削除」では、半角スペース、全角スペース、タブ文字での字下げをすべて削除する。
 *   ※「クリップボード」、「任意の文字列」、「空行」、「JS/XML コメントアウト」は、
 *     「1つ削除//すべて削除」では削除されない。
 * 
 * ・選択範囲があったときには行全体に拡張・復帰するようにした。
 * ・選択範囲なしの状態からカーソル行にたいして「追加/1つ削除/すべて削除」を実行したときは、
 *   実行前の位置にカーソルを返せるようにした。
 * ・「1つ削除」の不具合箇所を修正した。
 */

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

// ■ 「任意の文字列」で入力した文字列の一時記憶方法
var tagType = 2;
  // 0 : 一時記憶なし
  // 1 : タブ(文書)ごとに一時記憶する(Document.Tag)
  // 2 : ウインドウごとに一時記憶する(Editor.Tag)
  // 3 : すべてのタブとウインドウ共通で一時記憶する(window.Tag)


// ■ 引用符の種類(追加用)
var q = [ "" ,		// 以下 r = 1~10、11~20、21~30、31~ の ID 順
  "> " ,  "・" ,  " * " ,  "- " ,  " " ,  " " ,  "\t" ,  "// " ,  "# " ,  "; " ,
  "' " ,  "-- " ,  ": " ,  ":: " ,  "REM " ,  "※" ,  ">>" ,  "* " ,  "" ,  "" ,
  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,
  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,  "" ,
"" ];


/* 
 * ※半角スペースの有無で「1つ削除/すべて削除」がマッチしなくなるので、
 *   以下に半角スペース あり/なし の差分要素を適当に追加してあります。
 * ※半角スペース差分(中黒=ビュレットは全角/半角差分も)の変更は、配列内の編集ではなく
 *   ポップアップメニュー項目のID(番号)変更で対応しないと、「1つ削除/すべて削除」が効かなくなります
 * (同一文字をふくむ要素は文字列の長いものが先に置かれていないとダメ)。
 * 「・」は半角カナの中黒(U+FF65)、「·」は欧文用ユニコード文字のビュレット(U+00B7)
 */
// ■ 引用符の種類(削除用差分)
var qq = [
  ">" ,  "・ " ,  "・" ,  "· " ,  "·" ,  "*" ,  "-" ,  "//" ,  "#" ,  ";" ,
  "'" ,  "--" ,  ":" ,  "::" ,  "" ,  "" ,  ""
];
q = q.concat( qq );

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

var d = editor.ActiveDocument;
var s = editor.ActiveDocument.selection;
var m = CreatePopupMenu();

if ( d.ReadOnly ) {
  m.Add( " ドキュメントは 書き換え禁止 です & ", 0 );
}

else {
  var cb = ClipboardData.GetData();	// クリップボードのテキストデータを取得

  // ポップアップメニューの項目とID
  // m.Add( "ラベル", r ); の各行は、任意に上下移動(並べ替え)してよいが、
  // r の数値は上の配列 q の並び順やテキスト変換処理の case r: に対応しているので変更しないこと!

  m.Add( "?	任意の文字列 (&E)...", 1010 );
  m.Add( "[ … ]	クリップボード (&C)", 1000, !cb *2 );

  m.Add( "", 0, meMenuSeparator );
  m.AddPopup( "▲	削除 (&D)", sm1 = CreatePopupMenu() );
    sm1.Add( "●	1つ削除 (&D)", 1013 );
    sm1.Add( "●	すべて削除 (&A)", 1014 );
    sm1.Add( "", 0, meMenuSeparator );
    sm1.Add( " ↲ 	空行を削除 (&B)", 1015 );
    sm1.Add( "␣␣	行頭の空白文字を削除 (&S)", 1016 );
    sm1.Add( "", 0, meMenuSeparator );
    sm1.Add( "?	行頭から任意の文字列を削除 (&Q)...", 1011 );
    sm1.Add( "? 	行頭から任意の文字数を削除 (&X)...", 1012 );

  m.Add( "", 0, meMenuSeparator );
  m.Add( "> 	> メール引用符 (&>)", 1 );
  m.Add( ">>	>> BBS アンカー (&>)", 17 );
  m.Add( "・	・ 箇条書き (&/)", 2 );
  m.Add( " * 	* 箇条書き (&*)", 3 );
  m.Add( "* 	* 箇条書き (&W)", 18 );
  m.Add( "- 	- 箇条書き (&-)", 4 );
  m.Add( "※	※注意書き (&K)", 16 );

  m.Add( "", 0, meMenuSeparator );
  m.Add( "␣	半角スペース (&1)", 5 );
  m.Add( "⃞	全角スペース (&2)", 6 );
  m.Add( "›	タブコード (&T)", 7 );
  m.Add( "⏎	空行 (&N) ...", 1006 );

  m.Add( "", 0, meMenuSeparator );
  m.Add( "\/\/ 	JS・C コメント (&J)", 8 );	// アクセラレータは「JS コメントアウト」と重複
  m.Add( "# 	Perl コメント (&P)", 9 );
  m.Add( "; 	INI コメント (&I)", 10 );
  m.Add( "’ 	VB コメント (&V)", 11 );
  m.Add( "-- 	SQL コメント (&S)", 12 );
  m.Add( ": 	MS-DOS ラベル (&M)", 13 );
  m.Add( ":: 	BAT コメント (&B)", 14 );
  m.Add( "REM 	BAT コメント (&R)", 15 );

  m.Add( "", 0, meMenuSeparator );
  m.Add( "/*  *  */	JS コメントアウト 2 (&J)", 1002 );
  m.Add( "/*  */	JS コメントアウト 1  (&J)", 1001 );
  m.Add( "	JS アンコメント (&U)", 1003 );
  m.Add( "", 0, meMenuSeparator );
  m.Add( "<!--  -->	XML コメントアウト (&X)", 1004 );
  m.Add( "	XML アンコメント (&L)", 1005 );

  m.Add( "", 0, meMenuSeparator );
  m.Add( "キャンセル	& ", 0 );
}

// ポップアップメニューの表示
// m.Track(0); ならキャレット位置、m.Track(1); ならカーソル位置にサブメニューがポップアップ
var r = m.Track( mePosMouse = 1 );

if ( r > 0 ) {
  var p;
  var tagKey = "LineCommentQuote";
  if ( r === 1010 ) { p = AddStrPrompt( tagType, tagKey ); }
  if ( r === 1011 ) { p = DelStrPrompt( tagType, tagKey ); }
  if ( r === 1012 ) { p = DelByNumPrompt(); }	// 「任意の文字数を削除」

  var sx = ScrollX,  sy = ScrollY;
  // マルチカーソル対応
  var arg = [ q, r, p, cb ];
  // 選択範囲が1つで矩形選択ではないとき
  if ( ! s.Mode || s.Mode === meModeStream ) {
    LineCommentQuote_Main( arg );
  }
  // 矩形選択または複数選択のとき
  else {
    BeginUndoGroup();
    AddUndo();
    MultiFunction( LineCommentQuote_Main, arg );
    EndUndoGroup();
  }
  ScrollX = sx;  ScrollY = sy;
}


/**
 * 関数 LineCommentQuote_Main( [ q, r, p, cb ] )
 * 「引用符/コメント」マクロ
 */
function LineCommentQuote_Main( arg ) {
  var q  = arg[0];
  var r  = arg[1];
  var p  = arg[2];
  var cb = arg[3];

  // 選択範囲
  var d = editor.ActiveDocument;
  var s = editor.ActiveDocument.selection;
  var pos = s.IsEmpty ? s.GetActivePos() : -1;
  var tx = s.GetTopPointX( mePosLogical );
  var ty = s.GetTopPointY( mePosLogical );
  var bx = s.GetBottomPointX( mePosLogical );
  var by = s.GetBottomPointY( mePosLogical );
  // 選択範囲の末尾が行頭にあるときの調整
  if ( ty < by && bx == 1 && d.Text.charAt( s.GetActivePos() ) ) {
    by --;
  }
  // 選択範囲を拡張
  s.SetActivePoint( mePosLogical, 1, by );
  s.EndOfLine( false, mePosLogical );
  s.SetAnchorPoint( mePosLogical, 1, ty );

  // 選択範囲の文字列を取得
  var st = tmp = s.Text;
  var exit = false;	// Quit の代用フラグ

  // IDごとのテキスト変換処理
  switch ( r ) {

  // 引用符/コメントマークを追加
  case 1:  case 2:  case 3:  case 4:  case 5:
  case 6:  case 7:  case 8:  case 9:  case 10: 
  case 11:  case 12:  case 13:  case 14:  case 15:
  case 16:  case 17:  case 18:
    // 各行の先頭に引用符/コメントマークを追加
    s.Text = st.replace( /^/gm, q[r] );
//     /* とりあえずコメントアウトしておく */ 
//     // マクロ実行前に選択範囲がなかった場合は、カーソルを元の位置に戻す
//     if ( pos > -1 ) {
//       s.SetActivePos( pos + q[r].length );
//       exit = true;
//     }
    break;

  // クリップボード
  case 1000:
    s.Text = st.replace( /^/gm, cb );
    if ( pos > -1 ) { s.SetActivePos( pos + cb.length );  exit = true; }
    break;

  // 空行を挿入	※空行は削除コマンドの削除対象に含まれない
  case 1006:
    var n = "\n";	// 空改行
    s.Text = st.replace( /^/gm, n );
    if ( pos > -1 ) { s.SetActivePos( pos + n.length );  exit = true; }
    break;

  // 任意の文字列(テキストボックス)
  case 1010:
    if ( p ) { s.Text = st.replace( /^/gm, p ); }
    if ( pos > -1 ) { s.SetActivePos( pos + p.length );  exit = true; }
    break;


  // 任意の文字列を削除
  case 1011:
    var reg = new RegExp(
      "^" + p.replace( /[.*+?^=!:${}()|[\]\/\\]/g, "\\$&" )
    , "gm" );
    tmp = st.replace( reg, "" );
    if ( tmp !== st ) {
      s.Text = tmp;	
      if ( pos > -1 && tmp.length ) {
        s.SetActivePos( pos - ( st.length - tmp.length ) );
        exit = true;
      }
    }
    else if ( pos > -1 ) { s.SetActivePos( pos );  exit = true; }
    break;

  // 任意の文字数を削除
  case 1012:
    if ( p && st ) {
      tmp = DeleteCharByNum( st, p );
      if ( tmp !== st ) {
        s.Text = tmp;
        if ( pos > -1 && tmp.length ) {
          s.SetActivePos( pos - ( st.length - tmp.length ) );
          exit = true;
        }
      }
      else if ( pos > -1 ) { s.SetActivePos( pos );  exit = true; }
    }
    break

  // 1つ削除
  case 1013:
    // 各行ごとに、配列 q の並び順で最初にマッチした引用符/コメントマークを削除
    tmp = DeleteQuote( st, q );
    if ( tmp !== st ) {
      s.Text = tmp;
      if ( pos > -1 && tmp.length ) {
        s.SetActivePos( pos - ( st.length - tmp.length ) );
        exit = true;
      }
    }
    else if ( pos > -1 ) { s.SetActivePos( pos );  exit = true; }
    break;

  // すべて削除
  case 1014:
    var org = st;	// DeleteQuote() のループ処理前のテキスト
    // st を最大40回 DeleteQuote() でループ処理
    for ( var i = 0; i < 40; i ++ ) {
      tmp = DeleteQuote( st, q );
      // DeleteQuote() 処理の前後でテキストが一致したらループを抜ける
      if ( tmp == st ) { break; }
      st = tmp;		// DeleteQuote() したテキスト tmp を st に再代入
    }
    if ( tmp !== org ) {
      s.Text = tmp;
      if ( pos > -1 && tmp.length) {
        s.SetActivePos( pos - ( org.length - tmp.length ) );
        exit = true;
      }
    }
    else if ( pos > -1 ) { s.SetActivePos( pos );  exit = true; }
    break;

  // 空行を削除
  case 1015:
    // キャレット位置が空行上のとき
    if ( pos > -1 && tx == 1 && d.GetLine( ty, 0 ) == "" ) {
      s.Delete();  exit = true;
    }
    else {
      tmp = st.replace( /\n+/g, "\n" );
      if ( st !== tmp ) { s.Text = tmp; }
      else if ( pos > -1 ) { s.SetActivePos( pos );  exit = true; }
    }
    break;

  // 行頭の空白文字を削除
  case 1016:
    tmp = st.replace( /^[\t  ]+/gm, "" );
    if ( tmp !== st ) {
      s.Text = tmp;
      if ( pos > -1 && tmp.length ) {
        s.SetActivePos( pos - ( st.length - tmp.length ) );
        exit = true;
      }
    }
    else if ( pos > -1 ) { s.SetActivePos( pos );  exit = true; }
    break;


  /* JavaScript コメントアウト1 */
  // <!-- XML コメントアウト -->   
  case 1001:  case 1004:
    // 「引用の追加」では行単位に拡張した選択範囲をまとめてコメントアウトするので、
    // 行の中間部分だけをコメントアウトするなら「カッコで囲う」マクロを使うこと。
    var p1 = ( r == 1001 ) ? "/\* " : "<!-- ";
    var p2 = ( r == 1001 ) ? " */"  : " -->";
    s.Text = p1 + st + p2;
    if ( ! st ) {
      s.SetActivePos( pos + p1.length );  exit = true;
    }
    break;

  /* * JavaScript コメントアウト2 */
  case 1002:
    var p1 = "/\**",  p2 = " */",  ast = " * ",  n = "\n";
    s.Text = CommentOutJS( st, p1, p2, ast );
    // マクロ実行前に空行だった場合はカーソルをコメント枠のなかに移動
    if ( pos > -1 ) {
      s.SetActivePos( pos + ( p1 + n + ast ).length  );
      exit = true;
    }
    // カーソルを接頭辞のうしろに移動
    else {
      s.SetActivePoint( mePosLogical, 1, ty );
      s.EndOfLine();  exit = true;
    }
    break;

  /* JSアンコメント */	// ※選択範囲内の各行頭のコメントマークにのみマッチ
  // case 41 & 42 の JavaScript コメント をアンコメントする
  case 1003:
    // コメントの "接頭辞" と "接尾辞" と "行頭記号" を正規表現で配列に
    var p = new Array( "\\/\\*+ ?" , " ?\\*\\/" , " ?(\\*|[・・]) ?" );
    tmp = DeleteComment( st, p )
    if ( tmp !== st ) { s.Text = tmp; }	// アンコメント完了
    else if ( pos > -1 ) { s.SetActivePos( pos );  exit = true; }
    break;

  /* XML アンコメント */
  // case 44 の <!--␣ XMLコメント ␣--> をアンコメントする
  case 1005:
    tmp = st.replace( / ?<!-+ ?| ?-+ *> ?/g , "" );
    if ( tmp !== st ) { s.Text = tmp; }
    else if ( pos > -1 ) { s.SetActivePos( pos );  exit = true; }
    break;

  default:
    break;
  }	// end switch(r)

  // 選択範囲を復元
  if ( ! exit ) {
    // ※移動/コピー/切り取り しやすいように末尾改行まで含める
    s.SetActivePos( s.GetActivePos() + 1 );
    s.SetAnchorPoint( mePosLogical, 1, ty );

    // 選択範囲の中身が \n のみなら選択解除
    if ( /^\n$/.test( s.Text ) ) {
      s.SetActivePos( s.GetAnchorPos() );
    }
  }
}


/* 関数 DeleteQuote( str, q ) */
function DeleteQuote( str, q ) {
  var a = str.split( "\n" );
  for ( var i = 0, len = a.length; i < len; i ++ ) {
    for ( var j = 0, qLen = q.length, qt; j < qLen; j ++ ) {
      qt = q[j];
      if ( ! qt ) { continue; }
      if ( a[i].substr( 0, qt.length ) === qt ) {
        a[i] = a[i].substr( qt.length );
        break;
      }
    }
  }
  return a.join( "\n" );
}

/**
 * 関数 CommentOutJS( arg0, arg1, arg2, arg3 )	// CommentOutJS( st, p1, p2, ast )
 *
 * ※ 行頭のインデントを維持して字下げされた位置でコメントアウトするパターン(コメントドキュメント向け)
 *   ・選択範囲内の最小の字下げ位置にあわせる。
 *   ・基本的に「JS アンコメント」してもレイアウトを保持できるが、
 *     各行の字下げルールが同じである前提なので、半角スペースもタブコードも1文字として数える。
 *   ・空白文字だけの行は空行と見做し、行頭記号 " * " を付けた後ろに空白文字を残さない。
 */
function CommentOutJS( str, p1, p2, ast ) {
  var a = str.split( "\n" );
  var b = [];

  // 各行の字下げ数からの最小値を取得
  for ( var i = 0, len = a.length; i < len; i ++ ) {
    var id = a[i].search( /[^ \t]/ );	// 字下げの空白文字数を取得(空白行では -1)
    b.push( id < 0 ? 100000 : id );	// ※ -1 は sort のジャマなのでデタラメな数値に置きかえる
  }
  // 配列 b を昇順で並びかえ(先頭行から最小の字下げ部分の「空白文字 ␣␣ 」を取得)
  b.sort( function( a, b ) { return ( a < b ) ? -1 : ( a > b ) ? 1 : 0; } );
  var blanc = a[0].slice( 0, b[0] );

  // 字下げ位置に中間行の行頭記号 " * " を追加
  for ( var i = 0, len = a.length; i < len; i ++ ) {
    if ( /^[ \t]*$/.test( a[i] ) ) { a[i] = blanc + ast; }	// 空白行
    else { a[i] = blanc + ast + a[i].slice( b[0] ); }
  }

  var prefix = blanc + p1 + "\n";	// 接頭辞
  var suffix = "\n" + blanc + p2;	// 接尾辞
  return prefix + a.join( "\n" ) + suffix;
}

/**
 * 関数 DeleteComment( arg1, arg2 )
 *
 * 複数のコメントブロックが選択範囲内にある場合、最初のコメントブロックしかアンコメントしない
 * 引数 arg2 は 配列[ "接頭辞" , "接尾辞" , "中間行の行頭記号" ] で、
 *   各要素はあらかじめ JavaScript の正規表現で記述されていないとダメ
 *   arg2 = p = [ "\\/\\*+ ?" , " ?\\*\\/" , " ?(\\*|[・・]) ?" ];
 */
function DeleteComment( str, p ) {
  var a = str.split( "\n" ), len = a.length;
  var hit = false, line0 = 0, line1 = len;

  // コメントの接尾辞と接尾辞を検索・置換する
  for ( var j = 0, reg1; j < 2; j ++ ) {
    // 接頭辞 p[0], 接尾辞 p[1] の検索用
    reg1 = new RegExp( p[j], "" );

    for ( var i = 0; i < len; i ++ ) {
      if ( reg1.test( a[i] ) ) {
        a[i] = a[i].replace( reg1, "" );
        // 接頭辞 p[0] がヒットした行
        if ( j == 0 ) { line0 = i;  hit = true;  break; }
        // 接尾辞 p[1] がヒットした行
        else { line1 = i;  break; }
      }
    }
  }

  // 中間行の行頭記号 p[2] (アスタリスク、中黒)を検索・削除
  // 行頭空白文字の後ろの2文字までを置換対象にする
  var reg2 = new RegExp( "^[ \\t]*" + p[2], "" );	// 行頭空白+行頭記号の検索用
  var reg3 = new RegExp( p[2], "" );	// 行頭記号の置換(削除)用

  // 接頭辞(よりも前)の行と接尾辞(よりも後ろ)の行は、検索・置換の対象外
  for ( var i = line0, limit = Math.min( len, line1 ), id, a1, a2;
  i < limit; i ++ ) {
    // 行頭記号がヒットしたら
    if ( reg2.test( a[i] ) ) {
      // 置換範囲(空白文字数+2文字)
      id = a[i].indexOf( a[i].match( /[^ \t]/ ) ) + 2;
      a1 = a[i].slice( 0, id ).replace( reg3, "" );
      a2 = a[i].slice( id );
      a[i] = a1 + a2;
    }
  }

  // 各行の行末空白文字と、先頭/末尾の空行を削除
  return a.join( "\n" )
          .replace( /[\t  ]*$/gm, "" )	
          .replace( /^[\s ]*\n|\n[\s ]*$/g, "" );
}

/**
 * 関数 GetTag( tagType, tagKey, property )
 * 指定された Tag の値を返す
 */
function GetTag( tagType, tagKey, property ) {
  try {
    var obj = ( typeof tagType === "object" ) ? tagType
            : ( tagType === 1 ) ? editor.ActiveDocument
            : ( tagType === 2 ) ? editor
            : ( tagType === 3 ) ? window
            : window;
    return ( obj.Tag.Exists( tagKey )
    && ( property ? property in obj.Tag( tagKey ) : true ) )
    ? property
      ? obj.Tag( tagKey )[ property ]
      : obj.Tag( tagKey )
    : null;
  } catch( e ) { Status = e;  return null; }
}

/**
 * 関数 SetTag( value, tagType, tagKey, property )
 * 指定された値を Tag に書き込む
 */
function SetTag( value, tagType, tagKey, property ) {
  try {
    var obj = ( typeof tagType === "object" ) ? tagType
            : ( tagType === 1 ) ? editor.ActiveDocument
            : ( tagType === 2 ) ? editor
            : ( tagType === 3 ) ? window
            : window;
    if ( property ) {
      if ( obj.Tag.Exists( tagKey ) ) {
        obj.Tag( tagKey )[ property ] = value;
      }
      else { obj.Tag( tagKey ) = { property: value }; }
    }
    else { obj.Tag( tagKey ) = value; }
  }
  catch( e ) { Status = e; }
  finally { return; }
}

/**
 * 関数 AddStrPrompt( tagType, tagKey )
 * 「任意の文字列」コマンド
 * 入力ダイアログで指定された文字列を返す
 */
function AddStrPrompt( tagType, tagKey ) {
  // 前回使用した文字列があればダイアログの初期値に再利用
  var str = "";
  if ( tagType && ( t = GetTag( tagType, tagKey, "addStr" ) ) ) {
    str = t;
  }
  // 入力ダイアログ
  var msg = "につける文字列:\t"
          + "改行 = \\\\\\n ; タブ = \\\\\\t  (注:¥記号3つ)";
  p = Prompt( "前" + msg, str ).replace( /\\\\\\t/g, "\t" )
                               .replace( /\\\\\\n/g, "\n" );
  if ( p && tagType ) {
    SetTag( p.replace( /\t/g, "\\\\\\t" ).replace( /\n/g, "\\\\\\n" )
    , tagType, tagKey, "addStr" );
  }
  return p;
}

/**
 * 関数 DelStrPrompt( tagType, tagKey )
 * 「任意の文字列を削除」コマンド
 * 入力ダイアログで指定された文字列を返す
 */
function DelStrPrompt( tagType, tagKey ) {
  // 前回使用した文字列があればダイアログの初期値に再利用
  var str = "";
  if ( tagType && ( t = GetTag( tagType, tagKey, "delStr" ) ) ) {
    str = t;
  }
  // 入力ダイアログ
  var msg = "から削除する文字列:\t"
          + "タブ = \\\\\\t  (注:¥記号3つ)";
  p = Prompt( "先頭(行頭)" + msg, str ).replace( /\\\\\\t/g, "\t" );
  if ( p && tagType ) {
    SetTag( p.replace( /\t/g, "\\\\\\t" ), tagType, tagKey, "delStr" );
  }
  return p;
}

/**
 * 関数 ToHalfWidth( strVal )
 * 全角英数記号を半角変換して返す
 */
function ToHalfWidth( strVal ){
  // 半角変換(文字コードをシフト)
  return strVal.replace( /[!-~]/g, function( tmpStr ) {
    return String.fromCharCode( tmpStr.charCodeAt(0) - 0xFEE0 );
  } );
}

/**
 * 関数 DelByNumPrompt()
 * 「任意の文字数を削除」コマンド
 * 入力ダイアログで指定された文字列から
 * 数字を抽出して半角数字にして返す
 */
function DelByNumPrompt() {
  var msg = "から削除する文字数 (数字のみ有効):";
  return ToHalfWidth( Prompt( "先頭(行頭)" + msg, "" )
         ).replace( /\D/g , "" );	// 半角に変換して数字以外の文字は削除
}


/**
 * 関数 MultiFunction( Fn, arg1 )
 * マルチカーソル(複数選択範囲)に対応させる
 * 第1引数: Function; 選択範囲ごとに適用する処理の関数
 * 第2引数以降: Function に渡す引数をまとめた配列
 */
function MultiFunction( Fn, arg ) {
  var d = editor.ActiveDocument;
  var s = d.selection;

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

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

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

    Fn( arg );	// LineCommentQuote_Main() 関数

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

  // マルチカーソル(複数選択範囲)を復帰
  for ( var i = 0; i < sCount; i ++ ) {
    s.AddPos( Sel[i].anc, Sel[i].act );
  }
}

謝辞

Kuro 氏の「引用の追加」マクロは Mery のマクロの勉強をはじめたきっかけになったマクロです。
文字列操作、ポップアップメニューの使い方、JavaScript の配列や関数の使い方、if 文、switch 文、for 文、etc... 入門者の "とっかかり" として最適なマクロだったとおもいます(※もとの「引用の追加」は、このページのものよりもシンプル&コンパクトなマクロでした)。
たいへん有用なマクロを作ってくださった Kuro 氏に感謝申し上げます。

残念ながら「引用の追加」マクロは公開停止になってしまいましたので、この度わたしの手元でカスタマイズしたものを公開させていただきました。
改訂版の公開について承諾していただいた Kuro 氏に重ねて御礼申し上げます。 (sukemaru)

スポンサーリンク