LoginSignup
1
1

More than 5 years have passed since last update.

Paper Plane xUI(PPx)に一発でソースコード検索&表示できる「peco」改を移植した

Posted at

はじめに

もとの記事はこちらです。

コマンド一発でソースコード検索&表示できる「peco」改が凄い!

コマンド一発でインクリメンタル検索してキーワード周辺のソースコードを読めるツールが欲しいなぁって思ってたんです。

こちらが使ってみると大変便利でしたので、PPxでも使えるように移植しました。

デモ

全てのCソースから、main関数を検索

peco.gif

こっちはPPxならではの応用で、srcディレクトリのCソースを一端検索し、そこから「m」の文字が含まれるソースの中からmain関数を検索

peco2.gif

ツール構成

ここからは以前紹介した記事のを利用しています。

紹介リンク

スクリプト

再掲

ppxshell.js
//! script

//
// コマンド実行結果を返す(コマンド置換対応)
//

function _read(file) {
    var fs = new ActiveXObject("Scripting.FileSystemObject");
    var cwd = fs.GetParentFolderName(PPx.ScriptFullName);
    var fullpath = cwd + '\\' + file;
    if (!fs.FileExists(fullpath)) { PPx.Echo('* file not found:' + file); PPx.Quit(-1); }
    var f = fs.OpenTextFile(fullpath, 1);
    try { return f.ReadAll(); } finally { f.Close(); }
}

var util = eval(_read('./lib/util.js'));

///

// 環境変数の記号を置換する
function replaceEnvSymbol(cmd, sep, newsep) {
    while (true) {
        var m = cmd.match(new RegExp(sep + "[^ \\\/]+?" + sep));
        if (!m) break;

        var left = cmd.substring(0, m.index);
        var target = cmd.substring(m.index + sep.length, m.lastIndex - sep.length);
        var right = cmd.substring(m.lastIndex);

        cmd = left + newsep + target + newsep + right;
    }
    return cmd;
}
function escapeEnv(cmd) { return replaceEnvSymbol(cmd, '%', '_@ENV@_'); }
function restoreEnv(cmd) { return replaceEnvSymbol(cmd, '_@ENV@_', '%'); }


// 文字列を置換する
var g_escape_i = 1;
var g_escaped = {};
function escapeString(cmd) {
    while (true) {
        var m = null;
        if (!m) m = cmd.match(/'.*?[^\\]'/);
        if (!m) m = cmd.match(/".*?[^\\]"/);

        if (!m) break;

        var left = cmd.substring(0, m.index);
        var l = cmd.substring(m.index, m.index + 1);
        var target = cmd.substring(m.index + 1, m.lastIndex - 1);
        var r = cmd.substring(m.lastIndex - 1, m.lastIndex);
        var right = cmd.substring(m.lastIndex);

        target = internalCommand(target).cmd;

        var key = "__@ESCAPED{" + g_escape_i++ + "}@__";
        g_escaped[key] = l + target + r;
        cmd = left + key + right;
    }
    return cmd;
}
function restoreString(cmd) {
    var m;
    while ((m = cmd.match(/__@ESCAPED{\d+}@__/))) {
        var key = cmd.substring(m.index, m.lastIndex);
        cmd = cmd.replace(key, g_escaped[key]);
    }
    return cmd;
}


