C#(csc.exe)だけで導入できる正規表現で検索置換する簡易ツールつくった【自分用】

色々とインストールができないお堅い環境にいて、正規表現検索すらままならないので、つくってみた。
Windowsでcsc.exeさえあれば導入可能。(Windows7以降なら入っている)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/6808084c-bd83-211f-487f-09dcbf0d4ea8.png)


# ソースコード(1/2)

```csharp:Replacer.cs

using System;
using System.Drawing;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Windows.Forms;


class TextReplacer:Form
{
    SplitContainer spl;

    TextBox txtPtrn; // for search
    TextBox txtReplace;
    Button btnFind;
    Button btnReplace;
    Button btnReplaceAll;
    CheckBox chkRegex;
    CheckBox chkSenseCase;
    
    MyRichTextBox rtxt;
    
    ToolStripStatusLabel statusLabel;

    static readonly Color defaultBackColor = Color.White;
    Font baseFont;

    string curFilePath;
    Encoding curEnc;
    bool editFlag;


    TextReplacer(string[] args)
    {
        curEnc = Encoding.UTF8;

        ClientSize = new Size(500, 400);

        var menuStrip1 = new MenuStrip();
        

        SuspendLayout();
        menuStrip1.SuspendLayout();

        var fileMenuItem = new ToolStripMenuItem(){ Text = "&File"};
        //var editMenuItem = new ToolStripMenuItem(){ Text = "&Edit"};
        menuStrip1.Items.Add(fileMenuItem);
        //menuStrip1.Items.Add(editMenuItem);

        fileMenuItem.DropDownItems.Add( new ToolStripMenuItem("&Open...", null, (s,e)=>{OpenFileDialogWithEditingCheck();}, Keys.Control | Keys.O) );
        var fileMenuItemReopenEnc = new ToolStripMenuItem("Reopen with encoding");
        fileMenuItem.DropDownItems.Add( fileMenuItemReopenEnc );
        fileMenuItemReopenEnc.DropDownItems.Add( new ToolStripMenuItem("UTF8"     , null, (s,e)=>{ReopenWithEnc(Encoding.UTF8);}, null) );
        fileMenuItemReopenEnc.DropDownItems.Add( new ToolStripMenuItem("Shift_JIS", null, (s,e)=>{ReopenWithEnc(Encoding.GetEncoding("Shift_JIS"));}, null) );

        //editMenuItem.DropDownItems.Add( new ToolStripMenuItem("&Find...", null, (s,e)=>{ShowFindDialog();}, Keys.Control | Keys.F) );



        Controls.Add(spl = new SplitContainer(){
            Dock = DockStyle.Fill,
            Orientation = Orientation.Horizontal,
            IsSplitterFixed = true,
        });

        Label lblSearch;
        spl.Panel1.Controls.AddRange(
            new Control[]{
                lblSearch = new Label(){
                    Text = "Find",
                    TextAlign = ContentAlignment.MiddleLeft,
                    Location = new Point(0, 0),
                    Size = new Size(50, 25),
                },
                txtPtrn = new TextBox(){
                    Multiline = false,
                    Location = new Point(50, 0),
                    Size = new Size(200, 25),
                },
                btnFind = new Button(){
                    Text = "Find",
                    Top = 0,
                    Size = new Size(100, 25),
                },
                lblSearch = new Label(){
                    Text = "Replace",
                    TextAlign = ContentAlignment.MiddleLeft,
                    Location = new Point(0, 25),
                    Size = new Size(50, 25),
                },
                txtReplace = new TextBox(){
                    Multiline = false,
                    Location = new Point(50, 25),
                    Size = new Size(200, 25),
                },
                btnReplace = new Button(){
                    Text = "Replace",
                    Top = 25,
                    Size = new Size(100, 25),
                },
                btnReplaceAll = new Button(){
                    Text = "ReplaceAll",
                    Top = 55,
                    Size = new Size(100, 25),
                },
                chkSenseCase = new CheckBox(){
                    Text = "大文字小文字を区別",
                    Location = new Point(50, 50),
                    Size = new Size(140, 25),
                },
                chkRegex = new CheckBox(){
                    Text = "正規表現",
                    Location = new Point(50, 75),
                    Size = new Size(80, 25),
                },
            }
        );
        
        txtPtrn.TextChanged += TxtPtrn_TextChanged;
        txtPtrn.KeyDown += TxtPtrn_KeyDown;

        chkRegex.Click += (s,e)=>{ReflectRegexValidityToControl();};
        
        btnFind.Click += BtnFind_Click;
        btnReplace.Click += BtnReplace_Click;
        btnReplaceAll.Click += BtnReplaceAll_Click;

        spl.Panel2.Controls.Add(rtxt = new MyRichTextBox(){
            Multiline = true,
            WordWrap = false,
            AcceptsTab = true,
            ScrollBars = RichTextBoxScrollBars.Both,
            Dock = DockStyle.Fill,
            AllowDrop = true,
            HideSelection = false,
        });

        rtxt.DragEnter += Rtxt_DragEnter;
        rtxt.DragDrop  += Rtxt_DragDrop;
        

        baseFont = new Font("ＭＳ ゴシック", 10); // rtxt.Font.Size);
        rtxt.Font = baseFont;

        //rtxt.BackColor = Color.FromArgb(240,240,240);
        rtxt.TextChanged += Rtxt_TextChanged;
        rtxt.SelectionChanged += Rtxt_SelectionChanged;

        var statusStrip = new StatusStrip();
        Controls.Add(statusStrip);
        
        statusStrip.Items.Add(statusLabel = new ToolStripStatusLabel(){
            Text = "",
        });
        

        // spl.IsSplitterFixed = true; はユーザがつかめないようにするだけで、固定してくれないっぽい。
        Load      += (sender,e)=>{MyResizeHandler();};
        Resize    += (sender,e)=>{MyResizeHandler();};
        ResizeEnd += (sender,e)=>{MyResizeHandler();};


        Controls.Add(menuStrip1);
        MainMenuStrip = menuStrip1;
        menuStrip1.ResumeLayout(false);
        menuStrip1.PerformLayout();
        ResumeLayout(false);
        PerformLayout();

        if ( args.Length == 1 ) {
            OpenFile(args[0], curEnc);
        }
        UpdateWindowTitle();
    }

    void Rtxt_SelectionChanged(object sender, EventArgs e)
    {
        statusLabel.Text = "L:"+(rtxt.GetLineFromCharIndex(rtxt.SelectionStart)+1).ToString();
    }

    void MyResizeHandler()
    {
        int w = ClientSize.Width - txtPtrn.Left - btnFind.Width;
        if(w<10){w=10;}
        int splDistanceTarget = chkRegex.Bottom;
        try {
            spl.SplitterDistance = splDistanceTarget;
        }
        catch ( InvalidOperationException ) {
            // ignore
        }
        txtPtrn.Width = w;
        txtReplace.Width = w;
        btnFind.Left = txtPtrn.Right;
        btnReplace.Left = txtReplace.Right;
        btnReplaceAll.Left = txtReplace.Right;
    }

    void TxtPtrn_TextChanged(object sender, EventArgs e)
    {
        ReflectRegexValidityToControl();
    }

    void TxtPtrn_KeyDown(object sender, KeyEventArgs e)
	{
		if ( e.KeyData == Keys.Enter ) {
            FindNext();
        }
    }

    void BtnReplaceAll_Click(object sender, EventArgs e)
	{
        Regex r = TryNewRegex();

        if ( r != null ) {
            string s = rtxt.Text;
            string afterS = txtReplace.Text;
            string s2 = r.Replace(s, afterS);
            rtxt.Text = s2;
        }
    }

    void BtnReplace_Click(object sender, EventArgs e)
    {
        ReplaceCurrent();
        FindNext();
    }

    void BtnFind_Click(object sender, EventArgs e)
    {
        FindNext();
    }
    
    void FindNext()
    {
        Regex r = TryNewRegex();

        if ( r != null ) {
            string s = rtxt.Text;
            int searchStartI;

            if ( rtxt.SelectionLength == s.Length ) {
                searchStartI = 0;
            }
            else if ( rtxt.SelectionLength == 0 ) {
                    searchStartI = rtxt.SelectionStart + 1;
            }
            else {
                searchStartI = rtxt.SelectionStart + rtxt.SelectionLength;
            }

            if ( searchStartI <= s.Length ) {
                Match m = r.Match(s, searchStartI);
                if ( m.Success ) {
                    rtxt.Select(m.Index, m.Length);
                }
                else {
                    statusLabel.Text = "Not found.";
                }
            }
        }
    }

    void ReplaceCurrent()
    {
        string s = rtxt.Text;
        if ( rtxt.SelectionLength != s.Length ) {
            int i = rtxt.SelectionStart;
            int len = rtxt.SelectionLength;
            rtxt.SelectedText = txtReplace.Text;
        }
    }

    void Rtxt_TextChanged(object sender, EventArgs e)
    {
        editFlag = true;
        UpdateWindowTitle();
    }

    void ReflectRegexValidityToControl()
    {
        if ( TryNewRegex() != null ) {
            txtPtrn.BackColor = defaultBackColor;
        }
        else {
            txtPtrn.BackColor = Color.FromArgb(0xFF,0xDD,0xDD);
        }
    }

    Regex TryNewRegex()
    {
        RegexOptions ropt = (chkSenseCase.Checked)?RegexOptions.None:RegexOptions.IgnoreCase;
        try {
            if ( chkRegex.Checked ) {
                return new Regex(txtPtrn.Text, ropt);
            }
            else {
                return new Regex(Regex.Escape(txtPtrn.Text), ropt);
            }
        }
        catch ( ArgumentException ) {
        }
        return null;
    }

    void UpdateWindowTitle()
    {
        string s = curFilePath??"Untitled";
        string dirtyMark = editFlag?" (*)":"";
        Text = s + dirtyMark + " - Replacer";
    }


	void Rtxt_DragEnter(object sender, DragEventArgs e)
	{
		if ( e.Data.GetDataPresent(DataFormats.FileDrop) ) {
			e.Effect = DragDropEffects.Copy;
		}
		else {
			e.Effect = DragDropEffects.None;
		}
	}
	
	void Rtxt_DragDrop(object sender, DragEventArgs e)
	{
		if ( e.Data.GetDataPresent(DataFormats.FileDrop) ) {
			string[] fileNames;
			fileNames = (string[])e.Data.GetData(DataFormats.FileDrop, false);
			if ( fileNames.Length == 1 ) {
                OpenFileWithEditingCheck(fileNames[0], curEnc);
            }
		}
	}

    bool GetDiscurdDialogResultWhenEditing()
    {
        if ( editFlag ) {
            DialogResult ret = MessageBox.Show("Are you O.K. to discard current text.", "Confirmation", MessageBoxButtons.OKCancel);
            if ( ret != DialogResult.OK ) {
                return false;
            }
        }
        return true;
    }

    bool OpenFileWithEditingCheck(string fileName, Encoding enc)
    {
        if ( !GetDiscurdDialogResultWhenEditing() ){
            return false;
        }
        return OpenFile(fileName, enc);
    }

    bool OpenFile(string fileName, Encoding enc)
    {
        string tmpPath = Path.GetFullPath(fileName);
        Text = Path.GetFileName(tmpPath);
        try {
            rtxt.Text = File.ReadAllText(tmpPath, enc);
        }
        catch ( IOException e ) {
            MessageBox.Show(e.ToString());
            return false;
        }
        editFlag = false;
        curFilePath = tmpPath;
        UpdateWindowTitle();
        return true;
    }

    bool ReopenWithEnc(Encoding enc)
    {
        if ( curFilePath != null ) {
            curEnc = enc;
            return OpenFileWithEditingCheck(curFilePath, enc);
        }
        else {
            curEnc = enc;
            return false;
        }
    }

    bool OpenFileDialogWithEditingCheck()
    {
        if ( !GetDiscurdDialogResultWhenEditing() ) {
            return false;
        }
        OpenFileDialog ofd = new OpenFileDialog();
        ofd.FileName = "テンプレート.xml";
        ofd.Filter = "Text file|*.txt;*.log;*.csv;*.xml;*.htm;*.html;*.adoc;*.bat;*.ps;*.c;*.h;*.asm;*.ld;*.map;*.mot;*.js;*.cs;*.bas;*.java;*.rb;*.py|All file(*.*)|*.*";
        ofd.RestoreDirectory = true;

        if ( ofd.ShowDialog() == DialogResult.OK ) {
            return OpenFile(ofd.FileName, curEnc);
        }
        return false;
    }

    [STAThread]
    public static void Main(string[] args)
    {
        Application.Run(new TextReplacer(args));
    }
}
```


