「TAB/半角空白 トグル変換」の版間の差分

提供: MeryWiki
ナビゲーションに移動 検索に移動
Sukemaru (トーク | 投稿記録)
編集の要約なし
Sukemaru (トーク | 投稿記録)
第2版
1行目: 1行目:
実行するごとに「TAB ⇔ 半角空白」の相互変換をします。
実行するごとに「TAB ⇔ 半角空白」の相互変換をします。<br>
<span style="color:#0000c0;">'''選択範囲内のみ''' の「TAB ⇔ 半角空白」の相互変換も可。</span>
<br><br>
<br><br>
[編集] メニュー >> [選択範囲の変換] サブメニューのコマンド「[[ヘルプ:編集#タブを空白に変換|タブを空白に変換]]」と「[[ヘルプ:編集#空白をタブに変換|空白をタブに変換]]」は
[編集] メニュー >> [選択範囲の変換] サブメニュー のコマンド「[[ヘルプ:編集#タブを空白に変換|タブを空白に変換]]」と「[[ヘルプ:編集#空白をタブに変換|空白をタブに変換]]」は...
# メニューの中をたどってコマンドを見つけるのが面倒
# メニューの中をたどってコマンドを見つけるのが面倒
# 選択範囲がないと実行できない
# 選択範囲がないと実行できない
# 選択した範囲を無視して論理行全体が変換される
# 選択した範囲を無視して論理行全体が変換される
# トリプルクリックや行番号のクリック(ドラッグ)で範囲選択した状態から実行すると、ひとつ下の行まで変換されてしまう
# トリプルクリックや行番号のクリック(ドラッグ)で範囲選択した状態から実行すると、ひとつ下の行まで変換されてしまう
などいろいろと使いづらい部分があります。
などのような使いづらい部分があります。
<br><br>
<span style="color:#0000c0;">このマクロでは、キャレット位置に TAB や連続する半角空白があるときは自動的に空白文字列を範囲選択して「TAB ⇔ 半角空白」変換します。</span><br>
連続で実行した場合にはトグル式に相互変換しますので、ツールバーアイコンひとつでふたつの機能(+α)を利用できます。
<br><br>
<br><br>
このマクロでは、キャレット位置に TAB や連続する半角空白があるときは自動的に空白文字列を範囲選択して「TAB ⇔ 半角空白」変換します。<br> 連続実行した場合にはトグル式に相互変換しますので、ツールバーアイコンひとつでふたつの機能(+α)を利用できます。
<br>


== 設定項目 ==
== 設定項目 ==
<br>
<br>
; <code>tabColumns</code>
'''<code>indentSize</code>''' (初期値: '''0''')<br>
「タブの桁数」(半角空白で何コ分の幅か)を数値で指定します。<br> [オプション] ダイアログの [基本] カテゴリの「タブの桁数」の設定値で指定してください。
「タブの桁数」(半角空白で何コ分の幅か)を数値で指定します。
: ''e.g.'' <code>tabColumns = 4</code> で「半角空白6コ」の選択範囲を変換すると「TAB1コ+半角空白2コ」になります。
: 変更する場合は、[オプション] ダイアログ >> [基本] カテゴリ の「タブの桁数」の設定値で指定してください。
* '''<code>tabColumns = 0</code>''' を指定すると「タブの桁数」を Mery から自動取得します。
 
: Mery ver 3 以降では <code>Document.IndentSize</code> プロパティでの取得を試行します。 取得できなかった場合や Mery ver 2.x では Mery.ini から読み込みます。
: ''e.g.'' <code>indentSize = 4</code> で「半角空白6コ」の選択範囲を変換すると「TAB1コ+半角空白2コ」になります。
: [https://www.haijin-boys.com/software/mery/mery-2-8-7 EditorConfig] で <code>indent_size</code> をカスタマイズしている場合は、<code>tabColumns = 0</code> に設定してください。
 
* '''<code>indentSize = 0</code>''' なら「タブの桁数」を Mery から自動取得します。
: Mery ver 3 以降では <code>Document.IndentSize</code> プロパティでの取得を試行します。 <code>Document.IndentSize</code> を取得できなかった場合や Mery ver 2.x では Mery.ini から「タブの桁数」を読みこみます。
<br>
<br>
; <code>wholeLinesEnable</code>
 
: 論理行全体の「TAB ⇔ 半角空白」変換を許可するかしないかの設定です。
'''<code>wholeLines</code>''' (初期値: '''true''')<br>
: <code>wholeLinesEnable = true</code> のとき、選択範囲がなくキャレット位置に変換対象になる空白文字列がない場合や、選択範囲があっても変換対象になる空白文字列がない(または半角空白の数が「タブの桁数」に満たない)場合、は、論理行全体を選択範囲にして「TAB ⇔ 半角空白」の変換を試行します。
論理行全体(複数行可)の「TAB ⇔ 半角空白」変換を許可するかしないかの設定です。
: <source lang="javascript" inline>wholeLines = true</source> のときで、選択範囲がなくキャレット位置に変換対象になる空白文字列がない場合や、選択範囲があっても変換対象になる空白文字列がない(または半角空白の数が「タブの桁数」に満たない)場合、選択範囲に非空白文字(全角空白や改行も)がふくまれている場合は、論理行全体を選択範囲にして「TAB ⇔ 半角空白」の変換を試行します。
* Mery の標準コマンドの「タブを空白に変換」と「空白をタブに変換」で処理するので複数行の範囲選択状態からでも変換可能ですが、つねに論理行全体、論理行内のすべての TAB とすべての連続する半角空白が対象となります。
* Mery の標準コマンドの「タブを空白に変換」と「空白をタブに変換」で処理するので複数行の範囲選択状態からでも変換可能ですが、つねに論理行全体、論理行内のすべての TAB とすべての連続する半角空白が対象となります。
* 事前処理コードを入れてあるので、トリプルクリックや行番号のクリックまたはドラッグで行全体+末尾改行のある範囲選択状態から実行したときに、ひとつ下の行は変換対象になりません。
* トリプルクリックや行番号のクリックまたはドラッグで「行全体+末尾改行のある範囲選択状態」から実行したとき、ひとつ下の行は変換対象になりません。
<br>
<br>
'''<code>FullToHalf2</code>''' (初期値: '''false''')<br>
全角空白を「空白をタブに変換」の対象に含めるための設定です。
* 設定項目で FullToHalf2 = true にすると全角空白を「半角空白×2」とみなして「空白 ⇒ TAB」変換します。
* さいしょに「全角空白 ⇒ 半角空白×2」変換してから「TAB ⇒ 半角空白」「空白 ⇒ TAB」の順に試行します。
以下の理由によりあまり役に立つ機能ではなさそうなので、初期値を <source lang="javascript" inline>false</source> に設定してあります。
* 「連続する全角空白」や半角空白や TAB と混在文字列では、空白文字列すべてを自動選択することはできません。 あらかじめ範囲選択してから実行してください。
* 「全角空白 ⇒ 半角空白×2」の変換は一方通行(不可逆)です。
* このマクロの基本プランが「TAB ⇔ 半角空白」の相互変換であるため、「全角空白 ⇒ 半角空白×2」変換は単独的機能として設計していません。 全角空白をふくむ選択範囲から「全角空白 ⇒ 半角空白×2」変換した「連続する半角空白」の先頭が TAB の区切り位置にあれば、「空白 ⇒ TAB」変換します。


== 使用上の注意 ==
== 使用上の注意 ==
<br>
<br>
; 優先ルール
; 優先ルール
* 「TAB+半角空白」からの実行では、つねに「TAB ⇒ 半角空白」変換になります(再度実行すれば「半角空白 ⇒ TAB」変換)。
* 「TAB+半角空白」からの実行では、つねに「TAB ⇒ 半角空白」変換になります(再度実行すれば「半角空白 ⇒ TAB」変換できます)。<br>
 
* 本来、全角空白は「空白をタブに変換」の対象外です。 <source lang="javascript" inline>FullToHalf2 = false</source> で全角空白をふくむ空白文字列を範囲選択している場合は、選択範囲をふくむ行全体が変換対象になります。
:また、選択範囲が複数行にまたがっている場合は、つねに論理行全体が変換対象になります。
: ※ <source lang="javascript" inline>wholeLines = false</source> のときは変換なし。
 
* このマクロの設定項目 <code>indentSize</code> の値と Mery の [オプション] の「タブの桁数」の設定値(または [https://www.haijin-boys.com/software/mery/mery-2-8-7 EditorConfig] の <code>indent_size</code> の値)がことなると、「半角空白 ⇒ TAB」変換の可否判定や変換結果がうまくないかんじになる場合があります。
: EditorConfig で <code>indent_size</code> をカスタマイズしている場合、このマクロの設定は <source lang="javascript" inline>var indentSize = 0</source> にしてください(Mery ver 3.0.x 以降)。
: ※「TAB ⇔ 半角空白」変換されなかったときには「変更行」のマーキングがつかないようにしてあります。
 
* <span style="color:#c00;">マルチカーソル複数選択や矩形選択には非対応です。</span> 回避処理は入れていませんので、意図したとおりの結果にならなかったときは Undo (Ctrl+Z) してください。
<br>
<br>
* 選択範囲が複数行にまたがっている場合は、つねに論理行全体が変換対象になります(<code>wholeLinesEnable = true</code> のとき)。


* このマクロの設定項目 <code>tabColumns</code> の値と Mery の [オプション] の「タブの桁数」の設定値がことなると、行全体の「半角空白 ⇒ TAB」変換の可否判定や変換結果がうまくないかんじになる場合があります。
; キャレット位置または選択範囲内の「TAB ⇔ 半角空白」変換
* <source lang="javascript" inline>wholeLines = false</source> のときで、選択範囲内に改行がある場合や、非空白文字(全角空白も含む)がある場合は変換しません。


* マルチカーソル複数選択や矩形選択には非対応です。 回避処理は入れていませんので、意図したとおりの結果にならなかったときは Undo (Ctrl+Z) してください。
* キャレット位置の空白文字列を変換するとき、行頭の TAB インデントから半角空白への変換では後ろの文字列がズレることはないようですが、<s>行の途中の TAB を半角空白に変換した場合や、TAB の区切り位置からズレた連続する半角空白幅を TAB に変換した場合は、うしろの文字列の先頭位置がズレます。</s><br><span style="color:#0000c0;"> ⇒ うしろの非空白文字列がズレないようにしました</span>(2020/05/29 更新)。
 
: ※ 連続する半角空白を中途半端な位置から範囲選択して「半角空白 ⇒ TAB」変換すると、選択範囲よりうしろの文字列の先頭位置がズレます。<br>
 
* 行の途中にある TAB ひとつを「TAB ⇒ 半角空白」変換するとき、TAB の見かけ上の幅が「タブの桁数」<code>indentSize</code> より小さいと、「TAB ⇒ 半角空白」したあとの半角空白の数が「タブの桁数」よりも少なくなります。
: ※「TAB ⇒ 半角空白」変換後にこのマクロを再実行しても「半角空白 ⇒ TAB」変換にならないことがあります。
<br>
<br>
; キャレット位置または選択範囲内の「TAB ⇔ 半角空白」変換
* <code>wholeLines = false</code> のとき、選択範囲内に改行がある場合や、非空白文字(全角空白も含む)がある場合は変換しません。


* キャレット位置の空白文字列を変換するとき、行頭の TAB インデントから半角空白への変換では後ろの文字列がズレることはないようですが、行の途中の TAB を半角空白に変換した場合や、TAB の区切り位置からズレた連続する半角空白幅を TAB に変換した場合は、後ろの文字列の先頭位置がズレます。
; 行全体の「TAB ⇔ 半角空白」変換
* 複数行選択の状態から「TAB ⇔ 半角空白」変換したときに、選択範囲内の行のブックマーク設定が維持されるかどうかは Mery 本体のバージョンによってことなります。 最新のベータバージョン(2020/05/29 時点で [https://www.haijin-boys.com/software/mery/mery-3-0-0 ver 3.0.3])ではブックマーク設定が維持されますが、2020/05/29 時点での <q>正式版</q> ver 2.6.7 ではブックマーク設定がズレたり消えたりします。
<br>
<br>


47行目: 76行目:
<br>
<br>
; 「[[ファイル:TAB/半角空白変換.zip]]」(アイコン入り)
; 「[[ファイル:TAB/半角空白変換.zip]]」(アイコン入り)
* 半角空白変換.js
* TAB/半角空白変換.js
* マテリアルデザインっぽい専用アイコン
* マテリアルデザインっぽい専用アイコン
* (オマケ)タブを空白に変換.js
* (オマケ)タブを空白に変換.js
* (オマケ)空白をタブに変換.js
* (オマケ)空白をタブに変換.js
<br>
<br>
2020/05/29: 第2版<br>
2020/05/27: 初版<br>


== ソースコード ==
== ソースコード ==
<source lang="javascript">
<source lang="javascript" style="height:60em; overflow:auto;">
#title = "TAB ⇔ 半角空白 トグル変換"
#title = "TAB ⇔ 半角空白 トグル変換"
#tooltip = "キャレット位置だけ TAB ⇔ 半角空白 の変換をする"
#tooltip = "キャレット位置だけ TAB ⇔ 半角空白 の変換をする"
#icon = "tabify[3].ico"
#icon = "tabify[3].ico"
/**
/**
  * ---------------------------------------------------------
  * ---------------------------------------------------------
  * 「TAB/半角空白変換」マクロ
  * 「TAB/半角空白変換」マクロ
  * sukemaru, 2020/05/27
  * sukemaru, 2020/05/27 - 2020/05/29
  * ---------------------------------------------------------
  * ---------------------------------------------------------
  * キャレット位置または選択範囲内だけの TAB 半角空白 変換可能
  * キャレット位置または選択範囲内だけの「TAB 半角空白」変換可
* 2020/05/29: うしろの非空白文字列の開始位置がズレないようコード改編
  *  
  *  
  * ※ 優先ルール: タブ+半角空白 からの変換のばあいは TAB 半角空白 変換
  * ※ 優先ルール: タブ+半角空白 からの変換のばあいは「TAB 半角空白」変換
  *  (再度実行すれば 半角空白 TAB 変換)
  *  (再度実行すれば「半角空白 TAB」変換)
* ※ EditorConfig でタブの桁数をカスタマイズしている場合は、
*  indentSize = 0 にすること。
  */
  */


72行目: 107行目:


// ■ タブの桁数
// ■ タブの桁数
var tabColumns = 0;
// ※ Mery から自動取得するなら indentSize = 0 にする
  // ※ Mery から自動取得するなら tabColumns = 0 にする
var indentSize = 0;
 
// ■ 論理行全体(複数行可)の「TAB ⇔ 半角空白」変換
var wholeLines = true; // true: する / false: しない


// ■ 論理行全体(複数行可)の TAB ⇔ 半角空白 もおこなう
// ■「全角空白 ⇒ 半角空白×2」変換
var wholeLinesEnable = true; // true: する / false: しない
// ※「全角空白 ⇒ 半角空白×2」変換後に「空白をタブに変換」する
var FullToHalf2 = false; // true: する / false: しない


// ---------- ▲ 設定項目 ▲ ---------- //
// ---------- ▲ 設定項目 ▲ ---------- //
84行目: 123行目:
var isEmpty = s.IsEmpty;
var isEmpty = s.IsEmpty;
var pos = ( isEmpty ) ? s.GetActivePos() : -1;
var pos = ( isEmpty ) ? s.GetActivePos() : -1;
var ay = s.GetActivePointY( mePosLogical );


if ( d.ReadOnly ) {
if ( d.ReadOnly ) {
   Status = " ドキュメントは書き換え禁止です。";
   Status = " ドキュメントは書き換え禁止です。";
}
}
else if ( ! tabColumns || ! ( tabColumns > 0 ) ) {
else {
  // Mery ver 3 以降?
  if ( ! indentSize || ! ( indentSize > 0 ) ) {
  if ( "IndentSize" in window.Document ) {
    // Mery ver 3 以降?
    tabColumns = d.IndentSize;
    if ( "IndentSize" in Document ) {
      indentSize = d.IndentSize;
    }
    // Mery.ini から「タブの桁数」を取得する
    if ( ! indentSize || ! ( indentSize > 0 ) ) {
      indentSize = GetIniOptionNum( "TabColumns" ) || 4;
    }
   }
   }
   // Mery.ini から「タブの桁数」を取得する
   Status = " タブの桁数: " + indentSize;
   if ( ! tabColumns || ! ( tabColumns > 0 ) ) {
 
     tabColumns = GetIniOptionNum( "TabColumns" ) || 8;
   if ( isEmpty ) {
     s.SelectWord();
   }
   }
}
  var act = s.GetActivePos();
  var anc = s.GetAnchorPos();
  var ty = s.GetTopPointY( mePosLogical );
  var bx = s.GetBottomPointX( mePosLogical );
  var st = s.Text;


if ( isEmpty ) {
   if ( st && indentSize > 0 && /^[\t  ]+$/.test( st ) ) {
  s.SelectWord();
     var ty = s.GetTopPointY( mePosLogical );
   if ( s.GetActivePointY( mePosLogical ) != ay ) {
    var bx = s.GetBottomPointX( mePosLogical );
     s.SetActivePos( pos );
  }
}
var act = s.GetActivePos(),  anc = s.GetAnchorPos();
var bp = Math.min( act, anc );
var str = tmp = s.Text;


if ( str && tabColumns > 0 && /^[\t ]+$/.test( str ) ) {
    // 全角空白 ⇒ 半角空白×2
    if ( FullToHalf2 && st.indexOf( " " ) > -1 ) {
      st = st.replace( /[ ]/g, "  " );
    }


  // TAB ⇒ 半角空白(キャレット位置または選択範囲内のみ)
    // TAB ⇒ 半角空白(キャレット位置または選択範囲内のみ)
  if ( str.indexOf( "\t" ) > -1 ) {
    if ( st.indexOf( "\t" ) > -1 ) {
    // Act1: 選択範囲を新規の空白行にして範囲選択
      // Act1: 行頭から選択範囲の末尾までを新規の行にする
    s.Text = "\n" + str + "\n";
      s.Text = "\n" + d.GetLine( ty, 0 ).slice( 0, bx - 1 ) + "\n";
    s.CharLeft();  s.SetAnchorPos( bp + 1 );
      s.CharLeft();  s.WordLeft( true );
    // Act2: 「タブを半角空白に変換」した文字列を取得
      // Act2: 「タブを半角空白に変換」して行末の空白文字列を取得
    s.Untabify();
      s.Untabify();
    tmp = s.Text;
      s.Collapse( meCollapseEnd );  s.WordLeft( true );
    // Act1, Act2 を巻き戻して TAB 半角空白 変換終了
      st = s.Text;
    d.Undo();  d.Undo();
      // Act1, Act2 を巻き戻して「TAB 半角空白」変換終了
    s.Text = tmp;
      d.Undo();  d.Undo();
    s.SetAnchorPos( s.GetActivePos() - tmp.length );
      s.Text = st;
  }
      s.SetAnchorPos( s.GetActivePos() - st.length );
    }


  // 半角空白 ⇒ TAB(キャレット位置または選択範囲内のみ)
    // 半角空白 ⇒ TAB(キャレット位置または選択範囲内のみ)
  else if ( RegExp( "[ ]{" + tabColumns + "}", "" ).test( str ) ) {
    // else if ( RegExp( "[ ]{" + indentSize + "}", "" ).test( st ) ) { // }
     // Act1: 選択範囲を新規の空白行にして範囲選択
     else if ( st.indexOf( " " ) > -1 ) {
    s.Text = "\n" + str + "\n";
      // Act1: 行頭から選択範囲の末尾までを新規の行にする
    s.CharLeft();  s.SetAnchorPos( bp + 1 );
      s.Text = "\n" + d.GetLine( ty, 0 ).slice( 0, bx - 1 ) + "\n";
    // Act2: 「半角空白をタブに変換」した文字列を取得
      s.CharLeft();  s.WordLeft( true );
    s.Tabify();
      // Act2: 「半角空白をタブに変換」して行末の空白文字列を取得
    tmp = s.Text;
      s.Tabify();
    // Act1, Act2 を巻き戻して 半角空白 TAB 変換終了
      s.Collapse( meCollapseEnd );  s.WordLeft( true );
    d.Undo();  d.Undo();
      st = s.Text;
    s.Text = tmp;
      // Act1, Act2 を巻き戻して「半角空白 TAB」変換終了
    s.SetAnchorPos( s.GetActivePos() - tmp.length );
      d.Undo();  d.Undo();
      if ( st !== s.Text ) {
        s.Text = st;
        s.SetAnchorPos( s.GetActivePos() - st.length );
      }
    }
   }
   }
}


// wholeLinesEnable = true のとき
  // wholeLines = true のとき
if ( str === s.Text && wholeLinesEnable ) {
  if ( st === s.Text && wholeLines ) {
  var ty = s.GetTopPointY( mePosLogical );
    var by = s.GetBottomPointY( mePosLogical );
  var bx = s.GetBottomPointX( mePosLogical );
    // 選択範囲を拡張(末尾改行を含めない)
  var by = s.GetBottomPointY( mePosLogical );
    if ( by != ty && bx === 1 ) { by -= 1; }
  // 選択範囲を拡張
    s.SetActivePoint( mePosLogical, 1, by );
  if ( by != ty && bx === 1 ) { by -= 1; }
    s.EndOfLine( false, mePosLogical );
  s.SetActivePoint( mePosLogical, 1, by );
    s.SetAnchorPoint( mePosLogical, 1, ty );
  s.EndOfLine( false, mePosLogical );
    st = s.Text;
  s.SetAnchorPoint( mePosLogical, 1, ty );
  str = s.Text;


  // TAB ⇒ 半角空白(行全体)
    // TAB ⇒ 半角空白(行全体)
  if ( str.indexOf( "\t" ) > -1 ) {
    if ( st.indexOf( "\t" ) > -1 ) {
    s.Untabify();
      s.Untabify();
  }
    }


  // 半角空白 ⇒ TAB(行全体)
    // 半角空白 ⇒ TAB(行全体)
  else if ( RegExp( "[ ]{2}", "" ).test( str ) ) {
    else if ( RegExp( "[ ]{" + indentSize + "}", "" ).test( st ) ) {
    s.Tabify();
      s.Tabify();
    if ( str === s.Text ) { d.Undo();  d.Saved = sv;  }
      if ( st === s.Text ) { d.Undo();  d.Saved = sv;  }
  }
    }
  // else {
    // else {
  //  s.Tabify();
    //  s.Tabify();
  //  if ( str === s.Text ) { d.Undo();  d.Saved = sv;  }
    //  if ( st === s.Text ) { d.Undo();  d.Saved = sv;  }
  // }
    // }


  if ( str === s.Text ) {
    // キャレット位置または選択範囲の復帰
    if ( pos >= 0 ) { s.SetActivePos( pos ); }
    if ( st === s.Text ) {
    else { s.SetActivePos( act );  s.SetAnchorPos( anc ); }
      if ( pos >= 0 ) { s.SetActivePos( pos ); }
      else { s.SetActivePos( act );  s.SetAnchorPos( anc ); }
    }
    // 行全体を変換したときは論理行全体を範囲選択
    else { s.SelectLine( true ); }
   }
   }
  else { s.CharRight( true ); }
}
}


194行目: 246行目:
   var iniText = iniFile.ReadAll();
   var iniText = iniFile.ReadAll();
   iniFile.Close();  Fso = null;
   iniFile.Close();  Fso = null;
   var value = RegExp( "^" + key + "=(\\d*)$", "m" ).exec( iniText )[1];
   var value = "";
  if ( ( reg = RegExp( "^" + key + "=(\\d*)$", "m" ) ).test( iniText ) )
  { value = reg.exec( iniText )[1]; }
   return Number( value );
   return Number( value );
}
}
201行目: 255行目:


== メモ ==
== メモ ==
前半部分は、行頭インデントの「ハード ⇔ ソフト」変換を目的として「Yes/No マクロ」への自家用追加コードとしてつくった簡易的な処理なので、行の途中にある空白文字列の「TAB ⇔ 半角空白」に使うには適していないとおもいます。<br>
* 2020/05/27:
<code>Alert(Document.IndentSize);</code>で -1 が返ってくるファイルもあって、なんかモヤっとしてます...。
: 前半部分は、行頭インデントの「ハード ⇔ ソフト」変換を目的として「Yes/No マクロ」への自家用追加コードとしてつくった簡易的な処理なので、<s>行の途中にある空白文字列の「TAB ⇔ 半角空白」に使うには適していないとおもいます。</s><br> <code>Alert(Document.IndentSize);</code>で -1 が返ってくるファイルもあって、なんかモヤっとしてたり...。
* 2020/05/29:
: 投稿した後になってからエラーや弱点を修正するのが「毎度」のパターンですみません。

2020年5月30日 (土) 14:50時点における版

実行するごとに「TAB ⇔ 半角空白」の相互変換をします。
選択範囲内のみ の「TAB ⇔ 半角空白」の相互変換も可。

[編集] メニュー >> [選択範囲の変換] サブメニュー のコマンド「タブを空白に変換」と「空白をタブに変換」は...

  1. メニューの中をたどってコマンドを見つけるのが面倒
  2. 選択範囲がないと実行できない
  3. 選択した範囲を無視して論理行全体が変換される
  4. トリプルクリックや行番号のクリック(ドラッグ)で範囲選択した状態から実行すると、ひとつ下の行まで変換されてしまう

などのような使いづらい部分があります。

このマクロでは、キャレット位置に TAB や連続する半角空白があるときは自動的に空白文字列を範囲選択して「TAB ⇔ 半角空白」変換します。
連続で実行した場合にはトグル式に相互変換しますので、ツールバーアイコンひとつでふたつの機能(+α)を利用できます。

設定項目


indentSize (初期値: 0)
「タブの桁数」(半角空白で何コ分の幅か)を数値で指定します。

変更する場合は、[オプション] ダイアログ >> [基本] カテゴリ の「タブの桁数」の設定値で指定してください。
e.g. indentSize = 4 で「半角空白6コ」の選択範囲を変換すると「TAB1コ+半角空白2コ」になります。
  • indentSize = 0 なら「タブの桁数」を Mery から自動取得します。
Mery ver 3 以降では Document.IndentSize プロパティでの取得を試行します。 Document.IndentSize を取得できなかった場合や Mery ver 2.x では Mery.ini から「タブの桁数」を読みこみます。


wholeLines (初期値: true)
論理行全体(複数行可)の「TAB ⇔ 半角空白」変換を許可するかしないかの設定です。

wholeLines = true のときで、選択範囲がなくキャレット位置に変換対象になる空白文字列がない場合や、選択範囲があっても変換対象になる空白文字列がない(または半角空白の数が「タブの桁数」に満たない)場合、選択範囲に非空白文字(全角空白や改行も)がふくまれている場合は、論理行全体を選択範囲にして「TAB ⇔ 半角空白」の変換を試行します。
  • Mery の標準コマンドの「タブを空白に変換」と「空白をタブに変換」で処理するので複数行の範囲選択状態からでも変換可能ですが、つねに論理行全体、論理行内のすべての TAB とすべての連続する半角空白が対象となります。
  • トリプルクリックや行番号のクリックまたはドラッグで「行全体+末尾改行のある範囲選択状態」から実行したとき、ひとつ下の行は変換対象になりません。


FullToHalf2 (初期値: false)
全角空白を「空白をタブに変換」の対象に含めるための設定です。

  • 設定項目で FullToHalf2 = true にすると全角空白を「半角空白×2」とみなして「空白 ⇒ TAB」変換します。
  • さいしょに「全角空白 ⇒ 半角空白×2」変換してから「TAB ⇒ 半角空白」「空白 ⇒ TAB」の順に試行します。

以下の理由によりあまり役に立つ機能ではなさそうなので、初期値を false に設定してあります。

  • 「連続する全角空白」や半角空白や TAB と混在文字列では、空白文字列すべてを自動選択することはできません。 あらかじめ範囲選択してから実行してください。
  • 「全角空白 ⇒ 半角空白×2」の変換は一方通行(不可逆)です。
  • このマクロの基本プランが「TAB ⇔ 半角空白」の相互変換であるため、「全角空白 ⇒ 半角空白×2」変換は単独的機能として設計していません。 全角空白をふくむ選択範囲から「全角空白 ⇒ 半角空白×2」変換した「連続する半角空白」の先頭が TAB の区切り位置にあれば、「空白 ⇒ TAB」変換します。


使用上の注意


優先ルール
  • 「TAB+半角空白」からの実行では、つねに「TAB ⇒ 半角空白」変換になります(再度実行すれば「半角空白 ⇒ TAB」変換できます)。
  • 本来、全角空白は「空白をタブに変換」の対象外です。 FullToHalf2 = false で全角空白をふくむ空白文字列を範囲選択している場合は、選択範囲をふくむ行全体が変換対象になります。
また、選択範囲が複数行にまたがっている場合は、つねに論理行全体が変換対象になります。
wholeLines = false のときは変換なし。
  • このマクロの設定項目 indentSize の値と Mery の [オプション] の「タブの桁数」の設定値(または EditorConfigindent_size の値)がことなると、「半角空白 ⇒ TAB」変換の可否判定や変換結果がうまくないかんじになる場合があります。
EditorConfig で indent_size をカスタマイズしている場合、このマクロの設定は var indentSize = 0 にしてください(Mery ver 3.0.x 以降)。
※「TAB ⇔ 半角空白」変換されなかったときには「変更行」のマーキングがつかないようにしてあります。
  • マルチカーソル複数選択や矩形選択には非対応です。 回避処理は入れていませんので、意図したとおりの結果にならなかったときは Undo (Ctrl+Z) してください。


キャレット位置または選択範囲内の「TAB ⇔ 半角空白」変換
  • wholeLines = false のときで、選択範囲内に改行がある場合や、非空白文字(全角空白も含む)がある場合は変換しません。
  • キャレット位置の空白文字列を変換するとき、行頭の TAB インデントから半角空白への変換では後ろの文字列がズレることはないようですが、行の途中の TAB を半角空白に変換した場合や、TAB の区切り位置からズレた連続する半角空白幅を TAB に変換した場合は、うしろの文字列の先頭位置がズレます。
    ⇒ うしろの非空白文字列がズレないようにしました(2020/05/29 更新)。
※ 連続する半角空白を中途半端な位置から範囲選択して「半角空白 ⇒ TAB」変換すると、選択範囲よりうしろの文字列の先頭位置がズレます。
  • 行の途中にある TAB ひとつを「TAB ⇒ 半角空白」変換するとき、TAB の見かけ上の幅が「タブの桁数」indentSize より小さいと、「TAB ⇒ 半角空白」したあとの半角空白の数が「タブの桁数」よりも少なくなります。
※「TAB ⇒ 半角空白」変換後にこのマクロを再実行しても「半角空白 ⇒ TAB」変換にならないことがあります。


行全体の「TAB ⇔ 半角空白」変換
  • 複数行選択の状態から「TAB ⇔ 半角空白」変換したときに、選択範囲内の行のブックマーク設定が維持されるかどうかは Mery 本体のバージョンによってことなります。 最新のベータバージョン(2020/05/29 時点で ver 3.0.3)ではブックマーク設定が維持されますが、2020/05/29 時点での 正式版 ver 2.6.7 ではブックマーク設定がズレたり消えたりします。


ダウンロード


ファイル:TAB/半角空白変換.zip」(アイコン入り)
  • TAB/半角空白変換.js
  • マテリアルデザインっぽい専用アイコン
  • (オマケ)タブを空白に変換.js
  • (オマケ)空白をタブに変換.js


2020/05/29: 第2版
2020/05/27: 初版

ソースコード

#title = "TAB ⇔ 半角空白 トグル変換"
#tooltip = "キャレット位置だけ TAB ⇔ 半角空白 の変換をする"
#icon = "tabify[3].ico"

/**
 * ---------------------------------------------------------
 * 「TAB/半角空白変換」マクロ
 * sukemaru, 2020/05/27 - 2020/05/29
 * ---------------------------------------------------------
 * キャレット位置または選択範囲内だけの「TAB ⇔ 半角空白」変換可
 * 2020/05/29: うしろの非空白文字列の開始位置がズレないようコード改編
 * 
 * ※ 優先ルール: タブ+半角空白 からの変換のばあいは「TAB ⇒ 半角空白」変換
 *  (再度実行すれば「半角空白 ⇒ TAB」変換)
 * ※ EditorConfig でタブの桁数をカスタマイズしている場合は、
 *   indentSize = 0 にすること。
 */

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

// ■ タブの桁数
// ※ Mery から自動取得するなら indentSize = 0 にする
var indentSize = 0;

// ■ 論理行全体(複数行可)の「TAB ⇔ 半角空白」変換
var wholeLines = true;		// true: する / false: しない

// ■「全角空白 ⇒ 半角空白×2」変換
// ※「全角空白 ⇒ 半角空白×2」変換後に「空白をタブに変換」する
var FullToHalf2 = false;	// true: する / false: しない

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

var d = editor.ActiveDocument,  s = d.selection;
var sv = d.Saved;
var isEmpty = s.IsEmpty;
var pos = ( isEmpty ) ? s.GetActivePos() : -1;

if ( d.ReadOnly ) {
  Status = " ドキュメントは書き換え禁止です。";
}
else {
  if ( ! indentSize || ! ( indentSize > 0 ) ) {
    // Mery ver 3 以降?
    if ( "IndentSize" in Document ) {
      indentSize = d.IndentSize;
    }
    // Mery.ini から「タブの桁数」を取得する
    if ( ! indentSize || ! ( indentSize > 0 ) ) {
      indentSize = GetIniOptionNum( "TabColumns" ) || 4;
    }
  }
  Status = " タブの桁数: " + indentSize;

  if ( isEmpty ) {
    s.SelectWord();
  }
  var act = s.GetActivePos();
  var anc = s.GetAnchorPos();
  var ty = s.GetTopPointY( mePosLogical );
  var bx = s.GetBottomPointX( mePosLogical );
  var st = s.Text;

  if ( st && indentSize > 0 && /^[\t  ]+$/.test( st ) ) {
    var ty = s.GetTopPointY( mePosLogical );
    var bx = s.GetBottomPointX( mePosLogical );

    // 全角空白 ⇒ 半角空白×2
    if ( FullToHalf2 && st.indexOf( " " ) > -1 ) {
      st = st.replace( /[ ]/g, "  " );
    }

    // TAB ⇒ 半角空白(キャレット位置または選択範囲内のみ)
    if ( st.indexOf( "\t" ) > -1 ) {
      // Act1: 行頭から選択範囲の末尾までを新規の行にする
      s.Text = "\n" + d.GetLine( ty, 0 ).slice( 0, bx - 1 ) + "\n";
      s.CharLeft();  s.WordLeft( true );
      // Act2: 「タブを半角空白に変換」して行末の空白文字列を取得
      s.Untabify();
      s.Collapse( meCollapseEnd );  s.WordLeft( true );
      st = s.Text;
      // Act1, Act2 を巻き戻して「TAB ⇒ 半角空白」変換終了
      d.Undo();  d.Undo();
      s.Text = st;
      s.SetAnchorPos( s.GetActivePos() - st.length );
    }

    // 半角空白 ⇒ TAB(キャレット位置または選択範囲内のみ)
    // else if ( RegExp( "[ ]{" + indentSize + "}", "" ).test( st ) ) {	// }
    else if ( st.indexOf( " " ) > -1 ) {
      // Act1: 行頭から選択範囲の末尾までを新規の行にする
      s.Text = "\n" + d.GetLine( ty, 0 ).slice( 0, bx - 1 ) + "\n";
      s.CharLeft();  s.WordLeft( true );
      // Act2: 「半角空白をタブに変換」して行末の空白文字列を取得
      s.Tabify();
      s.Collapse( meCollapseEnd );  s.WordLeft( true );
      st = s.Text;
      // Act1, Act2 を巻き戻して「半角空白 ⇒ TAB」変換終了
      d.Undo();  d.Undo();
      if ( st !== s.Text ) {
        s.Text = st;
        s.SetAnchorPos( s.GetActivePos() - st.length );
      }
    }
  }

  // wholeLines = true のとき
  if ( st === s.Text && wholeLines ) {
    var by = s.GetBottomPointY( mePosLogical );
    // 選択範囲を拡張(末尾改行を含めない)
    if ( by != ty && bx === 1 ) { by -= 1; }
    s.SetActivePoint( mePosLogical, 1, by );
    s.EndOfLine( false, mePosLogical );
    s.SetAnchorPoint( mePosLogical, 1, ty );
    st = s.Text;

    // TAB ⇒ 半角空白(行全体)
    if ( st.indexOf( "\t" ) > -1 ) {
      s.Untabify();
    }

    // 半角空白 ⇒ TAB(行全体)
    else if ( RegExp( "[ ]{" + indentSize + "}", "" ).test( st ) ) {
      s.Tabify();
      if ( st === s.Text ) { d.Undo();  d.Saved = sv;  }
    }
    // else {
    //   s.Tabify();
    //   if ( st === s.Text ) { d.Undo();  d.Saved = sv;  }
    // }

    // キャレット位置または選択範囲の復帰
    if ( st === s.Text ) {
      if ( pos >= 0 ) { s.SetActivePos( pos ); }
      else { s.SetActivePos( act );  s.SetAnchorPos( anc ); }
    }
    // 行全体を変換したときは論理行全体を範囲選択
    else { s.SelectLine( true ); }
  }
}

/**
 * 関数 GetIniOptionNum( key )
 * 引数で指定された設定項目の「値」を返す(※数値のみ)
 */
function GetIniOptionNum( key ) {
  var Fso = new ActiveXObject( "Scripting.FileSystemObject" );
  // Mery.ini を探す
  var meryPath = editor.FullName;
  var mery     = Fso.GetBaseName( meryPath );
  var iniPath  = meryPath.replace( /\.exe$/i, ".ini" );
  if ( ! Fso.FileExists( iniPath ) ) {
    iniPath = new ActiveXObject( "WScript.Shell" )
              .ExpandEnvironmentStrings( "%APPDATA%" )
              + "\\Mery\\" + mery + ".ini";
  }
  // Mery.ini を読み込んで項目 key の値を取得する
  var iniFile = Fso.OpenTextFile( iniPath, 1 );
  var iniText = iniFile.ReadAll();
  iniFile.Close();  Fso = null;
  var value = "";
  if ( ( reg = RegExp( "^" + key + "=(\\d*)$", "m" ) ).test( iniText ) )
  { value = reg.exec( iniText )[1]; }
  return Number( value );
}


メモ

  • 2020/05/27:
前半部分は、行頭インデントの「ハード ⇔ ソフト」変換を目的として「Yes/No マクロ」への自家用追加コードとしてつくった簡易的な処理なので、行の途中にある空白文字列の「TAB ⇔ 半角空白」に使うには適していないとおもいます。
Alert(Document.IndentSize);で -1 が返ってくるファイルもあって、なんかモヤっとしてたり...。
  • 2020/05/29:
投稿した後になってからエラーや弱点を修正するのが「毎度」のパターンですみません。
スポンサーリンク