// 内部のコマンドを実行し、結果へと置き換える
function internalCommand(_cmd) {
    // 文字列を退避
    var cmd = escapeString(_cmd);

    var m, l, r;
    if ((m = cmd.match(/`.+?`/))) {
        l = '`';
        r = '`';
    } else if ((m = cmd.match(/\$\((?!.*\$\().*?\)/))) {
        l = '$(';
        r = ')';
    }

    if (!m) {
        return {
            'cmd': _cmd,
            'exec': false
        };
    }

    // 内部のコマンドを発見
    var left = cmd.substring(0, m.index);
    var target = cmd.substring(m.index + l.length, m.lastIndex - r.length);
    var right = cmd.substring(m.lastIndex);

    left = restoreString(left);
    target = restoreString(target);
    right = restoreString(right);

    var ret = internalCommand(target);

    if (ret.exec) {
        // コマンドのネスト
        cmd = left + l + ret.cmd + r + right;
    } else {
        var ret = util.execSync('cmd /c ' + target);
        var stdout = ret.StdOut.ReadAll().replace(/\r?\n/g, ' ').replace(/ $/, '');
        var stderr = ret.StdErr.ReadAll().replace(/\r?\n/g, ' ').replace(/ $/, '');
        if (ret.ExitCode != 0) {
            PPx.Echo('エラー発生!\n'
                + 'command:' + target + "\n"
                + 'StdOut:' + stdout + "\n"
                + 'StdErr:' + stderr);
            PPx.Quit(-1);
        }
        cmd = left + stdout + right; // 対話モードへの対応はこっち
        //cmd = left + util.execSyncBackground(target)[0] + right; // 対話モードが不要ならこっち
    }

    return {
        'cmd': cmd,
        'exec': true
    };
}


var cmd = PPx.Arguments.item(0);
while (true) {
    // エイリアス/マクロ文字を展開、環境変数はそのまま
    while (true) {
        var exec = false;
        cmd = escapeEnv(cmd); // 環境変数をPPx.Extractの対象にならないように退避して

        if (cmd.match(/%/)) { // エイリアス/マクロ文字を展開
            cmd = PPx.Extract(cmd);
            exec = true;
        }

        cmd = restoreEnv(cmd); // 退避してた環境変数を戻す
        if (!exec) break;
    }

    var ret = internalCommand(cmd);
    cmd = ret.cmd;
    if (!ret.exec) break;
}

PPx.Result = cmd;
entry2vimargs.js
//!script

//
// エントリーをvim用の起動引数へと変換する
//
// 従来のパスのみの他に、行番号付きのgrep検索結果にも対応
// * 行番号付きのgrep検索結果の場合
//   パス:行番号:内容 -> パス +行番号
//

function getPath(str) {
    return str.split(/\:[^\\]/)[0];
}

function getLineNumber(str) {
    var m = str.match(/:\d+:/);
    if (!m) return null;

    return parseInt(str.substring(m.index + 1, m.lastIndex - 1));
}

///

var line_path = (function() {
    var args = PPx.Arguments;
    if (('' + args) == "") {
        // キャンセル
        PPx.Quit(-1);
    }
    var arg = PPx.Arguments.item(0);
    if (arg == "") {
        PPx.Echo('arg is empty');
        PPx.Quit(-1);
    }

    return [getLineNumber(arg), getPath(arg)];
})();

var args = [];
if (line_path[0] != null) args.push('+' + line_path[0]);
args.push(line_path[1]);

PPx.Result = args.join(' ');Gs
findargsparser.js
//! script

//
// find用の引数を展開する
// 独自オプションを使えるようにするヘルパスクリプト
//
// PPx.Arguments.item(0) : 検索オプション
//
// usage)
//   -SEPname "*.cpp;*.h" ->
//     \( -name "*.cpp" -o -name "*.h" \)
//
//   -SEPname "cc:" ->
//     -SEPname "*.H;*.c;*.h" ->
//     \( -name "*.H" -o -name "*.c" -o -name "*.h" \)
//
//   -SEPname "cc:;h:" ->
//     -SEPname "*.H;*.c;*.h;*.h;*.hpp" ->
//     \( -name "*.H" -o -name "*.c" -o -name "*.h" -o -name "*.h" -o -name "*.hpp" \)


function argsJoin(args) {
    if (args.length == 0) return "";
    if (args.length == 1) return args[0];

    return '\\( ' + args.join(' -o ') + ' \\)';
}

///

// 独自オプション内の、ファイルタイプリストを展開する
// NOTE : ripgrepの--type-listを真似ている
function preReplacePattern(pattern) {
    pattern = ';' + pattern; // 先頭をなくし、常に/\Wxxx:/で単語単位検索になるように
    // C/C++
    pattern = pattern.replace(/\Wcpp:/, ';*.C;*.H;*.cc;*.cpp;*.cxx;*.h;*.hh;*.hpp');
    pattern = pattern.replace(/\Wcc:/, ';*.H;*.c;*.h');
    pattern = pattern.replace(/\Wh:/, ';*.h;*.hpp');
    // JavaScript
    pattern = pattern.replace(/\Wjs:/, ';*.js;*.jsx;*.vue');
    // Rails
    pattern = pattern.replace(/\Wrails:/, ';*.erb;*.rb');
    pattern = pattern.replace(/\Wcss:/, ';*.css;*.scss');
    // その他
    // ...

    pattern = pattern.substring(1); // ';'を取り除く
    //PPx.Echo(pattern);

    var m = pattern.match(/\w+:/);
    if (m) {
        PPx.Echo('未実装のファイルパターンがあります\n>' + pattern.substring(m.index, m.lastIndex));
    }

    return pattern;
}

// 独自オプションをオリジナルへ置換する
function sepReplaceInternal(str, sepstr, restr, origname) {
    var m2 = sepstr.match(/"[^\"]*"/);
    if (!m2) return str;

    var pattern = sepstr.substring(m2.index + 1, m2.lastIndex - 1);
    pattern = preReplacePattern(pattern);
    var patterns = pattern.split(';');

    var origs = [];
    for (var i = 0; i < patterns.length; i++) {
        if (patterns[i] != "") origs.push(origname +' "' + patterns[i] + '"');
    }
    return str.replace(new RegExp(restr), argsJoin(origs));
}
function sepReplace(str, sepname, origname) {
    var restr = sepname + ' "[^\"]*"';
    while (true) {
        var m = str.match(new RegExp(restr));
        if (!m) break;

        var sepstr = str.substring(m.index, m.lastIndex);
        str = sepReplaceInternal(str, sepstr, restr, origname);
    }

    return str;
}

///

var result;
var arg = PPx.Arguments.item(0);
if (arg == "") {
    result = "";
} else {
    // 独自オプションの展開
    arg = sepReplace(arg, '-SEPiname', '-iname');
    arg = sepReplace(arg, '-SEPname', '-name');
    //PPx.Echo(arg);

    result = arg;
}

PPx.Result = result;

PPx設定

今回のコマンドはCtrl+Pに割り当てています。

※自身の設定が消えないように、適用前にバックアップを取るか、追加取り込みをしてください
※パスは自身の環境へと合わせて下さい

PPX.CFG
A_exec  = { ; エイリアス
gvim    = E:\tool\editor\vim80-kaoriya-win64\gvim.exe
}

KC_main = { ; PPcメイン窓
^P  ,
    *findcmd
    *script %0\script\entry2file.js, %'ENTRY_FILE'
    *set PECO=cat %'ENTRY_FILE' | nkf -Lu -w | %'CMD' | xargs grep -nH --text -E . | nkf -Lu -w | peco
    *set SELECT=%*script(%0\script\ppxshell.js, `%'PECO'`)
    *set VIM_ARGS=%*script(%0\script\entry2vimargs.js, %'SELECT')
    %Ob %'gvim' %'VIM_ARGS'
}

_Command    = { ; ユーザコマンド・関数
findcmd =
    *set dummy='エントリへのfind'
    *set ENTRY_FILE=%'temp'\entryfile.tmp
    *set SEARCH_LSTFILE=%'temp'\searchlistfile.tmp
    *set dummy='---検索内容の入力-------------'
    *set ARG=%"_findx entry... [オプション](-SEPname:;区切り、i付きで大小無視)"%{-SEPiname "%|*%|"%}
    *set EXTRACT_ARG=%*script(%0\script\findargparser.js, %'ARG')
    *set dummy='---コマンド内容---------------'
    *set OPT=\( -type d \( -name .git -o -name .svn \) -prune \( -wholename .git -wholename .svn \) -o -type f \)
    *set CMD_BAK=xargs -P4 -I'{}' find {} %'OPT' %'EXTRACT_ARG'
    *set CMD=findx - %'OPT' %'EXTRACT_ARG'
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1