# ソースコード(2/2)

```csharp:MyRichTextBox.cs

using System;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Windows.Forms;


internal class MyRichTextBox:RichTextBox
{
    //----------------------------------------------------------------
    // Win32
    //
    private const int EM_SETTABSTOPS = 0x00CB;
    private const long IMF_DUALFONT = 0x80;
    private const int WM_SETREDRAW = 0x0008;
    private const int WM_USER = 0x0400;
    private const int EM_SETLANGOPTIONS = WM_USER + 120;
    private const int EM_GETLANGOPTIONS = WM_USER + 121;
    private const int EM_GETTEXTEX = (WM_USER + 94);
    private const int EM_GETTEXTLENGTHEX = (WM_USER + 95);

    // Flags for the GETEXTEX data structure  
    private const int GT_DEFAULT = 0;
    private const int GTL_CLOSE = 4; // Fast computation of a "close" answer 
    private const int GTL_DEFAULT = 0; // Do default (return # of chars) 

    class NativeMethods
    {
        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
        public extern static IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int msg, ref GETTEXTEX wParam, StringBuilder lParam);

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int msg, ref GETTEXTLENGTHEX wParam, IntPtr lParam);
    }

    [StructLayout(LayoutKind.Sequential)]
    struct GETTEXTEX
    {
        public int cb;
        public int flags;
        public int codepage;
        public IntPtr lpDefaultChar;
        public IntPtr lpUsedDefChar;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct GETTEXTLENGTHEX
    {
        public int flags;
        public int codepage;
    }
    //
    //----------------------------------------------------------------

    protected override void OnFontChanged(EventArgs e)
    {
        SetFixFont();
        SetTabStop(4);
    }

    public override string Text
    {
        get {
            var getLength = new GETTEXTLENGTHEX();
            getLength.flags = GTL_CLOSE; //get buffer size
            getLength.codepage = 1200; //Unicode
            int textLength = (int)(NativeMethods.SendMessage(this.Handle, EM_GETTEXTLENGTHEX, ref getLength, IntPtr.Zero).ToInt64());
            var getText = new GETTEXTEX();
            getText.cb = textLength + 2; //add space for null terminator
            if ( getText.cb < 2 ) { return ""; } // overflow
            getText.flags = GT_DEFAULT;
            getText.codepage = 1200; //Unicode
            var sb = new StringBuilder(getText.cb);
            NativeMethods.SendMessage(this.Handle, EM_GETTEXTEX, ref getText, sb);
            return sb.ToString();
        }
        set { base.Text = value; }
    }

    public override int TextLength
    {
        get {
            var getLength = new GETTEXTLENGTHEX();
            getLength.flags = GTL_DEFAULT; //Returns the number of characters
            getLength.codepage = 1200; //Unicode
            int textLength = (int)(NativeMethods.SendMessage(this.Handle, EM_GETTEXTLENGTHEX, ref getLength, IntPtr.Zero).ToInt64());
            if ( textLength < 0 ) { return 0; } // overflow
            return textLength;
        }
    }

    private void SetFixFont()
    {
        IntPtr lParam = NativeMethods.SendMessage(this.Handle, EM_GETLANGOPTIONS, new IntPtr(0), new IntPtr(0));
        lParam = new IntPtr( ((long)lParam) & (~IMF_DUALFONT));
        NativeMethods.SendMessage(this.Handle, EM_SETLANGOPTIONS, new IntPtr(0), lParam);
    }

    private void SetTabStop(int tabSize)
    {
        int[] tabarray = new int[] { tabSize*4 };
        int wparam = tabarray.Length;

        IntPtr parray = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * tabarray.Length);
        Marshal.Copy(tabarray, 0, parray, tabarray.Length);
        NativeMethods.SendMessage(this.Handle, EM_SETTABSTOPS, new IntPtr(wparam), parray);
    }
}
```