タグファイルから補完

提供: MeryWiki
移動先: 案内検索

ctagsの生成したtagsファイルから検索して単語のメニューを出します。

項目を選ぶとその単語が挿入されます。

単語が完全な形の場合、タグジャンプを行います。

kazyさんのctags.exeでtagsファイルを生成、タグジャンプを行うマクロとあわせてお使いください。(単独で使ってもタグジャンプ機能はあります)

インストール[編集]

  1. 下のスクリプトをコピーして、MeryのMy Macroフォルダなどに保存
  2. Mery.exeのあるフォルダに[ctags.exe]を置く
  3. マクロを登録し、Ctrl+→やCtrl+Shift+SpaceやCtrl+Qキーなどにお好みで設定する

使い方[編集]

  1. 補完したい単語にカーソルを置き、マクロを起動する
  2. 初回起動の場合、タグファイルの作成を待つ
  3. すでに単語が完全な場合、メニューを選ぶとその定義にジャンプする
  4. メニューの項目を選ぶと、その単語が挿入される
  5. メニューの最後の「タグファイルを更新する」を選ぶと、タグファイルを作り直すことができる

カスタマイズ[編集]

変数を変えることで、挙動がいくらかカスタマイズできます。

名前 初期設定 効果
TAGS ".merytags" タグファイルの名前
CTAGS "ctags.exe" ctags.exeの名前

相対パスの場合はmery本体のあるディレクトリのものを使います

"c:\\bin\\ctags.exe" のように絶対パスも指定できます

CTAGS_FLAGS "--excmd=number --recurse --fields=+S" ctagsに与える引数

初期設定の意味は左から

  • 位置を行番号で記憶 (タグジャンプに必要)
  • フォルダを再帰的に処理
  • 関数の引数を記憶
WHOLE_WORD true 単語の途中で補完する際の挙動

trueにするとカーソルの置かれた単語全体から補完

falseにするとカーソルより左から補完します

CASE_INSENSITIVE true 単語の検索の挙動

trueにすると検索時大文字と小文字を区別しません

falseにすると区別します

DEFAULT_TAGS_DIR "." タグを作るフォルダ

"." にすると、編集中のファイルのあるフォルダでタグを作ります

".." などにすると、編集中のファイルの一つ上のフォルダからタグを作ります

CONFIRM_CREATE_TAGS true タグファイルがない場合の挙動

trueにするとタグを作成するフォルダを尋ねます

falseにするとフォルダを尋ねずに無言で作成を始めます

更新履歴[編集]

  • r1~r2: 失念
  • r3: 単独でctags呼び出し
  • r4: ホットキー
  • r5: C言語の関数で引数を表示、今開いてるファイル中の単語からも補完、文字の大小を区別しないオプション
  • r6: 確認せずにctagsを起動するオプション
  • r7: 補完候補の単語が1つだけのときうまく補完されないのを修正

ソース[編集]

// タグファイルから補完.js r7 2012-10-24
// http://www.haijin-boys.com/wiki/%E3%82%BF%E3%82%B0%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%8B%E3%82%89%E8%A3%9C%E5%AE%8C
 
// http://hp.vector.co.jp/authors/VA025040/ctags/ の 日本語版ctags 5.8J2 で動作確認しています
 
/** 設定 **/
 
// タグファイルの名前
TAGS = ".merytags"
 
// ctags.exeの名前
// 相対パスの場合はmery本体のあるディレクトリのものを使います
// "c:\\bin\\ctags.exe" のように絶対パスも指定できます
CTAGS = "ctags.exe"
 
// ctagsに与える引数 (初期設定:位置を行番号で記憶(必須)、フォルダを再帰的に処理、関数の引数を記憶)
CTAGS_FLAGS = "--excmd=number --recurse --fields=+S"
 
// 単語の途中で補完する際の挙動
// trueにするとカーソルの置かれた単語全体から補完
// falseにするとカーソルより左から補完します
WHOLE_WORD = true
 
// 単語の検索の挙動
// trueにすると検索時大文字と小文字を区別しません
// falseにすると区別します
CASE_INSENSITIVE = true
 
// タグを作るフォルダ
// "." にすると、編集中のファイルのあるフォルダでタグを作ります
// ".." などにすると、編集中のファイルの一つ上のフォルダからタグを作ります
DEFAULT_TAGS_DIR = "."
 
// タグファイルがない場合の挙動
// trueにするとタグを作成するフォルダを尋ねます
// falseにするとフォルダを尋ねずに無言で作成を始めます
CONFIRM_CREATE_TAGS = false
 
/** 本体 **/
 
var FileSystem = new ActiveXObject("Scripting.FileSystemObject")
var Shell = new ActiveXObject("WScript.Shell")
 
main()
 
function main()
{
  var ctags = makeCtags()
  var tags = makeTags(ctags)
  var query = getQueryWord()
  var result = tags.lookup(query)
  var word = chooseWord(result)
 
  if ("string" === typeof word)
  {
    writeWord(word)
  }
  else if (word === false)
  {
    tags.update()
  }
  else
  {
    jumpToDefinition(word)
  }
}
 
function makeCtags()
{
  return { run: run }
 
  function run(dir)
  {
    var ctags = findCtags()
 
    try
    {
      Shell.CurrentDirectory = dir
      window.Status = 'タグファイルを作成しています お待ちください'
      Shell.Run('"' + ctags + '" -o ' + TAGS + " " + CTAGS_FLAGS, 0, true)
    }
    catch (e)
    {
    }
 
    window.Status = 'タグファイルを作成しました'
    return FileSystem.BuildPath(dir, TAGS)
  }
 
  function findCtags()
  {
    if (FileSystem.FileExists(CTAGS))
    {
      return CTAGS
    }
 
    var dir = FileSystem.GetParentFolderName(editor.FullName)
    var ctags = FileSystem.BuildPath(dir, CTAGS)
 
    if (FileSystem.FileExists(ctags))
    {
      return ctags
    }
 
    throw new Error("ctags.exeが見つかりませんでした\nMery本体のディレクトリに置くか、絶対パスを設定してください")
  }
}
 
