IDE 사용 중에는 소스코드의 함수호출 부분에 ctrl+클릭하면, 

자동으로 해당 함수가 정의된 파일을 열어 함수정의부분이 시작되는 라인에 커서를 위치시켜주는 기능이 상당히 편함.

이걸 에디트플러스에서 구현해보고 싶었음.

덧붙여서 ctags로는 할 수 없는, factory method 등이 난무하는 거대규모의 복잡다단한 프로젝트용임을 밝힘.


대략적인 시나리오는 아래와 같음.

1. 에디트플러스에서 사용자도구로 등록된 스크립트 실행 (인수: 파일경로, 라인번호, 커서위치)

2. 스크립트에서 토큰을 추출하여 함수정의부분을 찾아 다시 에디트플러스를 실행.


2번과정에서 포인트는 아래와 같음.

* 에디트플러스는 백그라운드로 실행되어야 함. 그렇지 않으면 에디트플러스가 종료될 때까지 스크립트가 대기상태.

* 이미 실행되어 있는 에디트플러스 창에서 실행되어야 함. 이건 -e 옵션으로가능.

* 에디트플러스 실행시 파일경로, 라인번호 지정가능 해야 함. (OK)



에디트플러스 실행파일의 경로는 C:\Program Files (x86)\EditPlus 3\editplus.exe 이고,

php.exe의 경로는 C:\php\bin\php.exe 이고,

스크립트 파일의 경로는 C:\script\jump_to.php 라고 가정함.


에디트플러스 사용자 도구 설정.

1. 도구 > 사용자 도구구성 클릭.

2. 아래와 같은 형태로 사용자 도구 추가

명령: "C:\php\bin\php.exe" "C:\script\jump_to.php"

인수: "$(FilePath)" $(CurLine) $(CurCol)

'안보이게 실행' 에 체크


개인적으로 추가 단축키로 Alt+방향키UP 을 등록해놓고 사용중.



아래는 위에서 jump_to.php 로 가정한 스크립트 소스.

일단 하나의 파일에 함수호출/정의가 전부 있을 경우엔 아무런 수정없이 바로 동작함.


class method나 autoload 등을 프로젝트에 맞게 구성하여 토큰 추출부분과 위치추출 부분을 수정하면 얼마든지 확장하여 사용이 가능함.


// 파일경로
$filePath  = $argv[1];
// 라인번호
$lineNo    = (int)$argv[2];
// 커서위치
$cursorPos = (int)$argv[3];

if (file_exists($filePath) === false) {
    printf("[ERROR] file does not exists\n%s\n", $filePath);
    exit;
}

// 라인 추출
$line = getLine($filePath, $lineNo);
if (!$line) exit;

// 커서위치의 키워드 추출
$keyword = getKeyword($line, $cursorPos);
if (!$keyword) exit;

// 함수위치 추출
$funcPos = getFuncPos($filePath, $keyword);
if (!$funcPos) exit;

// 에디트플러스 실행
runEditPlus($funcPos['file'], $funcPos['line']);



// 지정 파일의 지정라인 추출
function getLine($filePath, $lineNo) 
{
    if (file_exists($filePath) === false) return false;

    $fp = fopen($filePath, 'r');
    for ($i=1; $i<$lineNo; $i++) {
        $line = fgets($fp);
    }
    $line = rtrim(fgets($fp));
    fclose($fp);

    return $line;
}

// 지정된 라인의 커서위치의 키워드 추출
// 현재는 함수명 한 단어만 처리하는 형태
function getKeyword($line, $cursorPos) 
{
    $len = 0;
    $tokenArr = token_get_all("<?php\n".$line);
    foreach ($tokenArr as $v) {
        if ($v[0] === T_OPEN_TAG) continue;

        $cntToken = count($v);

        if ($cntToken === 1) $len += strlen($v[0]);
        else $len += strlen($v[1]);

        if ($len >= $cursorPos) {
            if ($cntToken === 1) return $v[0];
            else return $v[1];
        }
    }

    return false;
}

// 파일경로를 뒤져서 함수정의 위치 추출
// include_path를 미리 지정하면 재귀호출을 이용해 계속 뒤지는 형태
function getFuncPos($filePath, $funcName) 
{
    if (file_exists($filePath) === false) return false;

    $regex = "/function\s+".$funcName."\s*\(/";

    $lineNo = 0;
    $lineFunc = -1;
    $fp = fopen($filePath, 'r');
    while (($line = fgets($fp)) !== false) {
        ++$lineNo;

        $line = rtrim($line);
        if (!$line) continue;

        preg_match($regex, $line, $match);
        if (count($match) < 1) continue;

        $lineFunc = $lineNo;
        break;
    }
    fclose($fp);

    // 못 찾았음. include_path 등 다른 곳을 뒤지자.
    if ($lineFunc === -1) {
        // 다음 파일 가져오기
        $filePath = getNextFile($filePath);
        if (!$filePath) return false;

        return getFuncPos($filePath, $funcName);
    }
    // 찾았음.
    else {
        return array(
            'file' => $filePath,
            'line' => $lineNo
        );
    }
}

// include_path의 파일리스트의 다음 파일을 가져오는 pseudo code
// 여길 각 프로젝트에 맞게 잘 구성하면 됨.
function getNextFile($filePath) 
{
    $includePath = array(
        'C:\project\lib\lib.php',
        'C:\project\lib\functions.php'
        // ... ...
    );

    if (!in_array($filePath, $includePath)) $k = 0;
    else $k = array_search($filePath, $includePath) + 1;

    if ($k >= count($includePath)) return false;

    return $includePath[$k];
}

// 파일경로와 라인번호를 넘겨 에디트플러스 실행
function runEditPlus($filePath, $lineNo) 
{
    $editplusPath = 'C:\Program Files (x86)\EditPlus 3\editplus.exe';
    $arg = ' & "'.$filePath.' -cursor '.$lineNo.':0 -e"';

    // 백그라운드 실행용 vbs
    $vbsScript = <<<EOS
Set WshShell = CreateObject("WScript.shell")
WshShell.Run """___CMD___""" ___ARG___, 0, false
Set WshShell = Nothing
EOS;

    $vbsScript = str_replace('___CMD___', $editplusPath, $vbsScript);
    $vbsScript = str_replace('___ARG___', $arg, $vbsScript);

    $vbsFilePath = sys_get_temp_dir().'\\'.uniqid().'.vbs';
    file_put_contents($vbsFilePath, $vbsScript);

    exec($vbsFilePath);
    unlink($vbsFilePath);
}








Posted by bloodguy
,