function makeTags(ctags)
{
  var fullname = findTagfile()
  var text = loadTagfile()
 
  return { lookup: lookup, update: update }
 
  function getPath()
  {
    return FileSystem.GetParentFolderName(fullname)
  }
 
  function lookup(word)
  {
    if (/^\s*$/.test(word)) { return }
 
    word = reEscape(word)
 
    var opts = CASE_INSENSITIVE ? "igm" : "gm"
    var re = new RegExp("^" + word + ".*$", opts)
    var lines = text.match(re)
 
    if (!lines) { return [] }
 
    return calcWords().concat(calcDefinitions(word))
 
    function calcWords()
    {
      var words = new Array(lines.length)
 
      words[0] = lines[0].substring(0, lines[0].indexOf("\t"))
 
      if (words.length === 1)
      {
        return words[0] === word ? [] : words
      }
 
      var n = 0
      for (var i = 1; i < lines.length; i++)
      {
        var w = lines[i].substring(0, lines[i].indexOf("\t"))
 
        if (words[n] !== w)
        {
          n++
          words[n] = w
        }
      }
 
      words.length = n + 1
 
      words.sort(function(a, b)
      {
        return a.localeCompare(b)
      })
 
      return words
    }
 
    function calcDefinitions(word)
    {
      var defs = new Array(lines.length)
      var n = 0
 
      for (var i = 0; i < lines.length; i++)
      {
        var line = lines[i]
        var tab1 = line.indexOf("\t")
 
        if (tab1 !== word.length) { continue }
 
        var tab2 = line.indexOf("\t", tab1 + 1)
        var endnum = line.indexOf(";", tab2 + 1)
        var sig = line.indexOf("signature:", endnum + 1)
 
        var name = line.substring(0, tab1)
        var filename = line.substring(tab1 + 1, tab2)
        var linenum = line.substring(tab2 + 1, endnum)
        var signature = sig < 0 ? "" : line.substring(sig + "signature:".length)
 
        var fullname = FileSystem.BuildPath(getPath(), filename)
 
        defs[n] = [fullname, linenum, filename, name + signature]
        defs[n].toString = joinme
        n++
      }
 
      defs.length = n
 
      return defs
    }
 
    function joinme()
    {
      return this[3] + " (" + this[2] + " : " + this[1] + ")"
    }
  }
 
  function reEscape(w)
  {
    return w.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
  }
 
  function loadTagfile()
  {
    if (!fullname)
    {
      fullname = makeTagfile()
    }
 
    return slurp(fullname)
  }
 
  function findTagfile()
  {
    var dir = document.Path
 
    while (FileSystem.FolderExists(dir))
    {
      var file = FileSystem.BuildPath(dir, TAGS)
      if (FileSystem.FileExists(file)) { return file }
      dir = FileSystem.GetParentFolderName(dir)
    }
  }
 
  function makeTagfile()
  {
    var dir = FileSystem.BuildPath(document.Path, DEFAULT_TAGS_DIR)
 
    if (CONFIRM_CREATE_TAGS)
    {
      dir = window.Prompt("タグファイル作成フォルダ", dir)
    }
 
    if (!dir)
    {
      window.Quit()
    }
 
    return ctags.run(dir)
  }
 
  function update()
  {
    return ctags.run(getPath())
  }
 
  function slurp(filename)
  {
    var f = FileSystem.OpenTextFile(filename)
    var text = f.ReadAll()
    f.Close()
    return text
  }
}
 
function getQueryWord()
{
  var s = document.selection
 
  if (!s.IsEmpty) { return s.Text }
 
  if (WHOLE_WORD)
  {
    s.SelectWord()
 
    // hoge|(); → hogeFuga|(); のように補完 
    if (s.Text.length == 1 && !/\w/.test(s.Text))
    {
      s.Collapse(meCollapseStart)
      s.WordLeft(true)
    }
  }
  else
  {
    s.WordLeft(true)
  }
 
  if (/\A\s+\z/.test(s.Text))
  {
    s.Collapse(meCollapseEnd)
  }
 
  return s.Text
}
 
function chooseWord(words)
{
  if (words && words.length === 1 && typeof words[0] === "string")
  {
    return words[0]
  }
 
  var menu = makeMenu()
  var i = menu.Track(0)
 
  return i === -1 ? false : words && words[i - 1]
 
  function makeMenu()
  {
    var menu = window.CreatePopupMenu()
    initMenu(menu)
    menu.Add("タグファイルを更新する", -1)
    return menu
  }
 
  function initMenu(menu)
  {
    if (!words || words.length == 0) { return }
 
    var n = 0
    function addHotkey(word)
    {
      if (n > 34) { return word }
      n++
      return "&" + n.toString(36) + ". " + word
    }
 
    for (var i = 0; i < words.length; i++)
    {
      menu.Add(addHotkey(words[i]), i + 1)
    }
 
    menu.Add(undefined, 0, meMenuSeparator)
  }
}
 
function jumpToDefinition(def)
{
  if (!def) { return }
  window.Status = def + "にジャンプします"
  var file = def[0]
  var line = def[1]
 
  editor.OpenFile(file, 0, meOpenAllowNewWindow)
  editor.ActiveDocument.selection.SetActivePoint(mePosLogical, 1, line, false)
  editor.ActiveDocument.Activate()
}
 
function writeWord(word)
{
  if (word)
  {
    document.selection.Text = word
  }
}