├── src ├── work │ └── .gitignore ├── test │ ├── inc3.h │ ├── stl.cpp │ ├── inc2.cpp │ ├── module │ │ ├── mod2.h │ │ ├── mod1.h │ │ ├── sub │ │ │ └── mod2.cpp │ │ └── mod1.cpp │ ├── sub.h │ ├── inc3.cpp │ ├── field.h │ ├── incFuncTest.c │ ├── split2.cpp │ ├── comp17.cpp │ ├── inctest.cpp │ ├── inc │ │ └── inctest1.h │ ├── size.cpp │ ├── stdinc.cpp │ ├── simple.cpp │ ├── comp17-2.cpp │ ├── comp13.cpp │ ├── comp14.cpp │ ├── comp15.cpp │ ├── enum3.cpp │ ├── enum4.cpp │ ├── unnecessaryInc.c │ ├── inc1.h │ ├── inc2.h │ ├── sub2.cpp │ ├── sub.cpp │ ├── comp11.cpp │ ├── comp19.cpp │ ├── enum2.cpp │ ├── comp12.cpp │ ├── comp16.cpp │ ├── inc1.cpp │ ├── class4.cpp │ ├── comp18.cpp │ ├── class2.cpp │ ├── enum.cpp │ ├── class3.cpp │ ├── operator.cpp │ ├── class.h │ ├── ns.cpp │ ├── sample.c │ ├── class5.cpp │ ├── comp.cpp │ ├── comp-2.cpp │ ├── comp2.cpp │ ├── macro.cpp │ ├── comp4.cpp │ ├── comp5.cpp │ ├── comp3.cpp │ ├── indirect.cpp │ ├── highlight.cpp │ ├── hoge.h │ ├── comp6.cpp │ ├── comp7.cpp │ ├── comp8.cpp │ ├── comp9.cpp │ ├── comp10.cpp │ ├── stack.cpp │ ├── class.cpp │ ├── split.cpp │ └── hoge.cpp ├── swig │ ├── wrap.c │ ├── .gitignore │ └── test.c ├── libclanglua │ └── .gitignore ├── graph.sample │ └── inc.png ├── lctags │ ├── lsqlite3.dummy.c │ ├── testMQueueGet.lua │ ├── testMQueuePut.lua │ ├── lctags │ ├── testHelper.lua │ ├── testLock.lua │ ├── idMap.lua │ ├── TermCtrl.lua │ ├── testMQueue.lua │ ├── Scan.lua │ ├── testServer.lua │ ├── testInc.lua │ ├── makefile │ ├── _lctags.conf │ ├── testOperator.lua │ ├── testJson.lua │ ├── testServerRequest.lua │ ├── testDB.lua │ ├── gcc.lua │ ├── armcc.lua │ ├── config.lua │ ├── LogCtrl.lua │ ├── StatusServer.lua │ ├── Server.lua │ ├── OutputCtrl.lua │ ├── Writer.lua │ └── Json.lua ├── .gitignore ├── sample.lua ├── start.lua ├── lisp │ ├── html │ │ ├── file.html │ │ ├── index.html │ │ ├── module-file-graph.html │ │ ├── func-call-graph.html │ │ ├── module-dir-graph.html │ │ └── file-list.html │ ├── lctags-anything.el │ ├── lctags-conf.el │ ├── lctags-test.el │ ├── lctags_httpd_var.py │ ├── lctags-rename.el │ ├── lctags-const.el │ ├── lctags-insert-func.el │ ├── lctags-split.el │ ├── httpd.py │ ├── lctags-dispatch.el │ ├── lctags-highlight.el │ └── lctags-servlet.el ├── lctags.conf └── test.lua ├── external ├── clang │ └── r390 │ │ └── .gitignore ├── luasqlite3 │ └── .gitignore └── .gitignore ├── doc ├── callgraph.png ├── module.graph.curl.png ├── rename.org ├── highlight.org ├── varSize.org ├── register.org ├── modulegraph.org ├── windows.org ├── callFunc.org ├── enum.org ├── callgraph.org └── subroutine.org ├── makefile ├── docker ├── ubuntu20.04 │ ├── lctags.sh │ ├── docker-compose.yml │ └── Dockerfile └── ubuntu22.04 │ ├── lctags.sh │ ├── docker-compose.yml │ └── Dockerfile ├── .gitignore └── LICENSE /src/work/.gitignore: -------------------------------------------------------------------------------- 1 | trunk -------------------------------------------------------------------------------- /src/test/inc3.h: -------------------------------------------------------------------------------- 1 | extern int inc3; 2 | -------------------------------------------------------------------------------- /src/test/stl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/test/inc2.cpp: -------------------------------------------------------------------------------- 1 | #include "inc1.h" 2 | -------------------------------------------------------------------------------- /src/swig/wrap.c: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /external/clang/r390/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | llvm 3 | -------------------------------------------------------------------------------- /src/swig/.gitignore: -------------------------------------------------------------------------------- 1 | *_wrap.c* 2 | libClangLua.i 3 | -------------------------------------------------------------------------------- /src/test/module/mod2.h: -------------------------------------------------------------------------------- 1 | extern void mod2_func(); 2 | -------------------------------------------------------------------------------- /src/swig/test.c: -------------------------------------------------------------------------------- 1 | #include "libClangLua_wrap.c" 2 | 3 | -------------------------------------------------------------------------------- /src/test/module/mod1.h: -------------------------------------------------------------------------------- 1 | extern void mod1_sub(); 2 | 3 | -------------------------------------------------------------------------------- /src/libclanglua/.gitignore: -------------------------------------------------------------------------------- 1 | core.so 2 | if.lua 3 | ifc.lua 4 | -------------------------------------------------------------------------------- /src/test/sub.h: -------------------------------------------------------------------------------- 1 | struct ddddd { 2 | int value; 3 | }; 4 | -------------------------------------------------------------------------------- /src/test/inc3.cpp: -------------------------------------------------------------------------------- 1 | namespace INC1 { 2 | #include "inc2.h" 3 | } 4 | -------------------------------------------------------------------------------- /external/luasqlite3/.gitignore: -------------------------------------------------------------------------------- 1 | lsqlite3_fsl09x 2 | lsqlite3_fsl09x.zip 3 | -------------------------------------------------------------------------------- /doc/callgraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifritJP/lctags/HEAD/doc/callgraph.png -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: 2 | (cd src; $(MAKE) $(MAKECMDGOALS)) 3 | 4 | $(MAKECMDGOALS): all 5 | -------------------------------------------------------------------------------- /src/test/field.h: -------------------------------------------------------------------------------- 1 | #ifndef FIELD 2 | int bbbb; 3 | #else 4 | int bbbb2; 5 | #endif 6 | -------------------------------------------------------------------------------- /src/test/incFuncTest.c: -------------------------------------------------------------------------------- 1 | //#include 2 | int main() { 3 | strl 4 | } 5 | -------------------------------------------------------------------------------- /external/.gitignore: -------------------------------------------------------------------------------- 1 | lua-* 2 | lua_redis 3 | luasql-firebird 4 | redis-3.2.8 5 | Firebird-* 6 | h2 -------------------------------------------------------------------------------- /src/graph.sample/inc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifritJP/lctags/HEAD/src/graph.sample/inc.png -------------------------------------------------------------------------------- /doc/module.graph.curl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifritJP/lctags/HEAD/doc/module.graph.curl.png -------------------------------------------------------------------------------- /src/test/split2.cpp: -------------------------------------------------------------------------------- 1 | int func() 2 | { 3 | char val; 4 | { 5 | val = 1; 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/comp17.cpp: -------------------------------------------------------------------------------- 1 | struct DATA { 2 | int val; 3 | }; 4 | 5 | void sub( DATA * pData ) 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /src/test/inctest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void sub( void ) 4 | { 5 | int VAL = 1; 6 | } 7 | -------------------------------------------------------------------------------- /src/test/module/sub/mod2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void mod2_func() 4 | { 5 | mod1_sub(); 6 | } 7 | -------------------------------------------------------------------------------- /src/test/inc/inctest1.h: -------------------------------------------------------------------------------- 1 | ======= 2 | #include 3 | 4 | STRUCT2( CC ); 5 | 6 | #define VAL val 7 | 8 | -------------------------------------------------------------------------------- /src/test/size.cpp: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | int val1; 3 | int val2[10]; 4 | } VAL_t; 5 | 6 | VAL_t val; 7 | -------------------------------------------------------------------------------- /src/test/stdinc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static void sub( void ) 4 | { 5 | void * pVal = NULL; 6 | } 7 | -------------------------------------------------------------------------------- /src/test/simple.cpp: -------------------------------------------------------------------------------- 1 | //#include 2 | int main() { 3 | const char * pTxt = "hello\n"; 4 | //printf 5 | } 6 | -------------------------------------------------------------------------------- /src/test/comp17-2.cpp: -------------------------------------------------------------------------------- 1 | struct DATA { 2 | int val; 3 | }; 4 | 5 | void sub( DATA * pData ) 6 | { 7 | pData-> 8 | } 9 | -------------------------------------------------------------------------------- /docker/ubuntu20.04/lctags.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | DIR=$(pwd) 4 | 5 | docker exec -i lctags_env bash -c "cd ${DIR}; lctags $@" 6 | -------------------------------------------------------------------------------- /docker/ubuntu22.04/lctags.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | DIR=$(pwd) 4 | 5 | docker exec -i lctags_env bash -c "cd ${DIR}; lctags $@" 6 | -------------------------------------------------------------------------------- /src/test/comp13.cpp: -------------------------------------------------------------------------------- 1 | class CCCC { 2 | CCCC * func( ) { 3 | m_val->func(); 4 | func()-> 5 | } 6 | 7 | CCCC * m_val; 8 | }; 9 | -------------------------------------------------------------------------------- /src/test/comp14.cpp: -------------------------------------------------------------------------------- 1 | class CCCC { 2 | CCCC * func( ) { 3 | m_val->func(); 4 | func()-> 5 | } 6 | 7 | CCCC * m_val; 8 | }; 9 | -------------------------------------------------------------------------------- /src/test/comp15.cpp: -------------------------------------------------------------------------------- 1 | class CCCC { 2 | CCCC * func( ) { 3 | m_val->func(); 4 | if ( func()-> 5 | } 6 | 7 | CCCC * m_val; 8 | }; 9 | -------------------------------------------------------------------------------- /src/test/enum3.cpp: -------------------------------------------------------------------------------- 1 | typedef enum { 2 | eval_val1, 3 | eval_val2, 4 | } eval_t; 5 | static eval_t sub( void ) 6 | { 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /src/test/enum4.cpp: -------------------------------------------------------------------------------- 1 | typedef enum { 2 | eval_val1, 3 | eval_val2, 4 | } eval_t; 5 | static void display( void ) 6 | { 7 | eval_t 8 | } 9 | -------------------------------------------------------------------------------- /src/test/module/mod1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void mod1_func() 4 | { 5 | mod2_func(); 6 | } 7 | 8 | void mod1_sub() 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/test/unnecessaryInc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void func() 6 | { 7 | printf( "" ); 8 | } 9 | -------------------------------------------------------------------------------- /src/lctags/lsqlite3.dummy.c: -------------------------------------------------------------------------------- 1 | typedef void * sqlite3; 2 | const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName) 3 | { 4 | return ""; 5 | } 6 | -------------------------------------------------------------------------------- /src/lctags/testMQueueGet.lua: -------------------------------------------------------------------------------- 1 | local Helper = require( 'lctags.Helper' ) 2 | 3 | local mqueue = Helper.createMQueue( "test" ) 4 | 5 | print( mqueue:get() ) 6 | -------------------------------------------------------------------------------- /src/test/inc1.h: -------------------------------------------------------------------------------- 1 | #ifndef __INC1__ 2 | #define __INC1__ 3 | 4 | extern int inc1; 5 | 6 | namespace INC1 { 7 | #include "inc2.h" 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/test/inc2.h: -------------------------------------------------------------------------------- 1 | #ifndef __INC2__ 2 | #define __INC2__ 3 | 4 | extern int inc2; 5 | 6 | namespace INC2 { 7 | #include "inc3.h" 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/lctags/testMQueuePut.lua: -------------------------------------------------------------------------------- 1 | local Helper = require( 'lctags.Helper' ) 2 | 3 | 4 | local mqueue = Helper.createMQueue( "test" ) 5 | 6 | mqueue:put( "123", true ) 7 | 8 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.o 3 | *.pch 4 | *.log 5 | *.sqlite3 6 | sql 7 | prof 8 | profi.* 9 | .lctags 10 | graph.sample 11 | test.sqlite3-journal 12 | cytoscape.js 13 | -------------------------------------------------------------------------------- /src/test/sub2.cpp: -------------------------------------------------------------------------------- 1 | #define NNNN 2 | #include <./hoge.h> 3 | 4 | #ifdef VVVVVVV 5 | int vvvvv2; 6 | #endif 7 | 8 | namespace jjjjj2 { 9 | #include 10 | } 11 | -------------------------------------------------------------------------------- /src/test/sub.cpp: -------------------------------------------------------------------------------- 1 | #define NNNN 2 | #include <./hoge.h> 3 | 4 | #ifdef VVVVVVV 5 | int vvvvv; 6 | #endif 7 | 8 | namespace jjjjj { 9 | #include 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/test/comp11.cpp: -------------------------------------------------------------------------------- 1 | #define ABC(X) \ 2 | void func##X( void ) { \ 3 | ; \ 4 | } 5 | 6 | ABC( 123 ); 7 | 8 | static void func( void ) 9 | { 10 | f 11 | } 12 | -------------------------------------------------------------------------------- /src/test/comp19.cpp: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | int val; 3 | } TEST; 4 | void sub( int val ) 5 | { 6 | } 7 | void func( TEST * pTest ) 8 | { 9 | if ( sub( pTest-> 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/enum2.cpp: -------------------------------------------------------------------------------- 1 | typedef enum { 2 | eval_val1, 3 | eval_val2, 4 | } eval_t; 5 | extern eval_t func(); 6 | static void sub( eval_t val ) 7 | { 8 | if ( func() == 9 | } 10 | -------------------------------------------------------------------------------- /src/test/comp12.cpp: -------------------------------------------------------------------------------- 1 | struct hoge { 2 | int val1; 3 | int val2; 4 | }; 5 | 6 | typedef struct hoge hoge_t; 7 | 8 | static void func( void ) 9 | { 10 | hoge_t aaa; 11 | aaa. 12 | } 13 | -------------------------------------------------------------------------------- /src/test/comp16.cpp: -------------------------------------------------------------------------------- 1 | #define METHOD() typedef void (func)( int val ); 2 | 3 | METHOD(); 4 | 5 | struct DATA { 6 | func * pFunc; 7 | }; 8 | 9 | void sub( DATA * pData ) 10 | { 11 | pData-> 12 | } 13 | -------------------------------------------------------------------------------- /src/test/inc1.cpp: -------------------------------------------------------------------------------- 1 | namespace INC1 { 2 | #include "inc2.h" 3 | } 4 | #include "inc1.h" 5 | #include "inc1.h" 6 | 7 | namespace ns4 { 8 | #include "field.h" 9 | #define FIELD 10 | #include "field.h" 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src.tar.gz 2 | \#* 3 | test-result 4 | test-result2 5 | doc/*.html 6 | src/a.out 7 | src/db.dump 8 | src/dump 9 | src/dump.c 10 | src/dump.update 11 | src/lctags/Helper 12 | src/lctags/Helper 13 | src/lctags/*.py -------------------------------------------------------------------------------- /src/test/class4.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class VVVV { 4 | int eeee; 5 | }; 6 | 7 | static void func() 8 | { 9 | std::function sub = [=](int a){ 10 | VVVV val; 11 | val. 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/comp18.cpp: -------------------------------------------------------------------------------- 1 | typedef void sub_t(void); 2 | typedef void (sub2_t)(void); 3 | typedef struct { 4 | sub_t * pSub; 5 | sub2_t * pSub2; 6 | } TEST; 7 | void func( TEST * pTest ) 8 | { 9 | pTest-> 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/class2.cpp: -------------------------------------------------------------------------------- 1 | class Super1 { 2 | int val1; 3 | }; 4 | 5 | class Super2 { 6 | int val2; 7 | }; 8 | 9 | class Sub : public Super1, Super2 { 10 | public: 11 | int val3; 12 | void func() { 13 | this-> 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/lctags/lctags: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # -*- mode: sh; coding:utf-8 -*- 3 | 4 | LUA_CMD=lua5.3 5 | DIR=$(dirname $0) 6 | #LUA_CMD=${DIR}/../clanglua 7 | 8 | LUA_DIR_SCRIPT=${DIR} 9 | 10 | ${LUA_CMD} ${LUA_DIR_SCRIPT}/lctags.lua $@ || exit 1 11 | -------------------------------------------------------------------------------- /src/lctags/testHelper.lua: -------------------------------------------------------------------------------- 1 | local Helper = require( 'lctags.Helper' ) 2 | 3 | print( Helper ) 4 | 5 | print( Helper.getTime() ) 6 | Helper.msleep( 100 ) 7 | print( Helper.getTime() ) 8 | Helper.msleep( 100 ) 9 | print( Helper.getTime() ) 10 | -------------------------------------------------------------------------------- /src/test/enum.cpp: -------------------------------------------------------------------------------- 1 | typedef enum { 2 | eval_val1, 3 | eval_val2, 4 | } eval_t; 5 | extern eval_t func(); 6 | static void sub( eval_t val ) 7 | { 8 | switch ( func() ) { 9 | case eval_val1: 10 | break; 11 | case 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/sample.lua: -------------------------------------------------------------------------------- 1 | local list = { { val = 1 }, { val = 4 }, { val = 2 } } 2 | 3 | table.sort( list, 4 | function( item1, item2 ) 5 | return item1.val > item2.val 6 | end 7 | ) 8 | 9 | for index, val in pairs( list ) do 10 | print( index, val.val ) 11 | end 12 | -------------------------------------------------------------------------------- /src/lctags/testLock.lua: -------------------------------------------------------------------------------- 1 | local Helper = require( 'lctags.Helper' ) 2 | 3 | local lock = Helper.createLock( "act" ) 4 | 5 | print( lock ) 6 | 7 | print( lock:isLocking() ) 8 | 9 | lock:begin() 10 | 11 | print( lock:isLocking() ) 12 | 13 | Helper.msleep( 1000 * 5 ) 14 | 15 | lock:fin() 16 | -------------------------------------------------------------------------------- /src/lctags/idMap.lua: -------------------------------------------------------------------------------- 1 | local idMap = {} 2 | local cursorKind2NameMap = {} 3 | local clang = require( 'libclanglua.if' ) 4 | 5 | idMap.cursorKind2NameMap = cursorKind2NameMap 6 | 7 | for key, info in pairs( clang.CXCursorKind ) do 8 | cursorKind2NameMap[ info.val ] = key 9 | end 10 | 11 | return idMap 12 | -------------------------------------------------------------------------------- /src/test/class3.cpp: -------------------------------------------------------------------------------- 1 | 2 | class Super1 { 3 | int val1; 4 | }; 5 | 6 | template 7 | class Super2 { 8 | T val2; 9 | }; 10 | 11 | template 12 | class Sub : public Super1, Super2 { 13 | public: 14 | T val3; 15 | Super2 func( T val ) { 16 | this-> 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/operator.cpp: -------------------------------------------------------------------------------- 1 | static void func( int val ) 2 | { 3 | val=1+2-3*4/5^6|7&8||9&&10; 4 | val+=100; 5 | val-=200; 6 | val*=300; 7 | val/=400; 8 | val^=500; 9 | val|=600; 10 | val&=700; 11 | val++; 12 | val--; 13 | ++val; 14 | --val; 15 | *(&val); 16 | } 17 | -------------------------------------------------------------------------------- /src/test/class.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | -------------------------------------------------------------------------------- /src/test/ns.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace ns1 { 4 | int val1 = 1; 5 | int val2 = 2; 6 | namespace ns2 { 7 | int val2 = -2; 8 | namespace ns3 { 9 | int val3 = 3; 10 | } 11 | void sub() { 12 | printf( "%d, %d, %d, %d\n", val1, val2, ns1::val2, ns3::val3 ); 13 | } 14 | } 15 | } 16 | 17 | main() 18 | { 19 | ns1::ns2::sub(); 20 | } 21 | -------------------------------------------------------------------------------- /src/test/sample.c: -------------------------------------------------------------------------------- 1 | typedef enum { 2 | enum_val0, 3 | enum_val1, 4 | enum_val2, 5 | } enum_t; 6 | 7 | const char * convert( enum_t val ) 8 | { 9 | switch (val) { 10 | case enum_val0: 11 | return "enum_val0"; 12 | case enum_val1: 13 | return "enum_val1"; 14 | case enum_val2: 15 | return "enum_val2"; 16 | default: 17 | return NULL; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/test/class5.cpp: -------------------------------------------------------------------------------- 1 | template class TEMP2 2 | { 3 | typedef T1 tmp_t; 4 | public: 5 | const tmp_t s_val; 6 | 7 | TEMP2() : s_val( 0 ) 8 | {} 9 | 10 | T2 aaaaaa(const T1 &t) const 11 | { 12 | return t; 13 | } 14 | 15 | T2 bbbbbb(const T1 &t) const 16 | { 17 | return aaaaaa( t); 18 | } 19 | }; 20 | 21 | static void func() 22 | { 23 | TEMP2 tmp; 24 | tmp. 25 | } 26 | -------------------------------------------------------------------------------- /src/test/comp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class Test1 4 | { 5 | int member; 6 | public: 7 | Test1 * pTest; 8 | static int sub() { 9 | return 1; 10 | } 11 | int func() { 12 | return 0; 13 | } 14 | }; 15 | 16 | void zzzz( int val1, int val2 ) 17 | { 18 | } 19 | 20 | int main() 21 | { 22 | Test1 * pTest1; /** */ 23 | 24 | Test1::sub(); 25 | 26 | zzzz( pTest1->me 27 | // return 0; 28 | // } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/test/comp-2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | class Test2; 3 | class Test1 4 | { 5 | int member; 6 | public: 7 | Test2 * pTest; 8 | static int sub() { 9 | return 1; 10 | } 11 | int func() { 12 | return 0; 13 | } 14 | }; 15 | class Test2 16 | { 17 | Test1 * pTest; 18 | }; 19 | 20 | int main() 21 | { 22 | Test1 * pTest1; /** */ 23 | 24 | Test1::sub(); 25 | pTest1->pTest-> 26 | // return 0; 27 | // } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/lctags/TermCtrl.lua: -------------------------------------------------------------------------------- 1 | local TermCtrl = {} 2 | 3 | local escp = "\x1b[" 4 | 5 | function printEscpCode( val, code ) 6 | io.stdout:write( escp .. tostring( val ) .. code ) 7 | end 8 | 9 | function TermCtrl:clr() 10 | printEscpCode( 2, "J" ) 11 | end 12 | 13 | function TermCtrl:clrLine() 14 | printEscpCode( 2, "K" ) 15 | end 16 | 17 | function TermCtrl:gotoAt( x, y ) 18 | printEscpCode( y, ";" .. tostring( x ) .. "H" ) 19 | end 20 | 21 | return TermCtrl 22 | -------------------------------------------------------------------------------- /src/test/comp2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class Test1 4 | { 5 | int member; 6 | public: 7 | static int sub() 8 | { 9 | return 1; 10 | } 11 | int func() { 12 | return 0; 13 | } 14 | }; 15 | 16 | void zzzz( int val1, int val2 ) 17 | { 18 | } 19 | 20 | Test1 * hoge() 21 | { 22 | return NULL; 23 | } 24 | 25 | int main() 26 | { 27 | Test1 * pTest1; /** */ 28 | 29 | Test1::sub(); 30 | 31 | hoge()->f 32 | // return 0; 33 | // } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/start.lua: -------------------------------------------------------------------------------- 1 | local command = string.format( 2 | 'python -c "import os; print( os.path.dirname( os.path.abspath( \'%s\' )));"', arg[0] ) 3 | local obj = io.popen( command ) 4 | local dir = obj:read( '*l' ) 5 | 6 | package.path = string.format( "%s/?.lua;%s", dir, package.path ) 7 | package.cpath = string.format( "%s/?.so;%s", dir, package.cpath ) 8 | 9 | local func, mess = loadfile( dir .. "/lctags/lctags.lua" ) 10 | if mess then 11 | print( mess ) 12 | end 13 | if func then 14 | func() 15 | end 16 | -------------------------------------------------------------------------------- /src/test/macro.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define aaa 123 4 | 5 | #define DECL( AAA ) \ 6 | int val##AAA; \ 7 | int VAL##AAA; \ 8 | 9 | 10 | typedef struct { 11 | DECL( aaa ); 12 | DECL( 2 ); 13 | DECL( 3 ); 14 | DECL( 4 ); 15 | DECL( 5 ); 16 | DECL( 6 ); 17 | } VAL; 18 | 19 | 20 | void macro_func() 21 | { 22 | } 23 | 24 | void macro_func2() 25 | { 26 | } 27 | 28 | #define macro_func() macro_func2() 29 | 30 | 31 | void macro_func3() 32 | { 33 | macro_func(); 34 | } 35 | -------------------------------------------------------------------------------- /src/test/comp4.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class Test1 4 | { 5 | int member; 6 | public: 7 | static int sub() 8 | { 9 | return 1; 10 | } 11 | int func() { 12 | return 0; 13 | } 14 | }; 15 | 16 | void zzzz( int val1, int val2 ) 17 | { 18 | } 19 | 20 | Test1 * hoge( int val1, int val2 ) 21 | { 22 | return NULL; 23 | } 24 | 25 | int main() 26 | { 27 | Test1 * pTest1; /** */ 28 | 29 | Test1::sub(); 30 | 31 | ((Test1*)NULL)->f 32 | // return 0; 33 | // } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/test/comp5.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class Test1 4 | { 5 | int member; 6 | public: 7 | static int sub() 8 | { 9 | return 1; 10 | } 11 | int func() { 12 | return 0; 13 | } 14 | }; 15 | 16 | void zzzz( int val1, int val2 ) 17 | { 18 | } 19 | 20 | Test1 * hoge( int val1, int val2 ) 21 | { 22 | return NULL; 23 | } 24 | 25 | int main() 26 | { 27 | Test1 * pTest1; /** */ 28 | 29 | Test1::sub(); 30 | 31 | ((Test1*)NULL)->f 32 | // return 0; 33 | // } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/test/comp3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class Test1 4 | { 5 | int member; 6 | public: 7 | static int sub() 8 | { 9 | return 1; 10 | } 11 | int func() { 12 | return 0; 13 | } 14 | }; 15 | 16 | void zzzz( int val1, int val2 ) 17 | { 18 | } 19 | 20 | Test1 * hoge( int val1, int val2 ) 21 | { 22 | return NULL; 23 | } 24 | 25 | int main() 26 | { 27 | Test1 * pTest1; /** */ 28 | 29 | Test1::sub(); 30 | 31 | zzzz( 0, hoge( 1, 2 )->s 32 | // return 0; 33 | // } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/lisp/html/file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | lctags file info 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /src/lctags/testMQueue.lua: -------------------------------------------------------------------------------- 1 | local Helper = require( 'lctags.Helper' ) 2 | local Json = require( 'lctags.Json' ) 3 | 4 | local lock = Helper.createLock( "test" ) 5 | local mqueue = Helper.createMQueue( "test", true, lock ) 6 | 7 | print( os.clock(), os.date() ) 8 | for index = 1, 10000 do 9 | mqueue:put( "123", true ) 10 | mqueue:get() 11 | end 12 | print( os.clock(), os.date() ) 13 | for index = 1, 10000 do 14 | mqueue:put( "123", true ) 15 | Json:convertFrom( mqueue:get() ) 16 | end 17 | print( os.clock(), os.date() ) 18 | 19 | Helper.deleteLock( "test" ) 20 | -------------------------------------------------------------------------------- /src/test/indirect.cpp: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | typedef struct { 3 | void (*func_t)(void); 4 | } test_str_t; 5 | } 6 | 7 | 8 | typedef void (test_indirect_t)(void); 9 | 10 | static void test_indirect( void ) 11 | { 12 | } 13 | 14 | static void test_indirect2( void ) 15 | { 16 | } 17 | 18 | static void test_indirect3( void ) 19 | { 20 | } 21 | 22 | void test_sub( test_indirect_t * pFunc, test_str_t * pStr) { 23 | pFunc(); 24 | pStr->func_t(); 25 | } 26 | 27 | static void foo( test_str_t * pStr ) 28 | { 29 | test_sub( test_indirect, pStr ); 30 | } 31 | -------------------------------------------------------------------------------- /docker/ubuntu20.04/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | main: 5 | build: 6 | context: ./ 7 | args: 8 | - UID=${UID:-1000} 9 | - GID=${GUI:-1000} 10 | - UNAME=${UNAME:-hoge} 11 | image: lctags_env 12 | container_name: lctags_env 13 | volumes: 14 | # - "../:/opt/lctags" 15 | - "${HOST_PROJ:-/home}:/proj" 16 | # ports: 17 | # - 15900:5900 18 | tty: true # docker-compose up -d でデーモン起動できるように 19 | # cap_add: 20 | # - CAP_SYS_PTRACE # docker 内で strace できるように 21 | user: ${UNAME:-hoge} 22 | -------------------------------------------------------------------------------- /src/lctags/Scan.lua: -------------------------------------------------------------------------------- 1 | local Util = require( 'lctags.Util' ) 2 | local log = require( 'lctags.LogCtrl' ) 3 | local clang = require( 'libclanglua.if' ) 4 | 5 | local Scan = {} 6 | 7 | function Scan:outputIncSrcHeader( 8 | scanMode, analyzer, src, target, fileContents ) 9 | 10 | local unit, compileOp, newAnalyzer = 11 | analyzer:createUnit( src, target, false, fileContents ) 12 | 13 | local incList = clang.getInclusionList( unit ) 14 | for index, incFile in ipairs( incList ) do 15 | log( 1, incFile:getIncludedFile():getFileName() ) 16 | end 17 | end 18 | 19 | return Scan 20 | -------------------------------------------------------------------------------- /docker/ubuntu22.04/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | main: 5 | build: 6 | context: ./ 7 | args: 8 | - UID=${UID:-1000} 9 | - GID=${GUI:-1000} 10 | - UNAME=${UNAME:-lctags} 11 | image: lctags_env_on_ubuntu22 12 | container_name: lctags_env_on_ubuntu22 13 | volumes: 14 | # - "../:/opt/lctags" 15 | - "${HOST_PROJ:-/home}:/proj" 16 | # ports: 17 | # - 15900:5900 18 | tty: true # docker-compose up -d でデーモン起動できるように 19 | # cap_add: 20 | # - CAP_SYS_PTRACE # docker 内で strace できるように 21 | user: ${UNAME:-lctags} 22 | -------------------------------------------------------------------------------- /src/lctags/testServer.lua: -------------------------------------------------------------------------------- 1 | local Server = require( 'lctags.Server' ) 2 | local DBCtrl = require( 'lctags.DBCtrl' ) 3 | 4 | local currentDir = Util:getcwd() 5 | DBCtrl:init( 'lctags.sqlite3', currentDir, '.', false, false, false ) 6 | local db = DBCtrl:open( 'lctags.sqlite3', false, currentDir ) 7 | 8 | print( 0, os.clock(), os.date() ) 9 | 10 | -- for index = 1, 10000 do 11 | -- db.db.db:exec( 12 | -- string.format( 13 | -- "INSERT INTO filePath VALUES ( NULL, '%d', 0, 0, '17c3aa6506bb39fb5d1ba63a3fc33c01', '|', 0 );", index ) ) 14 | -- end 15 | 16 | print( 1, os.clock(), os.date() ) 17 | 18 | Server:new( "test", db ) 19 | -------------------------------------------------------------------------------- /src/lisp/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lctags 6 | 7 | 8 | 13 | 14 | 15 |
16 | lctags db path: 17 | 18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /src/lctags/testInc.lua: -------------------------------------------------------------------------------- 1 | local Util = require( 'lctags.Util' ) 2 | local log = require( 'lctags.LogCtrl' ) 3 | local clang = require( 'libclanglua.if' ) 4 | 5 | local TestInc = {} 6 | 7 | function TestInc:run( analyzer, path, target ) 8 | if not target then 9 | target = "" 10 | end 11 | 12 | path = analyzer:convFullpath( path ) 13 | 14 | local unit, compileOp, newAnalyzer = 15 | analyzer:createUnit( path, target, false ) 16 | 17 | 18 | local incList = clang.getInclusionList( unit ) 19 | for index, incFile in ipairs( incList ) do 20 | log( 1, incFile:getIncludedFile():getFileName() ) 21 | end 22 | end 23 | 24 | return TestInc 25 | -------------------------------------------------------------------------------- /src/test/highlight.cpp: -------------------------------------------------------------------------------- 1 | typedef struct VAL_t { 2 | int val; 3 | int val2; 4 | } VAL_t; 5 | 6 | typedef struct VAL2_t { 7 | int val; 8 | int val2; 9 | } VAL2_t; 10 | 11 | typedef enum { 12 | enum_val1, 13 | enum_val2, 14 | enum_val3 15 | } enum_t; 16 | 17 | #define M_VAL enum_val3 18 | 19 | extern VAL2_t * func(); 20 | 21 | void sub( VAL_t * pVal, VAL2_t * pVal2, VAL_t * pVal3[], struct VAL2_t VAL2_t ) 22 | { 23 | /** テスト */ pVal->val = enum_val1; 24 | pVal2->val = enum_val2; 25 | pVal->val2 = enum_val3; 26 | pVal2->val2 = M_VAL; 27 | func()->val = enum_val2; 28 | pVal3[0]->val = enum_val1; 29 | VAL2_t.val = enum_val3; 30 | } 31 | -------------------------------------------------------------------------------- /src/lctags.conf: -------------------------------------------------------------------------------- 1 | -- -*- mode:lua coding:utf-8 -*- 2 | -- Copyright (C) 2017 ifritJP 3 | 4 | local conf = {} 5 | 6 | function conf:createCompileOptionConverter( compiler ) 7 | return nil 8 | end 9 | 10 | function conf:getDefaultOptionList( compiler ) 11 | return { "-I", "/usr/include/x86_64-linux-gnu/c++/15" } 12 | end 13 | 14 | function conf:getIgnorePattern() 15 | return {} 16 | end 17 | 18 | function conf:getClangIncPath() 19 | return "/usr/include/c++/15" 20 | end 21 | 22 | function conf:getIndirectFuncList( symbol ) 23 | if symbol == "::test_indirect_t" then 24 | return { "%$_indirect%" } 25 | elseif symbol == "::@struct::::func_t" then 26 | return { "%$_indirect2" } 27 | end 28 | return {} 29 | end 30 | 31 | 32 | return conf 33 | -------------------------------------------------------------------------------- /src/test/hoge.h: -------------------------------------------------------------------------------- 1 | extern int func(); 2 | extern int func3(); 3 | extern int func33(); 4 | 5 | 6 | #ifdef NNNN 7 | extern int func44(); 8 | #endif 9 | 10 | #define VVVVVVV 11 | 12 | #define STRUCT( X ) \ 13 | typedef struct { \ 14 | int aaa; \ 15 | } struct_1##X; \ 16 | 17 | #define STRUCT2( X ) \ 18 | typedef struct { \ 19 | int aaa; \ 20 | } struct_1##X; \ 21 | typedef struct { \ 22 | int aaa; \ 23 | } struct_2##X; 24 | 25 | STRUCT( AA ); 26 | STRUCT2( BB ); 27 | -------------------------------------------------------------------------------- /src/lctags/makefile: -------------------------------------------------------------------------------- 1 | LUA_COMMAND=lua5.3 2 | LUA_INC=../../external/lua/lua-5.3.4/src/ 3 | LUA_LIB= 4 | LUA_CFLAGS= 5 | LUA_LDFLAGS=-llua5.3 6 | INST_DIR=/usr/local/bin 7 | SO=so 8 | 9 | all: Helper.$(SO) 10 | 11 | Helper.$(SO): Helper.c 12 | gcc Helper.c -std=c99 -DHELPER_STAND_ALONE -o Helper $(LUA_LIB) $(LUA_CFLAGS) $(LUA_LDFLAGS) -lcrypto -lm -lpthread -lrt 13 | gcc Helper.c -std=c99 -fPIC -shared -DHELPER_STAND_ALONE -o Helper.$(SO) $(LUA_LIB) $(LUA_CFLAGS) $(LUA_LDFLAGS) -lcrypto -lm -lpthread -lrt 14 | 15 | test: all 16 | ./Helper 17 | (cd ..; $(LUA_COMMAND) lctags/testHelper.lua) 18 | 19 | clean: 20 | rm -f Helper.$(SO) Helper 21 | 22 | install: 23 | sed "s@lua5.3@$(LUA_COMMAND)@g" lctags | \ 24 | sed 's@$${DIR}@'"$(LUA_MOD_DIR)/lctags@g" > $(INST_DIR)/lctags 25 | chmod +x $(INST_DIR)/lctags 26 | 27 | uninstall: 28 | rm $(INST_DIR)/lctags 29 | -------------------------------------------------------------------------------- /src/test/comp6.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef enum { 4 | TEST60_1, 5 | TEST60_2, 6 | TEST60_3 7 | } enum_TEST60; 8 | 9 | class TEST601; 10 | class TEST602; 11 | class TEST603; 12 | 13 | namespace ns1 { 14 | typedef enum { 15 | TEST6_1, 16 | TEST6_2, 17 | TEST6_3 18 | } enum_TEST6; 19 | 20 | class TEST61; 21 | class TEST62; 22 | class TEST63; 23 | 24 | namespace ns2 { 25 | typedef enum { 26 | TEST62_1, 27 | TEST62_2, 28 | TEST62_3 29 | } enum_TEST62; 30 | 31 | class TEST611; 32 | class TEST612; 33 | class TEST613; 34 | 35 | namespace ns3 { 36 | typedef enum { 37 | TEST63_1, 38 | TEST63_2, 39 | TEST63_3 40 | } enum_TEST63; 41 | 42 | class TEST621; 43 | class TEST622; 44 | class TEST623; 45 | } 46 | int sub() 47 | { 48 | TE 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/lisp/html/module-file-graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lctags func call graph 6 | 7 | 8 | 9 | 10 | 19 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/comp7.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef enum { 4 | TEST70_1, 5 | TEST70_2, 6 | TEST70_3 7 | } enum_TEST70; 8 | 9 | class TEST701; 10 | class TEST702; 11 | class TEST703; 12 | 13 | namespace ns71 { 14 | typedef enum { 15 | TEST7_1, 16 | TEST7_2, 17 | TEST7_3 18 | } enum_TEST7; 19 | 20 | class TEST71; 21 | class TEST72; 22 | class TEST73; 23 | 24 | namespace ns72 { 25 | typedef enum { 26 | TEST72_1, 27 | TEST72_2, 28 | TEST72_3 29 | } enum_TEST72; 30 | 31 | class TEST711; 32 | class TEST712; 33 | class TEST713; 34 | 35 | namespace ns73 { 36 | typedef enum { 37 | TEST73_1, 38 | TEST73_2, 39 | TEST73_3 40 | } enum_TEST73; 41 | 42 | class TEST721; 43 | class TEST722; 44 | class TEST723; 45 | } 46 | int sub() 47 | { 48 | ns71::ns72:: 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/comp8.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef enum { 4 | TEST80_1, 5 | TEST80_2, 6 | TEST80_3 7 | } enum_TEST80; 8 | 9 | class TEST801; 10 | class TEST802; 11 | class TEST803; 12 | 13 | namespace ns81 { 14 | typedef enum { 15 | TEST8_1, 16 | TEST8_2, 17 | TEST8_3 18 | } enum_TEST8; 19 | 20 | class TEST81; 21 | class TEST82; 22 | class TEST83; 23 | 24 | namespace ns82 { 25 | typedef enum { 26 | TEST82_1, 27 | TEST82_2, 28 | TEST82_3 29 | } enum_TEST82; 30 | 31 | class TEST811; 32 | class TEST812; 33 | class TEST813; 34 | 35 | namespace ns83 { 36 | typedef enum { 37 | TEST83_1, 38 | TEST83_2, 39 | TEST83_3 40 | } enum_TEST83; 41 | 42 | class TEST821; 43 | class TEST822; 44 | class TEST823; 45 | } 46 | int sub() 47 | { 48 | ns81::TE 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/comp9.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef enum { 4 | TEST90_1, 5 | TEST90_2, 6 | TEST90_3 7 | } enum_TEST90; 8 | 9 | class TEST901; 10 | class TEST902; 11 | class TEST903; 12 | 13 | namespace ns91 { 14 | typedef enum { 15 | TEST9_1, 16 | TEST9_2, 17 | TEST9_3 18 | } enum_TEST9; 19 | 20 | class TEST91; 21 | class TEST92; 22 | class TEST93; 23 | 24 | namespace ns92 { 25 | typedef enum { 26 | TEST92_1, 27 | TEST92_2, 28 | TEST92_3 29 | } enum_TEST92; 30 | 31 | class TEST911; 32 | class TEST912; 33 | class TEST913; 34 | 35 | namespace ns93 { 36 | typedef enum { 37 | TEST93_1, 38 | TEST93_2, 39 | TEST93_3 40 | } enum_TEST93; 41 | 42 | class TEST921; 43 | class TEST922; 44 | class TEST923; 45 | } 46 | int sub() 47 | { 48 | ::T 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /doc/rename.org: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #+AUTHOR: ifritJP 3 | #+STARTUP: nofold 4 | 5 | #+TITLE: C/C++ ソースコードをリファクタリング by lctags on emacs (リネーム編) 6 | 7 | lctags は、変数名、引数名のリネームをサポートします。 8 | 9 | 多くの場合は、emacs が提供している文字列置換でも問題なくリネームできますが、 10 | リネーム対象と同じ名前のメンバが存在しているような場合、 11 | 単純な文字列置換では意図しないものまでリネームしてしまうことがあります。 12 | 13 | lctags では、構文解析した情報を基にリネームを行なうため、確実にリネームできます。 14 | 15 | * デモ 16 | 17 | [[https://www.youtube.com/watch?v=HovGT2yTUPA][https://www.youtube.com/watch?v=HovGT2yTUPA]] 18 | 19 | lctags で引数をリネームしているデモ動画です。 20 | この動画では、引数と同名のメンバ名が存在していますが、 21 | メンバ名は変わらずに指定の変数だけがリネームされています。 22 | 23 | * 使用方法 24 | 25 | リネームしたい変数の位置にカーソルを合せ、次のいずれかを実行します。 26 | 27 | - M-x lctags-rename-at 28 | - C-c l r r 29 | 30 | mini-buffer に変数名が表示されるので、これを編集し RET します。 31 | 32 | 指定された変数名に置換されます。 33 | 34 | このとき、置換された変数はハイライト表示されます。 35 | 36 | * 制限 37 | 38 | lctags は、リネーム対象を引数と変数に制限しています。 39 | そのため、メンバーや関数等のリネームはできません。 40 | -------------------------------------------------------------------------------- /src/lctags/_lctags.conf: -------------------------------------------------------------------------------- 1 | -- -*- coding:utf-8; mode:lua -*- 2 | 3 | local log = require( 'lctags.LogCtrl' ) 4 | 5 | local config = {} 6 | 7 | function config:getIgnorePattern() 8 | return { 9 | -- { "simple", "ignore.c" }, -- this is simple match. 10 | -- { "lua", "^ignore.c$" }, -- this is lua pattern match. 11 | } 12 | end 13 | 14 | --[[ 15 | This method is compile option converter from your compiler to clang. 16 | This is sample for armcc. 17 | ]] 18 | function config:createCompileOptionConverter( compiler ) 19 | return nil 20 | end 21 | 22 | function config:getDefaultOptionList( compiler ) 23 | return {} 24 | end 25 | 26 | function config:getClangIncPath() 27 | -- return "/usr/lib/llvm/lib/clang/include" 28 | return nil -- clang include path 29 | end 30 | 31 | function config:getIndirectFuncList( symbol, mode ) 32 | return {} 33 | end 34 | 35 | return config 36 | -------------------------------------------------------------------------------- /src/test/comp10.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef enum { 4 | TEST100_1, 5 | TEST100_2, 6 | TEST100_3 7 | } enum_TEST100; 8 | 9 | class TEST1001; 10 | class TEST1002; 11 | class TEST1003; 12 | 13 | namespace ns101 { 14 | typedef enum { 15 | TEST10_1, 16 | TEST10_2, 17 | TEST10_3 18 | } enum_TEST10; 19 | 20 | class TEST101; 21 | class TEST102; 22 | class TEST103; 23 | 24 | namespace ns102 { 25 | typedef enum { 26 | TEST102_1, 27 | TEST102_2, 28 | TEST102_3 29 | } enum_TEST102; 30 | 31 | class TEST1011; 32 | class TEST1012; 33 | class TEST1013; 34 | 35 | namespace ns103 { 36 | typedef enum { 37 | TEST103_1, 38 | TEST103_2, 39 | TEST103_3 40 | } enum_TEST103; 41 | 42 | class TEST1021; 43 | class TEST1022; 44 | class TEST1023; 45 | } 46 | int sub() 47 | { 48 | T 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/lisp/lctags-anything.el: -------------------------------------------------------------------------------- 1 | (require 'anything) 2 | (setq lctags-anything t) 3 | 4 | (setq anything-c-source-gtags-select 5 | '((name . "GTAGS") 6 | (init 7 | . (lambda () 8 | (call-process-shell-command 9 | "lctags -c" nil (anything-candidate-buffer 'global)))) 10 | (candidates-in-buffer) 11 | (action 12 | ("Goto the location" . (lambda (candidate) 13 | (gtags-push-context) 14 | (gtags-goto-tag candidate ""))) 15 | ("Goto the location (other-window)" . (lambda (candidate) 16 | (gtags-push-context) 17 | (gtags-goto-tag candidate "" t))) 18 | ("Move to the referenced point" . (lambda (candidate) 19 | (gtags-push-context) 20 | (gtags-goto-tag candidate "r")))))) 21 | 22 | (require 'lctags-helm) 23 | 24 | (provide 'lctags-anything) 25 | -------------------------------------------------------------------------------- /src/lisp/html/func-call-graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lctags func call graph 6 | 7 | 8 | 9 | 10 | 11 | 20 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/test/stack.cpp: -------------------------------------------------------------------------------- 1 | static void func1( int val ); 2 | static void func2( int val ); 3 | static void func3( int val ); 4 | static void sub(); 5 | 6 | 7 | static void func1( int val ) 8 | { 9 | if ( val >= 10 ) { 10 | return; 11 | } 12 | { 13 | char buf[ 10 ]; 14 | func2( val + 1 ); 15 | } 16 | { 17 | char buf[ 100 ]; 18 | func2( val + 1 ); 19 | } 20 | } 21 | 22 | static void func2( int val ) 23 | { 24 | if ( val >= 10 ) { 25 | return; 26 | } 27 | { 28 | char buf[ 10 ]; 29 | func3( val + 1 ); 30 | } 31 | { 32 | char buf[ 100 ]; 33 | func3( val + 1 ); 34 | } 35 | } 36 | 37 | static void func3( int val ) 38 | { 39 | if ( val >= 10 ) { 40 | return; 41 | } 42 | { 43 | char buf[ 10 ]; 44 | func1( val + 1 ); 45 | } 46 | { 47 | char buf[ 100 ]; 48 | func1( val + 1 ); 49 | } 50 | } 51 | 52 | static void sub() 53 | { 54 | char buf[ 10 ]; 55 | func1( 0 ); 56 | } 57 | -------------------------------------------------------------------------------- /src/lisp/html/module-dir-graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lctags func call graph 6 | 7 | 8 | 9 | 10 | 11 | 12 | 21 | 26 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /src/lisp/lctags-conf.el: -------------------------------------------------------------------------------- 1 | (add-hook 'lctags-mode-hook 2 | '(lambda () 3 | (local-set-key (kbd "M-t") 'lctags-def) 4 | (local-set-key (kbd "C-M-t") 'lctags-def-pickup-symbol) 5 | (local-set-key (kbd "M-r") 'lctags-ref) 6 | (local-set-key (kbd "C-c l") 'lctags-dispatch-mode) 7 | (local-set-key (kbd "C-c I") 'lctags-cursor-at) 8 | (local-set-key (kbd "C-c C-/") 'lctags-helm-completion-at) 9 | (local-set-key (kbd "C-c C-x") 'lctags-helm-change-enum-at) 10 | (local-set-key (kbd "C-c C-f") 'lctags-display-diag) 11 | (local-set-key (kbd "M-m") 'lctags-gtags-resume) 12 | (local-set-key "\C-t" 'gtags-pop-stack))) 13 | 14 | (cond ((featurep 'helm) 15 | (require 'lctags-helm)) 16 | ((featurep 'anything) 17 | (require 'lctags-anything)) 18 | (t 19 | (error "please set helm or anything"))) 20 | 21 | 22 | (eval-after-load 'simple-httpd 23 | '(require 'lctags-servlet)) 24 | 25 | (add-hook 'c-mode-common-hook 26 | '(lambda() 27 | (lctags-mode 1))) 28 | 29 | (require 'lctags) 30 | 31 | (provide 'lctags-conf) 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ifritJP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lctags/testOperator.lua: -------------------------------------------------------------------------------- 1 | local Util = require( 'lctags.Util' ) 2 | local log = require( 'lctags.LogCtrl' ) 3 | local clang = require( 'libclanglua.if' ) 4 | 5 | local TestOpe = {} 6 | 7 | local function visit( cursor, parent, info, addInfo ) 8 | local cursorKind = cursor:getCursorKind() 9 | 10 | Util:dumpCursorInfo( cursor, 1, "", 0 ) 11 | 12 | if cursorKind == clang.core.CXCursor_BinaryOperator or 13 | cursorKind == clang.core.CXCursor_CompoundAssignOperator or 14 | cursorKind == clang.core.CXCursor_UnaryOperator 15 | then 16 | log( 1, clang.getOperatorTxt( cursor ) ) 17 | end 18 | 19 | return 1 20 | end 21 | 22 | 23 | function TestOpe:at( analyzer, path, target ) 24 | if not target then 25 | target = "" 26 | end 27 | 28 | path = analyzer:convFullpath( path ) 29 | 30 | local analyzerForTokenize = analyzer:newAs( 31 | log( -4 ) >= 2 and true or false, false ) 32 | local unit, compileOp, newAnalyzer = 33 | analyzerForTokenize:createUnit( path, target, false ) 34 | 35 | clang.visitChildrenFast( unit:getTranslationUnitCursor(), visit, info, {}, 2 ) 36 | end 37 | 38 | return TestOpe -------------------------------------------------------------------------------- /src/lisp/html/file-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lctags file list 6 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /src/test/class.cpp: -------------------------------------------------------------------------------- 1 | #include "class.h" 2 | 3 | template class Set; 4 | 5 | template 6 | class List : public std::vector 7 | { 8 | typedef std::vector Base; 9 | public: 10 | static const size_t npos = std::string::npos; 11 | explicit List(size_t count = 0, const T &defaultValue = T()) 12 | : Base(count, defaultValue) 13 | {} 14 | 15 | template 16 | List(const std::vector &other) 17 | : Base(other.size(), T()) 18 | { 19 | const size_t len = other.size(); 20 | for (size_t i=0; i::operator[](i) = other.at(i); 22 | } 23 | } 24 | 25 | List(std::initializer_list list) 26 | : Base(list) 27 | {} 28 | 29 | List(typename Base::const_iterator f, typename Base::const_iterator l) 30 | : Base(f, l) 31 | { 32 | } 33 | 34 | bool contains(const T &t) const 35 | { 36 | return std::find(Base::begin(), Base::end(), t) != Base::end(); 37 | } 38 | 39 | bool isEmpty() const 40 | { 41 | return Base::empty(); 42 | } 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /src/lisp/lctags-test.el: -------------------------------------------------------------------------------- 1 | (require 'lctags-const) 2 | 3 | (defun lctags-servlet-api-info-2-python () 4 | "lctags-servlet-api-info-table から httpy.py の apiInfoMap を生成する。" 5 | ;;(lctags-switch-to-buffer-other-window (generate-new-buffer "*lctags-python*")) 6 | (with-temp-buffer 7 | (insert "apiInfoMap = {\n") 8 | (dolist (item lctags-servlet-api-info-table) 9 | (let ((first t) 10 | (param-list (plist-get (cadr item) :param))) 11 | (if (symbolp (car param-list)) 12 | nil 13 | (insert (format "\"%s\": { " (car item))) 14 | (insert "\"param\": [") 15 | (dolist (param param-list) 16 | (if first 17 | (setq first nil) 18 | (insert ", ")) 19 | (cond ((stringp param) 20 | (insert (format "\"%s\"" param))) 21 | ((listp param) 22 | (insert (format "[\"%s\", (lambda val: val.replace( \"_\", \"$_\" ))" 23 | (car param))) 24 | (insert "]")) 25 | )) 26 | (insert "] },\n"))) 27 | ) 28 | (insert "}\n") 29 | (buffer-string) 30 | )) 31 | 32 | ;; (lctags-servlet-api-info-2-python) 33 | 34 | (with-temp-buffer 35 | (insert (lctags-servlet-api-info-2-python)) 36 | (write-region (point-min) (point-max) "servlet-api-info")) 37 | 38 | (provide 'lctags-test) 39 | -------------------------------------------------------------------------------- /src/lisp/lctags_httpd_var.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | apiInfoMap = { 4 | "dumpDir": { "param": ["--lctags-form", "json"] }, 5 | "matchFile": { "param": ["?pattern", "?option", "--lctags-form", "json"] }, 6 | "searchFile": { "param": [["?path", (lambda val: val.replace( "_", "$_" ))], 7 | "--lctags-form", "json", 8 | "--lctags-candidateLimit", "?limit"] }, 9 | "searchDecl": { "param": [["?name", (lambda val: val.replace( "_", "$_" ))], 10 | "--lctags-form", "json", 11 | "--lctags-candidateLimit", "?limit"] }, 12 | "defAtFileId": { "param": ["?fileId", "--lctags-form", "json"] }, 13 | "callee": { "param": ["?nsId", "--lctags-form", "json"] }, 14 | "caller": { "param": ["?nsId", "--lctags-form", "json"] }, 15 | "refSym": { "param": ["?nsId", "--lctags-form", "json"] }, 16 | "refDir": { "param": ["?path", "--lctags-form", "json"] }, 17 | "refFile": { "param": ["?fileId", "?path", "--lctags-form", "json"] }, 18 | "reqDir": { "param": ["?path", "--lctags-form", "json"] }, 19 | "reqFile": { "param": ["?fileId", "?path", "--lctags-form", "json"] }, 20 | "decl": { "param": ["?nsId", "--lctags-form", "json"] }, 21 | } 22 | -------------------------------------------------------------------------------- /docker/ubuntu22.04/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ARG PROXY="" 4 | 5 | ARG GID=1000 6 | ARG UID=1000 7 | ARG UNAME=lctags 8 | 9 | # apt-get で入力待ちにならないようにするためセット 10 | ENV DEBIAN_FRONTEND=noninteractive 11 | # proxy 設定 12 | ENV HTTP_PROXY=$PROXY 13 | ENV http_proxy=$PROXY 14 | ENV https_proxy=$PROXY 15 | ENV HTTPS_PROXY=$PROXY 16 | RUN if [ "$PROXY" != "" ]; then \ 17 | echo set $PROXY; \ 18 | echo "Acquire::http::Proxy \"$PROXY\";" >> /etc/apt/apt.conf; \ 19 | echo "Acquire::https::Proxy \"$PROXY\";" >> /etc/apt/apt.conf; \ 20 | fi 21 | RUN apt update 22 | RUN apt-get install -y \ 23 | git \ 24 | make \ 25 | diffutils \ 26 | gcc \ 27 | curl \ 28 | libreadline-dev \ 29 | rsync \ 30 | time \ 31 | software-properties-common 32 | RUN apt-get install -y unzip 33 | RUN if [ "$PROXY" != "" ]; then \ 34 | git config --global http.proxy $PROXY; \ 35 | fi 36 | RUN apt update 37 | 38 | RUN if grep :$GID: /etc/group; then \ 39 | echo exist; else groupadd -g $GID $UNAME; fi 40 | RUN if grep :$UID: /etc/passwd; then \ 41 | echo exist; else useradd -g $GID -u $UID $UNAME; fi 42 | RUN apt-get install -y sudo 43 | RUN echo "$UNAME ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers 44 | 45 | WORKDIR /opt/ 46 | RUN git clone --depth 1 https://github.com/ifritJP/lctags.git 47 | WORKDIR /opt/lctags/src 48 | RUN make build_for_apt 49 | RUN make install 50 | -------------------------------------------------------------------------------- /docker/ubuntu20.04/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | ARG PROXY="" 4 | 5 | ARG GID=1000 6 | ARG UID=1000 7 | ARG UNAME=lctags 8 | 9 | # apt-get で入力待ちにならないようにするためセット 10 | ENV DEBIAN_FRONTEND=noninteractive 11 | # proxy 設定 12 | ENV HTTP_PROXY=$PROXY 13 | ENV http_proxy=$PROXY 14 | ENV https_proxy=$PROXY 15 | ENV HTTPS_PROXY=$PROXY 16 | RUN if [ "$PROXY" != "" ]; then \ 17 | echo set $PROXY; \ 18 | echo "Acquire::http::Proxy \"$PROXY\";" >> /etc/apt/apt.conf; \ 19 | echo "Acquire::https::Proxy \"$PROXY\";" >> /etc/apt/apt.conf; \ 20 | fi 21 | RUN apt update 22 | RUN apt-get install -y \ 23 | git \ 24 | make \ 25 | diffutils \ 26 | gcc \ 27 | curl \ 28 | libreadline-dev \ 29 | rsync \ 30 | time \ 31 | software-properties-common 32 | RUN apt-get install -y unzip 33 | RUN if [ "$PROXY" != "" ]; then \ 34 | git config --global http.proxy $PROXY; \ 35 | fi 36 | RUN apt update 37 | 38 | RUN if grep :$GID: /etc/group; then \ 39 | echo exist; else groupadd -g $GID $UNAME; fi 40 | RUN if grep :$UID: /etc/passwd; then \ 41 | echo exist; else useradd -g $GID -u $UID $UNAME; fi 42 | RUN apt-get install -y sudo 43 | RUN echo "$UNAME ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers 44 | 45 | WORKDIR /opt/ 46 | RUN git clone --depth 1 https://github.com/ifritJP/lctags.git 47 | WORKDIR /opt/lctags/src 48 | RUN make build_for_apt CLANG_VER=9 49 | RUN make install 50 | 51 | -------------------------------------------------------------------------------- /src/lctags/testJson.lua: -------------------------------------------------------------------------------- 1 | local Json = require( 'lctags.Json' ) 2 | 3 | print( Json:convertFrom( "123" ) ) 4 | 5 | function printJson( keyName, obj ) 6 | print( Json:convertTo( obj ) ) 7 | end 8 | 9 | local obj = Json:convertFrom( [[ 10 | { 11 | "directory": "/home/hoge/work/libclanglua/external/clang/r390/build/tools/llvm-config", 12 | "command": "/usr/bin/c++ -DCMAKE_CFG_INTDIR=\".\" -DGTEST_HAS_RTTI=0 -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/home/hoge/work/libclanglua/external/clang/r390/build/tools/llvm-config -I/home/hoge/work/libclanglua/external/clang/r390/llvm/tools/llvm-config -I/home/hoge/work/libclanglua/external/clang/r390/build/include -I/home/hoge/work/libclanglua/external/clang/r390/llvm/include -fPIC -fvisibility-inlines-hidden -Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wno-missing-field-initializers -pedantic -Wno-long-long -Wno-maybe-uninitialized -Wdelete-non-virtual-dtor -Wno-comment -Werror=date-time -std=c++11 -ffunction-sections -fdata-sections -O3 -DNDEBUG -fno-exceptions -fno-rtti -o CMakeFiles/llvm-config.dir/llvm-config.cpp.o -c /home/hoge/work/libclanglua/external/clang/r390/llvm/tools/llvm-config/llvm-config.cpp", 13 | "file": "/home/hoge/work/libclanglua/external/clang/r390/llvm/tools/llvm-config/llvm-config.cpp" 14 | } 15 | ]] 16 | ) 17 | 18 | printJson( "", obj ) 19 | 20 | obj = Json:convertFrom( io.open( arg[1], "r" ):read( '*a' ) ) 21 | 22 | printJson( "", obj ) 23 | -------------------------------------------------------------------------------- /src/lctags/testServerRequest.lua: -------------------------------------------------------------------------------- 1 | local Server = require( 'lctags.Server' ) 2 | local DBCtrl = require( 'lctags.DBCtrl' ) 3 | 4 | Server:connect( "test" ) 5 | 6 | local result = Server:requestExec( 7 | "INSERT INTO filePath VALUES ( NULL, '|/test/hoge.cpp', 1490273968, 0, '17c3aa6506bb39fb5d1ba63a3fc33c01', '|', 0 );" ) 8 | 9 | local query = "SELECT * FROM filePath WHERE id < 10 LIMIT 10" 10 | local item = Server:requestInq( 11 | query, 12 | function( item ) 13 | for key, val in pairs( item ) do 14 | print( key, val ) 15 | end 16 | return true 17 | end 18 | ) 19 | 20 | local db = DBCtrl:open( 'lctags.sqlite3', true, Util:getcwd() ) 21 | 22 | local fileInfo = db:getFileInfo( 2 ) 23 | print( "fileInfo 2", fileInfo ) 24 | 25 | fileInfo = db:getFileInfo( 1 ) 26 | print( "fileInfo 1", fileInfo ) 27 | 28 | 29 | 30 | print( 0, os.clock(), os.date() ) 31 | 32 | for index = 1, 10000 do 33 | Server:requestExec( 34 | string.format( 35 | "INSERT INTO filePath VALUES ( NULL, '%d', 0, 0, '17c3aa6506bb39fb5d1ba63a3fc33c01', '|', 0 );", index ) ) 36 | end 37 | 38 | print( 1, os.clock(), os.date() ) 39 | 40 | for index = 1, 1 do 41 | local item = Server:requestInq( 42 | "SELECT * FROM filePath WHERE id > 10 LIMIT 10", 43 | function( item ) 44 | print( item.id, item.path ) 45 | return true 46 | end 47 | ) 48 | end 49 | 50 | print( 1, os.clock(), os.date() ) 51 | Server:requestTest() 52 | print( 1, os.clock(), os.date() ) 53 | 54 | Server:requestEnd() 55 | 56 | -------------------------------------------------------------------------------- /doc/highlight.org: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #+AUTHOR: ifritJP 3 | #+STARTUP: nofold 4 | 5 | #+TITLE: C/C++ の特定シンボルをハイライト by lctags on emacs 6 | 7 | IDE には、カーソル位置の変数と、その変数を参照している箇所を 8 | ハイライト表示する機能があります。 9 | 10 | この機能を利用することで、 11 | 関数に何か不具合が起きた時や、関数に処理を追加する時などに、 12 | とある変数がどこでアクセスされているかが追い易くなります。 13 | 14 | lctags でも、このハイライト機能を提供しています。 15 | 16 | lctags で提供しているハイライト機能と、 17 | IDE で良くあるハイライト機能の差分には以下があります。 18 | 19 | - カーソルを動かしてもハイライトが残る 20 | - 複数同時に異なる変数をハイライト可能 21 | - ハイライトする色を指定可能 22 | - 新しく編集した箇所もハイライト可能 23 | - 単なる変数だけでなく、メンバもハイライト可能 24 | 25 | * デモ 26 | 27 | 文書よりも実際に動画を見た方が伝わり易い思うので、 28 | 以下のリンク先を確認ください。 29 | 30 | 31 | * 使用方法 32 | 33 | *前提として lctags にソースファイルを登録していることが必要です。* 34 | 35 | 36 | ** ハイライト表示 37 | 38 | ハイライト表示させたいシンボル上にカーソルを移動し、 39 | 次のいずれかを実行すると、mini buffer でハイライト色の入力を求められるので、 40 | 色を指定するか、そのまま Enter するとハイライトされます。 41 | なお、色指定時に C-i すると指定可能な色が確認できます。 42 | 43 | - M-x lctags-highlight-at 44 | - C-c l h h 45 | 46 | なお、デフォルト色は実行するごとに変わります。 47 | 48 | ** ハイライト終了 49 | 50 | ハイライト表示を終了させたい場合は、次のいずれかを実行します。 51 | 52 | - M-x lctags-highlight-clear 53 | - C-c l h c 54 | 55 | 終了すると、デフォルト色は元に戻ります。 56 | 57 | ** ハイライト更新 58 | 59 | ハイライト表示させている状態でコードを編集した場合、 60 | 編集した部分にハイライトさせるべきシンボルがあっても 61 | リアルタイムにはハイライトされません。 62 | 63 | ハイライトさせるには、次のいずれかを実行します。 64 | 65 | - M-x lctags-highlight-rescan 66 | - C-c l h r 67 | 68 | * カスタマイズ 69 | 70 | ハイライトのデフォルト色をカスタマイズする場合は、 71 | 以下のように設定してください。 72 | 73 | (setq lctags-search-token-color-list 74 | '("OrangeRed4" "OrangeRed3" "dark green" "dark magenta" "dark blue" 75 | "yellow4" "DeepSkyBlue4" "gray34")) 76 | 77 | リストの要素はいくつでも指定することが可能です。 78 | 指定した順に、ハイライトのデフォルト色になります。 79 | 80 | 指定可能な色は M-x list-colors-display で確認してください。 81 | -------------------------------------------------------------------------------- /doc/varSize.org: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #+AUTHOR: ifritJP 3 | #+STARTUP: nofold 4 | 5 | #+TITLE: C/C++ の変数サイズ確認 by lctags on emacs 6 | 7 | C/C++ ではデータサイズを意識して開発することが必要です。 8 | 9 | 小さいサイズのデータはスタック上に作れば良いですが、 10 | 大きいサイズのデータをスタック上に作るとスタックオーバーフローしてしまいます。 11 | 12 | まぁ、これは C/C++ に限らず Java や C# 等の他言語でも同じですが、 13 | C/C++ を利用するようなプロジェクトでは他の言語のプロジェクトと比べて 14 | 顕著だと言えるでしょう。 15 | 16 | プリミティブな型であればサイズを計算することは簡単です。 17 | しかし、構造体となるとサイズ計算出来ないこともないですが、面倒です。 18 | 19 | この面倒な作業を lctags を利用することで、簡単に実現できます。 20 | 21 | lctags 全般の紹介は次を参照してください。 22 | 23 | - C/C++ ソースコードタグシステム lctags の紹介 24 | https://qiita.com/dwarfJP/items/982ef7ee3f3bfd435156 25 | 26 | 27 | * 使い方 28 | 29 | サイズを確認したい変数や型にカーソルを移動して、 C-c I するだけです。 30 | 31 | これにより window が分割され、その window 内に幾つかの情報が表示されます。 32 | 33 | その情報に typeSize があり、これがその変数、型のサイズとなります。 34 | 35 | 例えば、次のソースの ~VAL_t val~ の val にカーソルを移動し、C-c I すると 36 | 37 | #+BEGIN_SRC c 38 | typedef struct { 39 | int val1; 40 | int val2[10]; 41 | } VAL_t; 42 | 43 | VAL_t val; 44 | #+END_SRC 45 | 46 | 次のようなバッファが表示されます。 47 | 48 | #+BEGIN_SRC txt 49 | spelling: val 50 | kindName: VarDecl 51 | type: VAL_t 52 | typeSize: 44(0x2C) 53 | kind: 9(0x9) 54 | #+END_SRC 55 | 56 | これは、val のタイプが ~VAL_t~ で、サイズが 44 Bytes であることを示しています。 57 | 58 | 59 | なお注意することは、 60 | *これはあくまで clang でコンパイルした場合のサイズ* であるということです。 61 | 62 | 構造体のサイズは padding や alignment によって違ってきます。 63 | そして、 padding や alignment はコンパイラ依存です。 64 | 65 | ただコンパイラ依存といっても、参考値としては十分使用できるとレベルだと思います。 66 | 67 | * 技術的な話 68 | 69 | このサイズ情報は、libclang の CXType:getSizeOf() を表示しているだけです。 70 | 71 | * どうでも良い話 72 | 73 | 実はこの情報を使って、関数のスタック使用量見積り機能も作ってたりするんですが、 74 | サブ関数コール(静的、動的)を考えると見積りが難しいので公開していない状況だったりします。 75 | 76 | GROSS で見積るのは難しいので、 77 | NET のスタック使用量だけでも公開しようかなぁ、と思ってたり思ってなかったり。。 78 | -------------------------------------------------------------------------------- /src/lisp/lctags-rename.el: -------------------------------------------------------------------------------- 1 | (require 'lctags-highlight) 2 | 3 | (defun lctags-rename-at () 4 | (interactive) 5 | (let ((buffer (lctags-get-process-buffer t)) 6 | location-info location-list kind new-symbol locationSet-list 7 | pos-list) 8 | (lctags-execute-xml (current-buffer) buffer 9 | (buffer-string) 10 | 'location-info 'ref 'location 11 | "ref-at-all" 12 | (buffer-file-name) 13 | (number-to-string (lctags-get-line)) 14 | (number-to-string (lctags-get-column)) "-i") 15 | (setq locationSet-list (lctags-xml-get-list location-info 'locationSet)) 16 | (cond 17 | (lctags-diag-info 18 | (lctags-helm-display-diag)) 19 | ((not (eq 1 (length locationSet-list))) 20 | (message "not support to rename function")) 21 | (t 22 | (setq location-list (lctags-xml-get-list (car locationSet-list) 'location)) 23 | (setq kind (lctags-location-item-get-kind (car location-list))) 24 | (when (or (equal kind "ParmDecl") (equal kind "VarDecl")) 25 | (setq new-symbol (read-string "rename?: " 26 | (lctags-location-item-get-symbol 27 | (car location-list)))) 28 | (save-excursion 29 | ;; 編集すると位置が変ってしまって正常に編集できなくなるので、 30 | ;; 位置が変わらないように marker を作る。 31 | (dolist (location (lctags-xml-get-list (car locationSet-list) 'location)) 32 | (let (begin-mark) 33 | (goto-char (lctags-location-item-get-point location)) 34 | (setq begin-mark (point-marker)) 35 | (goto-char (lctags-location-item-get-end-point location)) 36 | (setq pos-list (append pos-list `((:mark ,begin-mark 37 | :end-mark ,(point-marker))))))) 38 | ;; マーカーを元に編集する 39 | (dolist (pos-info pos-list) 40 | (goto-char (plist-get pos-info :mark)) 41 | (delete-region (point) 42 | (plist-get pos-info :end-mark)) 43 | (insert new-symbol)) 44 | (backward-char) 45 | (lctags-highlight-at-op lctags-search-token-color-default) 46 | )))))) 47 | 48 | 49 | (provide 'lctags-rename) 50 | -------------------------------------------------------------------------------- /src/lisp/lctags-const.el: -------------------------------------------------------------------------------- 1 | (defconst lctags-servlet-api-info-table 2 | `(("dumpDir" (:param ("--lctags-form" "json"))) 3 | ("matchFile" (:param ("?pattern" "?option" "--lctags-form" "json" ))) 4 | ("searchFile" (:param (("?path" ,(lambda (val) 5 | (lctags-replace-txt val "_" "$_"))) 6 | "--lctags-form" "json" 7 | "--lctags-candidateLimit" "?limit"))) 8 | ("searchDecl" (:param (("?name" ,(lambda (val) 9 | (lctags-replace-txt val "_" "$_"))) 10 | "--lctags-form" "json" 11 | "--lctags-candidateLimit" "?limit"))) 12 | ("defAtFileId" (:param ("?fileId" "--lctags-form" "json" ))) 13 | ("callee" (:param ("?nsId" "--lctags-form" "json" ))) 14 | ("caller" (:param ("?nsId" "--lctags-form" "json" ))) 15 | ("refSym" (:param ("?nsId" "--lctags-form" "json" ))) 16 | ("refDir" (:param ("?path" "--lctags-form" "json" ))) 17 | ("refFile" (:param ("?fileId" "?path" "--lctags-form" "json" ))) 18 | ("reqDir" (:param ("?path" "--lctags-form" "json" ))) 19 | ("reqFile" (:param ("?fileId" "?path" "--lctags-form" "json" ))) 20 | ("decl" (:param ("?nsId" "--lctags-form" "json" ))) 21 | ("openDecl" (:param (decl "inq" "decl" "?nsId") 22 | :func lctags-servlet-open-pos)) 23 | ("callPair" (:param (callPair "inq" "callPair" "?nsId" "?belongNsId") 24 | :func lctags-servlet-open-pos)) 25 | ("refPair" (:param (refPair "inq" "refPair" "?nsId" "?belongNsId") 26 | :func lctags-servlet-open-pos))) 27 | "REST API information table. 28 | '(API (:param PARAM-LIST :func FUNC)) 29 | 30 | - API: API name 31 | - PARAM-LIST: parameter list. 32 | if type of parameter is string and the parameter starts with ?, use from query. 33 | if type of parameter is string and the parameter does not starts with ?, use raw. 34 | if type of parameter is the list, the list consists of (val func). 35 | - FUNC: if FUNC is specified, servlet process the form (apply func param). 36 | ") 37 | 38 | (provide 'lctags-const) 39 | -------------------------------------------------------------------------------- /src/lctags/testDB.lua: -------------------------------------------------------------------------------- 1 | local log = require( 'lctags.LogCtrl' ) 2 | local DBCtrl = require( 'lctags.DBCtrl' ) 3 | local Helper = require( 'lctags.Helper' ) 4 | 5 | 6 | local path = "test.sqlite3" 7 | local currentDir = Util:getcwd() 8 | 9 | local dataCount = 100000 10 | 11 | if not arg[ 1 ] then 12 | DBCtrl:init( path, currentDir, 0, 0, 0 ) 13 | log( 1, "init", os.clock() ) 14 | 15 | Helper.msleep( 3000 ) 16 | 17 | local db = DBCtrl:open( path, false, currentDir ) 18 | 19 | log( 1, "open writeDb", os.clock() ) 20 | 21 | for index = 1, dataCount do 22 | db:insert( 23 | "namespace", 24 | string.format( "NULL, %d, %d, '%s', '%s', '%s', 1", 25 | 1, index, "", 26 | string.format( "name_eiaohap_%d", index ), 27 | string.format( "name_eiaohap_%d", index ) ) ) 28 | end 29 | 30 | log( 1, "insert", os.clock() ) 31 | 32 | db:close() 33 | 34 | db = DBCtrl:open( path, false, currentDir ) 35 | 36 | for index = 1, dataCount do 37 | local name = string.format( "name_fiaojo_%d", index ) 38 | db:insert( 39 | "namespace", 40 | string.format( "NULL, %d, %d, '%s', '%s', '%s', 1", 1, index, "", name, name ) ) 41 | if not db:getRow( "namespace", 42 | string.format( "name = '%s'", name ) ) then 43 | log( 1, "not found id" ) 44 | os.exit( 1 ) 45 | end 46 | end 47 | 48 | log( 1, "insert and select", os.clock() ) 49 | 50 | db:close() 51 | end 52 | 53 | 54 | db = DBCtrl:open( path, true, currentDir ) 55 | 56 | log( 1, "open read", os.clock() ) 57 | 58 | while true do 59 | for index = 1, dataCount do 60 | if not db:getRow( "namespace", 61 | string.format( "name = '%s'", 62 | string.format( "name_eiaohap_%d", index ) ) ) then 63 | if not arg[ 1 ] then 64 | log( 1, "not found name" ) 65 | os.exit( 1 ) 66 | end 67 | end 68 | end 69 | 70 | log( 1, "select name", os.clock() ) 71 | 72 | for index = 1, dataCount do 73 | if not db:getRow( "namespace", 74 | string.format( "id = %d", index ) ) then 75 | if not arg[ 1 ] then 76 | log( 1, "not found id" ) 77 | os.exit( 1 ) 78 | end 79 | end 80 | end 81 | 82 | log( 1, "select id", os.clock() ) 83 | end 84 | 85 | db:close() 86 | -------------------------------------------------------------------------------- /src/lctags/gcc.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding:utf-8 -*- 2 | -- Copyright (C) 2017 ifritJP 3 | 4 | local log = require( 'lctags.LogCtrl' ) 5 | 6 | local gcc = {} 7 | 8 | local function processParen( arg, macroParen ) 9 | for paren in string.gmatch( arg, "[()]" ) do 10 | if paren == "(" then 11 | macroParen = macroParen + 1 12 | else 13 | macroParen = macroParen - 1 14 | if macroParen < 0 then 15 | log( 1, "unmatch arg paren", arg ) 16 | os.exit( 1 ) 17 | end 18 | end 19 | end 20 | return macroParen 21 | end 22 | 23 | function gcc:createCompileOptionConverter( compiler ) 24 | if compiler ~= "gcc" then 25 | return nil 26 | end 27 | local obj = { 28 | nextType = nil, 29 | macroParen = 0, 30 | convert = function( self, arg ) 31 | if compiler == "gcc" then 32 | if self.nextType == "skip" then 33 | self.nextType = nil 34 | return "skip" 35 | elseif self.nextType == "opt" then 36 | self.nextType = nil 37 | return "opt", arg 38 | elseif self.nextType == "macroParen" then 39 | self.macroParen = processParen( arg, self.macroParen ) 40 | if self.macroParen == 0 then 41 | self.nextType = nil 42 | end 43 | return "opt", arg 44 | end 45 | if string.find( arg, "^-" ) then 46 | if string.find( arg, "^-[IDo]" ) then 47 | if string.find( arg, "^-D" ) then 48 | self.macroParen = processParen( arg, self.macroParen ) 49 | if self.macroParen > 0 then 50 | self.nextType = "macroParen" 51 | end 52 | end 53 | if arg == "-I" then 54 | self.nextType = "opt" 55 | end 56 | if arg == "-o" then 57 | self.nextType = "skip" 58 | return "skip" 59 | end 60 | return "opt", arg 61 | elseif string.find( arg, "-std=", 1, true ) then 62 | return "opt", arg 63 | elseif arg == "-include" then 64 | self.nextType = "skip" 65 | return "skip" 66 | end 67 | return "skip" 68 | end 69 | return "src", arg 70 | end 71 | end, 72 | } 73 | return obj 74 | end 75 | 76 | function gcc:getDefaultOptionList( compiler ) 77 | return {} 78 | end 79 | 80 | return gcc 81 | -------------------------------------------------------------------------------- /src/lctags/armcc.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding:utf-8; mode:lua -*- 2 | 3 | local log = require( 'lctags.LogCtrl' ) 4 | 5 | local config = {} 6 | 7 | function config:getIgnorePattern() 8 | return { 9 | -- { "simple", "ignore.c" }, -- this is simple match. 10 | -- { "lua", "^ignore.c$" }, -- this is lua pattern match. 11 | } 12 | end 13 | 14 | local function processParen( arg, macroParen ) 15 | for paren in string.gmatch( arg, "[()]" ) do 16 | if paren == "(" then 17 | macroParen = macroParen + 1 18 | else 19 | macroParen = macroParen - 1 20 | if macroParen < 0 then 21 | log( 1, "unmatch arg paren", arg ) 22 | os.exit( 1 ) 23 | end 24 | end 25 | end 26 | return macroParen 27 | end 28 | 29 | 30 | --[[ 31 | This method is compile option converter from your compiler to clang. 32 | This is sample for armcc. 33 | ]] 34 | function config:createCompileOptionConverter( compiler ) 35 | if compiler ~= "armcc" then 36 | return nil 37 | end 38 | local obj = { 39 | macroParen = 0, 40 | nextType = nil, 41 | convert = function( self, arg ) 42 | if compiler == "armcc" then 43 | if self.nextType == "skip" then 44 | self.nextType = nil 45 | return "skip" 46 | elseif self.nextType == "opt" then 47 | self.nextType = nil 48 | return "opt", arg 49 | elseif self.nextType == "macroParen" then 50 | self.macroParen = processParen( arg, self.macroParen ) 51 | if self.macroParen == 0 then 52 | self.nextType = nil 53 | end 54 | return "opt", arg 55 | end 56 | if string.find( arg, "^-" ) then 57 | if string.find( arg, "^-[JDoI]" ) then 58 | if string.find( arg, "^-D" ) then 59 | self.macroParen = processParen( arg, self.macroParen ) 60 | if self.macroParen > 0 then 61 | self.nextType = "macroParen" 62 | end 63 | end 64 | if arg == "-J" then 65 | self.nextType = "opt" 66 | end 67 | if arg == "-o" then 68 | self.nextType = "skip" 69 | return "skip" 70 | end 71 | if string.find( arg, "^-J" ) then 72 | arg = string.gsub( arg, "^-J", "-I" ) 73 | end 74 | return "opt", arg 75 | end 76 | return "skip" 77 | end 78 | return "src", arg 79 | end 80 | end, 81 | } 82 | return obj 83 | end 84 | 85 | function config:getDefaultOptionList( compiler ) 86 | return {} 87 | end 88 | 89 | function config:getClangIncPath() 90 | return nil -- clang include path 91 | end 92 | 93 | 94 | return config 95 | -------------------------------------------------------------------------------- /doc/register.org: -------------------------------------------------------------------------------- 1 | #+AUTHOR: ifritJP 2 | #+STARTUP: nofold 3 | #+TITLE: lctags のプロジェクト簡易登録 4 | 5 | lctags を利用するには、 6 | 解析対象のソースファイル毎にコンパイルオプションを登録する必要があります。 7 | 8 | これは、 lctags の解析処理のバックエンドとして利用している libclang が 9 | 正確なコンパイルオプションを必要としているためです。 10 | 11 | libclang を利用することで正確な構文解析を可能としていますが、 12 | この登録作業は案外面倒なものです。 13 | 14 | ちなみに、lctags のコンパイルオプションの登録方法として、 15 | 次の 2 つの方法をサポートしています。 16 | 17 | - ファイル毎に lctags build コマンドを実行しコンパイルオプションを指定する 18 | - 全ソースのコンパイルオプションが記載された JSON Compilation Database を用意し、 19 | lctags register コマンドを実行する 20 | 21 | cmake を利用しているプロジェクトでは、 22 | ~CMAKE_EXPORT_COMPILE_COMMANDS:BOOL~ を ON にすることで 23 | JSON ファイルを生成できます。 24 | また、GNU make の場合は https://github.com/rizsotto/Bear を利用することで、 25 | JSON ファイルを生成できます。 26 | 27 | JSON Compilation Database を利用することで、登録作業を簡単化出来ますが、 28 | GNU global と比べるとまだ手軽さで劣ります。 29 | 30 | ある程度の設定は仕方がないところではありますが、 31 | 小さなプロジェクトやサンプルコードなどでは、 32 | そもそもコンパイルオプションが不要な場合もあります。 33 | 34 | そのような場合に、 lctags をもっと手軽に使いたいです。 35 | 36 | ここでは GNU global レベルの簡単さで、lctags の登録が行なえる方法について説明します。 37 | 38 | * 登録方法 39 | 40 | 次のコマンドを実行するだけで、 lctags に登録が行なえます。 41 | 42 | #+BEGIN_SRC txt 43 | $ cd /proj/top/dir 44 | $ lctags init . 45 | $ lctags lazyUpdate -j 10 46 | #+END_SRC 47 | 48 | 上記の lctags init コマンドは、通常の lctags のプロジェクト初期化方法と同じです。 49 | これにより、 lctags の DB を初期化します。 50 | これは初回だけ行なえば OK です。 51 | 52 | 次の lctags lazyUpdate が、今回紹介する簡易登録用のコマンドです。 53 | 54 | このコマンドにより、次の処理を行ないます。 55 | 56 | - プロジェクトのディレクトリ以下のソースファイルを全て検索する 57 | - 解析対象として登録する 58 | - 登録された全てのファイルを解析する 59 | - このとき差分のあるファイルだけ更新する 60 | 61 | * 追加設定 62 | 63 | このコマンドで登録するコンパイルオプションは、 64 | プロジェクトディレクトリ以下の全ディレクトリを INCLUDE PATH とします。 65 | 66 | これ以外のコンパイルオプションが必要な場合は、次の設定が必要です。 67 | 68 | *** 追加のコンパイルオプション設定方法 69 | 70 | コンパイルオプションを追加で設定するには、 71 | lctags.conf ファイルを作成する必要があります。 72 | 73 | lctags.conf ファイルは次のコマンドで作成します。 74 | 75 | #+BEGIN_SRC txt 76 | $ lctags copyConf 77 | #+END_SRC 78 | 79 | なお、このコマンドは lctags init . コマンド後に実行する必要があります。 80 | 81 | lctags.conf は、lua ファイルになっています。 82 | このファイルの config:getDefaultOptionList() を適宜編集します。 83 | 84 | 例えば -DHOGE, -I/inc/path を追加する場合、次のように設定します。 85 | 86 | #+BEGIN_SRC lua 87 | function config:getDefaultOptionList( compiler ) 88 | return { 89 | "-DHOGE", 90 | "-I/inc/path" 91 | } 92 | end 93 | #+END_SRC 94 | 95 | これにより、全てのファイルのコンパイルオプションが追加されます。 96 | 97 | * 適応条件 98 | 99 | lctags lazyUpdate コマンドを適応して正しく解析できるプロジェクトには 100 | 下記の条件があります。 101 | 102 | - 全てのソースファイルのコンパイルオプションを同じに設定可能 103 | - コンパイル時のカレントディレクトリに影響せずにコンパイル可能 104 | 105 | この条件を 1 つでも満さない場合は、lctags lazyUpdate で正しく解析できません。 106 | -------------------------------------------------------------------------------- /doc/modulegraph.org: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #+AUTHOR: ifritJP 3 | #+STARTUP: nofold 4 | #+OPTIONS: ^:{} 5 | 6 | #+TITLE: C言語/C++ コードを解析してインタラクティブなモジュール構成図っぽいグラフ表示 by lctags 7 | 8 | lctags で解析した C言語/C++ コードの情報を基に、 9 | インタラクティブなモジュール構成っぽい図を表示できるように対応しました。 10 | 11 | * モジュール構成っぽい図とは 12 | 13 | 「モジュール構成っぽい図」の説明の前に、 14 | まずはモジュール構成図について簡単に説明します。 15 | 16 | ここで言うモジュール構成図とは、モジュール間の関連を示すソフトウェア構成図を指します。 17 | ULM のコンポーネント図のようなものというと、伝わるでしょうか? 18 | 19 | じゃぁ「モジュール構成っぽい図」ってなんやねん?というと、 20 | モジュール構成図のように、「モジュール間の関連が分かる図」ということです。 21 | 22 | あくまで「モジュール構成っぽい図」であって、モジュール構成図ではないです。 23 | 24 | まぁ、そもそもモジュール構成図って 25 | ULM のコンポーネント図のような明確な定義がないので、 26 | 「モジュール構成っぽい図」の「っぽい」も何もないんだけど 27 | 「モジュール構成図」と書いてしまうと 28 | 「想像してたものと違う」とかクレームが付く可能性もあるんで、 29 | 「っぽい」を付けています。 30 | 要は「ナポリ風ピザ」みたいなモノです。 31 | 32 | 以降では「モジュール構成っぽい図」と書くのは面倒なので、「モジュール構成図」と書きます。 33 | 34 | * サンプル 35 | 36 | 文書で色々説明するよりサンプルを見てもらった方が理解が早いと思うので、 37 | 次にサンプル画像を載せます。 38 | 39 | [[https://raw.githubusercontent.com/ifritJP/lctags/master/doc/module.graph.curl.png]] 40 | 41 | これは、 curl コマンドの cookie.c のモジュール構成図です。 42 | 43 | パッと見、コールグラフと同じですが、これが lctags が生成するモジュール構成図です。 44 | 45 | この図の見方は、次の通りです。 46 | - 左端のノードが注目しているモジュールを示します。 47 | - そのノードに繋がっているノードは全て関連情報を示しています。 48 | - 各ノードはディレクトリ、ファイル、シンボルのいずれかを示します。 49 | - このサンプルの場合、cookie.c で定義する関数やシンボルを 50 | url.c, transfer.c, http.c, easy.c, share.c, getinfo.c で参照していることを示します。 51 | 52 | 53 | 関数単位のコールグラフと違い、 54 | ファイル単位にすることで、モジュール間の依存関係が分かり易くなります。 55 | 56 | なお、上部にある「refSym」を「reqSym」に切り替えると、 57 | cookie.c が参照するシンボルの依存関係を表示します。 58 | 59 | なお、 lctags で表示可能なモジュール構成図は、 60 | ファイル単位とディレクトリ単位の 2 つです。 61 | 62 | ファイル単位では上記のサンプルのように、 63 | あるファイルが定義する(あるいは参照する)シンボルのモジュール間の依存関係を表示します。 64 | 65 | 一方ディレクトリ単位では、 66 | あるディレクトリ以降で定義する(あるいは参照する)シンボルのモジュール間の 67 | 依存関係を表示します。 68 | 69 | ある程度の規模のソフトウェアを開発する場合、 70 | 関連する機能ごとにディレクトリを作成し、 71 | それぞれのディレクトリ以下にソースを配置していくと思います。 72 | そのような場合、ファイル単位ではなくディレクトリ単位のモジュール構成図を見ると、 73 | 意味のある図になると思います。 74 | 75 | * 使い方 76 | 77 | 以降では、モジュール構成図の使い方について説明します。 78 | 79 | ** 表示方法 80 | 81 | コールグラフのディレクトリ一覧を表示している画面に、 82 | module graph ボタンをディレクトリごとに追加しています。 83 | このボタンを押すことで、ディレクトリ単位でのモジュール構成図を表示します。 84 | 85 | ファイル単位のモジュール構成図は、 86 | ディレクトリボタンを押してファイルリストを表示し、 87 | ディレクトリと同じように表示される module graph ボタンを押すと、 88 | ファイル単位のモジュール構成図を表示します。 89 | 90 | ** 操作方法 91 | 92 | モジュール構成図の操作方法は、 93 | 基本的にコールグラフと同じですが、次の点に注意してください。 94 | 95 | - モジュール構成図は最初のノード展開時に、所定モジュール間の全ての参照情報を取得します。 96 | - これにより、最初のノード展開に時間が掛ります。 97 | - ファイル単位のモジュール構成図の場合は、気にならないレベルだと思います。 98 | - ディレクトリ単位のモジュール構成図の場合は、ディレクトリ内のファイル数に依存して処理時間が掛ります。 99 | - 情報取得中は、モジュールグラフの上部にプログレスバーを表示します。 100 | - ディレクトリ単位のモジュール構成図の場合は、ディレクトリ内の依存関係は表示しません。 101 | - ディレクトリ単位のモジュール構成図は、所定のディレクトリ以下のサブディレクトリを含む全てのファイルを対象にします。 102 | - モジュール構成図では、シンボル参照と関数コールを区別しません。 103 | 104 | * 関連 105 | 106 | lctags のコールグラフ表示については、次を参照してください。 107 | 108 | - C/C++ コードを解析してインタラクティブなコールグラフ表示 by lctags 109 | https://qiita.com/dwarfJP/items/ef868813a7aaa2572468 110 | 111 | 112 | lctags 全般の紹介は次を参照してください。 113 | 114 | - C/C++ ソースコードタグシステム lctags の紹介 115 | https://qiita.com/dwarfJP/items/982ef7ee3f3bfd435156 116 | -------------------------------------------------------------------------------- /doc/windows.org: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #+AUTHOR: ifritJP 3 | #+STARTUP: nofold 4 | #+OPTIONS: ^:{} 5 | 6 | #+TITLE: Windows で lctags (C言語/C++ のタグジャンプ、コールグラフ、etc..) 7 | 8 | C/C++ のコーディングサポートツールとして lctags を開発していますが、 9 | Linux 上で動作させることを想定して開発しています。 10 | 11 | とはいえ、日本のエンジニアは Windows を使用している方が多いと思います。 12 | 特に日本の企業では、会社の基幹システムが MS 系アプリ前提で構築されていることが多いため、 13 | 必然的に Windows を使用することになると思います。 14 | 15 | そこで、ここでは Windows 上で lctags を利用する方法について説明します。 16 | 17 | 18 | なお、PC のシステム管理者権限が制限されている残念な環境では、 19 | 今回説明する方法は利用できません。 20 | 21 | * cygwin 22 | 23 | Windows で lctags を動かすには、 cygwin が必要です。 24 | 25 | Windows10 で Windows Subsystem for Linux が使用可能になりましたが、 26 | そちらの環境では動作確認していません。 27 | 28 | cygwin では、次のパッケージを導入してください。 29 | 30 | - lua 5.2.4 31 | - lua-devel 5.2.4 32 | - swig 3.0.12 33 | - libclang 5.0.1-2 34 | - libclang-devel 5.0.1-2 35 | - openssl 36 | - openssl-devel 37 | - curl 38 | - make 39 | - unzip 40 | - gcc 41 | 42 | 必ず環境変数の PATH 設定で、 cygwin が先頭に来るように設定してください。 43 | find 等のコマンドが、cygwin ではなく Windows 側のコマンドを使われると正常に動作しません。 44 | 45 | 46 | * lctags のビルド 47 | 48 | lctags のビルド手順を示します。 49 | 50 | ** lctags を clone 51 | 52 | git で lctags を clone してください。 53 | 54 | #+BEGIN_SRC txt 55 | $ git clone https://github.com/ifritJP/lctags.git 56 | #+END_SRC 57 | 58 | ** lctags/src/makefile の編集 59 | 60 | lctags/src/makefile の次の項目を編集してください。 61 | 62 | #+BEGIN_SRC txt 63 | SO=dll 64 | LUA_COMMAND=lua 65 | LUA_INC=/usr/include 66 | LUA_LDFLAGS= 67 | LUA_SO=-llua 68 | LIBCLANG_INC=/usr/include 69 | LIBCLANG_LIB=/lib 70 | LIBCLANG_STD_INC=/lib/clang/5.0.1/include 71 | #+END_SRC 72 | 73 | ** luasqlite3 のダウンロード 74 | 75 | 次のコマンドを実行。 76 | 77 | #+BEGIN_SRC txt 78 | $ make download_luasqlite3 [PROXY=http://proxy.hoge:port] 79 | #+END_SRC 80 | 81 | PROXY が必要な場合は、PROXY を指定してください。 82 | 83 | ** ビルド&インストール 84 | 85 | 次のコマンドを実行。 86 | 87 | #+BEGIN_SRC txt 88 | $ make build 89 | $ make install 90 | #+END_SRC 91 | 92 | ** ~/.bashrc の編集 93 | 94 | ~/.bashrc に次を追加。 95 | 96 | #+BEGIN_SRC sh 97 | export PATH=/usr/local/bin:${PATH} 98 | #+END_SRC 99 | 100 | cygwin のコンソールを再起動。 101 | 102 | 以上で、 cygwin のコンソールから lctags を使用できます。 103 | 104 | * Windows の lctags について 105 | 106 | ここまで Windows で lctags を使うための方法を説明してきてアレですが、 107 | 個人的には Windows 上で lctags を利用するのはオススメしません。 108 | 109 | それは、Linux に比べると Windows はオーバーヘッドが大きく、 110 | その分 C/C++ コード解析に時間が掛るためです。 111 | 112 | Windows と Linux とで、 113 | Lua-5.3.4 ソースの解析にかかる時間を次に示します。 114 | なお時間計測に利用した PC は、Windows, Linux ともに同一のものです。 115 | 116 | - linux 117 | 118 | #+BEGIN_SRC txt 119 | real 0m12.931s 120 | user 0m16.061s 121 | sys 0m6.421s 122 | #+END_SRC 123 | 124 | - Windows 125 | 126 | #+BEGIN_SRC txt 127 | real 0m20.396s 128 | user 0m32.066s 129 | sys 0m17.433s 130 | #+END_SRC 131 | 132 | 上記の結果から、Windows の方が倍近い時間が掛っていることが分かります。 133 | 134 | Windows で lctags を使用するよりは、 135 | Windows 上に Linux の VM を動かし、 136 | VM 上で lctags を使用する方がパフォーマンスが上がります。 137 | 138 | Linux を利用出来ない特別な理由がない限り、 139 | Linux で lctags を使用することをオススメします。 140 | 141 | * 参考 142 | 143 | lctags 全般の紹介は次を参照してください。 144 | 145 | - C/C++ ソースコードタグシステム lctags の紹介 146 | https://qiita.com/dwarfJP/items/982ef7ee3f3bfd435156 147 | -------------------------------------------------------------------------------- /src/lctags/config.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding:utf-8; mode:lua -*- 2 | 3 | local gcc = require( 'lctags.gcc' ) 4 | local log = require( 'lctags.LogCtrl' ) 5 | 6 | local config = {} 7 | 8 | config.conf = gcc 9 | 10 | function config:loadConfig( path, exitOnErr ) 11 | local fileHandle = io.open( path, "r" ) 12 | if fileHandle then 13 | fileHandle:close() 14 | local chunk, err = loadfile( path ) 15 | if chunk then 16 | self.conf = chunk() 17 | return self 18 | end 19 | print( err ) 20 | end 21 | if exitOnErr then 22 | print( "loadfile error", err ) 23 | os.exit( 1 ) 24 | end 25 | return nil 26 | end 27 | 28 | function config:hasUserConf() 29 | return self.conf ~= gcc 30 | end 31 | 32 | function config:getIgnorePattern() 33 | if self.conf and self.conf.getIgnorePattern then 34 | return self.conf:getIgnorePattern() 35 | end 36 | return { 37 | -- { "simple", "ignore.c" }, -- this is simple match. 38 | -- { "lua", "^ignore.c$" }, -- this is lua pattern match. 39 | } 40 | end 41 | 42 | --[[ 43 | This method is compile option converter from your compiler to clang. 44 | ]] 45 | function config:createCompileOptionConverter( compiler ) 46 | if self.conf and self.conf.createCompileOptionConverter then 47 | return self.conf:createCompileOptionConverter( compiler ) 48 | end 49 | return nil 50 | end 51 | 52 | function config:getDefaultOptionList( compiler ) 53 | if self.conf and self.conf.getDefaultOptionList then 54 | return self.conf:getDefaultOptionList( compiler ) 55 | end 56 | return {} 57 | end 58 | 59 | function config:getClangIncPath() 60 | if self.clangIncPath then 61 | return self.clangIncPath 62 | end 63 | 64 | if self.conf and self.conf.getClangIncPath then 65 | local path = self.conf:getClangIncPath() 66 | if path and path ~= "" then 67 | self.clangIncPath = string.gsub( path, "/$", "" ) 68 | return self.clangIncPath 69 | end 70 | end 71 | 72 | self.clangIncPath = nil -- replase by install 73 | if self.clangIncPath then 74 | return self.clangIncPath 75 | end 76 | 77 | local clangVer = require( 'libclanglua.if' ).getClangVersion() 78 | clangVer3 = string.gsub( 79 | clangVer, "^clang version (%d+)%.(%d+)%.(%d+)[^%d].*", "%1.%2.%3" ) 80 | clangVer2 = string.gsub( clangVer3, "^(%d+)%.(%d+)[^%d].*", "%1.%2" ) 81 | 82 | 83 | self.clangIncPath = string.format( "/usr/lib/llvm-%s/lib/clang/%s/include", 84 | clangVer2, clangVer3 ) 85 | return self.clangIncPath 86 | end 87 | 88 | 89 | function config:getClangIncPathOp() 90 | if not self.clangIncPathOp then 91 | local path = self:getClangIncPath() 92 | if not path then 93 | self.clangIncPathOp = "" 94 | else 95 | self.clangIncPathOp = string.format( "-I%s", path ) 96 | end 97 | end 98 | return self.clangIncPathOp 99 | end 100 | 101 | function config:getIndirectFuncList( symbol, mode ) 102 | if self.conf and self.conf.getIndirectFuncList then 103 | return self.conf:getIndirectFuncList( symbol, mode ) 104 | end 105 | return {} 106 | end 107 | 108 | 109 | return config 110 | -------------------------------------------------------------------------------- /src/lctags/LogCtrl.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2017 ifritJP 2 | 3 | local Helper = require( 'lctags.Helper' ) 4 | 5 | local displayLevel = 1 6 | local prefix = "lctags:" 7 | 8 | local LogCtrl = {} 9 | 10 | LogCtrl.lastMessage = "" 11 | 12 | function LogCtrl:log( level, ... ) 13 | local logLevel = level 14 | local param = { ... } 15 | 16 | if level == nil and ... == nil then 17 | local info = debug.getinfo( 2 ) 18 | print( info.source, info.currentline ) 19 | end 20 | if type( level ) ~= "number" then 21 | logLevel = 3 22 | table.insert( param, 1, level ) 23 | end 24 | if logLevel == 0 then 25 | local prev = displayLevel 26 | displayLevel = ... 27 | if displayLevel < 0 then 28 | displayLevel = prev 29 | end 30 | return prev 31 | elseif logLevel == -1 then 32 | prefix = ... 33 | return 34 | elseif logLevel == -3 then 35 | return prefix 36 | elseif logLevel == -4 then 37 | return displayLevel 38 | end 39 | if logLevel > displayLevel then 40 | if logLevel <= 2 and self.server then 41 | local message = "" 42 | for index, val in ipairs( table.pack( ... ) ) do 43 | message = message .. tostring( val ) .. "\t" 44 | end 45 | self.lastMessage = message 46 | self.server:requestUpdateStatus( self.statusName, message ) 47 | end 48 | return 49 | end 50 | if #param == 1 and type( param[ 1 ] ) == "function" then 51 | self:raw( logLevel, param[ 1 ]() ) 52 | else 53 | self:raw( logLevel, table.unpack( param ) ) 54 | end 55 | end 56 | 57 | function LogCtrl:getLastMessage() 58 | return self.lastMessage 59 | end 60 | 61 | function LogCtrl:raw( logLevel, ... ) 62 | if logLevel > displayLevel then 63 | return 64 | end 65 | if prefix ~= "" then 66 | print( prefix, logLevel, ... ) 67 | else 68 | print( logLevel, ... ) 69 | end 70 | if logLevel <= 2 and self.server then 71 | local message = "" 72 | for index, val in ipairs( table.pack( ... ) ) do 73 | message = message .. "\t" .. tostring( val ) 74 | end 75 | self.server:requestUpdateStatus( self.statusName, message ) 76 | end 77 | if logLevel == -2 then 78 | local debugInfo = debug.getinfo( 2 ) 79 | local debugInfo2 = debug.getinfo( 3 ) 80 | local debugInfo3 = debug.getinfo( 4 ) 81 | local debugInfo4 = debug.getinfo( 5 ) 82 | local debugInfo5 = debug.getinfo( 6 ) 83 | local debugInfo6 = debug.getinfo( 7 ) 84 | local debugInfo7 = debug.getinfo( 8 ) 85 | print( debugInfo.short_src, debugInfo.currentline, 86 | "\n", debugInfo2.short_src, debugInfo2.currentline, 87 | "\n", debugInfo3.short_src, debugInfo3.currentline, 88 | "\n", debugInfo4.short_src, debugInfo4.currentline, 89 | "\n", debugInfo5.short_src, debugInfo5.currentline, 90 | "\n", debugInfo6.short_src, debugInfo6.currentline, 91 | "\n", debugInfo7.short_src, debugInfo7.currentline ) 92 | end 93 | end 94 | 95 | function LogCtrl:calcTime( id, param ) 96 | local nowTime = Helper.getTime( true ) 97 | if self.prevTime then 98 | self:log( 2, "calcTime:", self.prevTimeId, self.prevTimeParam, nowTime - self.prevTime ) 99 | end 100 | self:log( 2, "calcTime:", "start", id, param ) 101 | self.prevTime = nowTime 102 | self.prevTimeId = id 103 | self.prevTimeParam = param 104 | end 105 | 106 | 107 | function LogCtrl:setStatusServer( server, name ) 108 | self.server = server 109 | self.statusName = name 110 | end 111 | 112 | function LogCtrl:openDB( readonly ) 113 | if self.server then 114 | self.server:requestNotifyOpenClose( true, readonly ) 115 | end 116 | end 117 | 118 | function LogCtrl:closeDB( readonly ) 119 | if self.server then 120 | self.server:requestNotifyOpenClose( false, readonly ) 121 | end 122 | end 123 | 124 | 125 | setmetatable( LogCtrl, { __call = function( func, ... ) return LogCtrl:log( ... ) end } ) 126 | 127 | return LogCtrl 128 | -------------------------------------------------------------------------------- /doc/callFunc.org: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #+AUTHOR: ifritJP 3 | #+STARTUP: nofold 4 | 5 | #+TITLE: lctags で C 言語の関数コールを簡単に 6 | 7 | ある関数をコールする際、その関数の名前がうろ覚えの場合や、 8 | 使いたい関数がどのヘッダで定義しているか分からないケースが多いと思います。 9 | 10 | lctags では、そのような場合でも簡単に関数補完できる方法を提供しています。 11 | 12 | lctags 全般の紹介は次を参照してください。 13 | 14 | - C/C++ ソースコードタグシステム lctags の紹介 15 | https://qiita.com/dwarfJP/items/982ef7ee3f3bfd435156 16 | 17 | 18 | * 使用方法 19 | 20 | 関数コールしたい箇所で M-x lctags-insert-call-func します。 21 | 22 | あるいは、C-c l i c を入力します。 23 | 24 | mini buffer に funcname-pattern: の入力が求められるので、 25 | 使用したい関数名の一部を入力します。 26 | 27 | 入力した関数名にマッチする関数の一覧がリストされるので、 28 | そのリストから関数を選択します。 29 | 30 | もしも、その関数を定義するインクルードファイルをインクルードしていなかった場合、 31 | インクルードすべきファイルがリストされます。 32 | 33 | なお、使用したい関数を定義しているヘッダーファイルを、 34 | 事前に lctags に登録しておく必要があります。 35 | 36 | * hello world サンプル 37 | 38 | [[https://www.youtube.com/watch?v=gMgyNMBmqws][https://www.youtube.com/watch?v=gMgyNMBmqws]] 39 | 40 | 上記リンクは、実際に emacs で操作した際の解説動画です。 41 | 42 | simple.c で hello world 出力するサンプルです。 43 | 44 | ** DB 生成 45 | 46 | まずはプロジェクトディレクトリのトップで DB を生成します。 47 | 48 | #+BEGIN_SRC txt 49 | $ lctags init . 50 | #+END_SRC 51 | 52 | ** ソース登録 53 | 54 | プロジェクトのソースを登録します。 55 | ここでは simple.c を登録します。 56 | 57 | #+BEGIN_SRC txt 58 | $ lctags addStdInc 59 | $ lctags build gcc test/simple.c 60 | #+END_SRC 61 | 62 | addStdInc は、C の標準ヘッダを登録します。 63 | 64 | simple.c は次の内容です。 65 | 66 | #+BEGIN_SRC c 67 | int main() { 68 | const char * hello = "hello "; 69 | 70 | } 71 | #+END_SRC 72 | 73 | 74 | ** 関数コール 75 | 76 | simple.c を emacs で開いて 3 行目の空行に移動し、次を入力します。 77 | 78 | ~C-c l i c~ 79 | 80 | 次に mini buffer に、使用した関数名の一部を入力します。 81 | 82 | ここでは write を入力します。 83 | 84 | すると、 関数名に write を含む関数一覧がリストされます。 85 | このリストは、DB に登録してあるファイル内で定義された関数情報を元に生成します。 86 | 87 | リストの中から、 fwrite を選択します。 88 | 89 | すると、次にヘッダファイルの選択になります。 90 | このサンプルでは、何も include していないため、 91 | fwrite を使用するにはヘッダファイルを include する必要があります。 92 | 93 | リストされているヘッダファイルには、 94 | 先頭に fwrite を直接宣言しているヘッダ stdio.h がリストされ、 95 | 残りは stdio.h を include しているヘッダがリストされます。 96 | 97 | ヘッダを選択すると、 98 | fwrite() の関数プロトタイプと、必要なヘッダが展開されます。 99 | 100 | #+BEGIN_SRC c 101 | int main() { 102 | const char * hello = "hello "; 103 | fwrite( const void * __restrict __ptr, size_t __size, size_t __n, FILE * __restrict __s ) => size_t 104 | // #include 105 | } 106 | #+END_SRC 107 | 108 | 次に、fwrite の第 1 引数に hello を指定し、 109 | fwrite の第2引数には strlen( hello ) を指定します。 110 | 111 | strlen をコールするとき、再度関数展開 ~C-c l i c~ を利用します。 112 | 113 | mini buffer には、 len と入力します。 114 | 115 | これにより len を含む関数一覧がリストされるので、strlen を選択します。 116 | 117 | strlen を利用するために必要な string.h がまだ include されていないため、 118 | ヘッダ選択モードになります。 119 | 120 | ヘッダを選択すると、次のようになります。 121 | 122 | #+BEGIN_SRC c 123 | int main() { 124 | const char * hello = "hello "; 125 | fwrite( hello, strlen( const char * __s ) => unsigned long, size_t __n, FILE * __restrict __s ) => size_t 126 | // #include 127 | // #include 128 | } 129 | #+END_SRC 130 | 131 | fwrite の残りの引数を適宜指定し、 132 | 展開された #include をファイル先頭に移動し、コメントを外します。 133 | 134 | #+BEGIN_SRC c 135 | #include 136 | #include 137 | int main() { 138 | const char * hello = "hello "; 139 | fwrite( hello, strlen( hello ), 1, stdout ); 140 | } 141 | #+END_SRC 142 | 143 | 次に world を出力するため 再度関数展開 ~C-c l i c~ を利用します。 144 | 145 | ここでは put を指定します。 146 | リストに表示されている関数から puts を選択すると、次のように展開されます。 147 | 148 | #+BEGIN_SRC c 149 | #include 150 | #include 151 | int main() { 152 | const char * hello = "hello "; 153 | fwrite( hello, strlen( hello ), 1, stdout ); 154 | puts( const char * __s ) => int 155 | } 156 | #+END_SRC 157 | 158 | ここで include が展開されません。 159 | なぜなら puts は stdio.h に定義されており、 160 | stdio.h は既に include 済みだからです。 161 | 162 | 最後に puts() の引数を次のように編集して終了です。 163 | 164 | #+BEGIN_SRC c 165 | #include 166 | #include 167 | int main() { 168 | const char * hello = "hello "; 169 | fwrite( hello, strlen( hello ), 1, stdout ); 170 | puts( "world" ); 171 | } 172 | #+END_SRC 173 | -------------------------------------------------------------------------------- /src/lisp/lctags-insert-func.el: -------------------------------------------------------------------------------- 1 | (require 'lctags-helm) 2 | 3 | (defun lctags-function-item-get-name (item) 4 | (lctags-xml-get-val item 'name) 5 | ) 6 | 7 | (defun lctags-function-item-get-include (item) 8 | (lctags-xml-get-val item 'include) 9 | ) 10 | 11 | (defun lctags-function-item-is-included (item) 12 | (string= (lctags-xml-get-val item 'included) "true") 13 | ) 14 | 15 | 16 | (defun lctags-function-item-get-declaration (item) 17 | (lctags-xml-get-val item 'declaration) 18 | ) 19 | 20 | 21 | 22 | 23 | (defun lctags-function-make-candidates (info candidates-info) 24 | (cons (format "%-30s\t%s" (concat (lctags-function-item-get-name info) ":") 25 | (lctags-conv-disp-path 26 | (lctags-function-item-get-declaration info) t)) 27 | info)) 28 | 29 | (defun lctags-execute-insert-func (src-buf lctags-buf input &rest lctags-opts) 30 | (if (eq (lctags-execute-op src-buf lctags-buf input nil lctags-opts) 0) 31 | (progn 32 | (setq lctags-insert-func-info (lctags-xml-get lctags-buf 'functionList)) 33 | (if (lctags-xml-get-child lctags-insert-func-info 'function) 34 | (setq lctags-diag-info nil) 35 | (setq lctags-diag-info (lctags-xml-get-diag lctags-buf)))) 36 | (setq lctags-insert-func-info nil) 37 | (with-current-buffer lctags-buf 38 | (setq lctags-diag-info `((message nil ,(buffer-string)))))) 39 | ) 40 | 41 | (defun lctags-insert-inc-decide (item) 42 | (let ((path (plist-get item :path))) 43 | (when (string-match "^!" path) 44 | ;; ソート用の ! を除外する 45 | (setq path (substring path 1))) 46 | (setq lctags-insert-inc path) 47 | )) 48 | 49 | (defun lctags-insert-select (item) 50 | (let ((filename (buffer-file-name (current-buffer))) 51 | (name (lctags-function-item-get-name item)) 52 | (include (lctags-function-item-get-include item)) 53 | (is-included (lctags-function-item-is-included item)) 54 | (buffer-txt (buffer-string)) 55 | (pos (point)) 56 | lineno column txt bak-pos mark lctags-insert-inc) 57 | ;; 関数の情報を取得するために、関数を参照するソースを作成 58 | (with-temp-buffer 59 | (insert buffer-txt) 60 | (goto-char pos) 61 | (setq mark (point-marker)) 62 | (beginning-of-buffer) 63 | (when (and (not is-included) include) 64 | (insert (format "#include <%s>\n" include))) 65 | (goto-char mark) 66 | (setq pos (point)) 67 | (setq lineno (lctags-get-line)) 68 | (setq column (lctags-get-column)) 69 | (insert (format "%s;" name )) 70 | (setq txt (buffer-string))) 71 | (setq bak-pos (point)) 72 | ;; 関数の展開 73 | (insert name) 74 | (lctags-expand-function-arg name pos txt lineno column) 75 | (setq mark (point-marker)) 76 | (end-of-line) 77 | ;; 必要な include の挿入 78 | (when (and (not is-included) include) 79 | (let ((lctags-buf (lctags-get-process-buffer t))) 80 | (lctags-execute-heml (current-buffer) lctags-buf nil 81 | "list" "incSrcHeader" include) 82 | (with-current-buffer lctags-buf 83 | (goto-char 1) 84 | ;; ソートで先頭になるように パスに ! を付加する 85 | (insert (format "%-16s %4d !%-16s \n" "path" 1 include)) 86 | ;;(end-of-buffer) 87 | (beginning-of-buffer) 88 | ) 89 | (lctags-select-gtags lctags-buf "select include file" 90 | 'lctags-gtags-select-mode 'lctags-insert-inc-decide) 91 | 92 | (insert (format "\n// #include <%s>" 93 | (lctags-conv-disp-path lctags-insert-inc nil))) 94 | (lctags-execute-op2 (current-buffer) lctags-buf nil nil 95 | "addIncRef" (buffer-file-name) include) 96 | (end-of-line))) 97 | (indent-region bak-pos (point)) 98 | (goto-char mark) 99 | )) 100 | 101 | 102 | (defun lctags-insert-call-func (&optional pattern) 103 | (interactive) 104 | (let ((buffer (lctags-get-process-buffer t)) 105 | candidates) 106 | (when (not pattern) 107 | (setq pattern (read-string "funcname-pattern: "))) 108 | (lctags-execute-insert-func (current-buffer) buffer 109 | (buffer-string) "call-func" 110 | (buffer-file-name) pattern "-i") 111 | (if lctags-diag-info 112 | (lctags-helm-display-diag) 113 | (with-current-buffer buffer 114 | (setq candidates 115 | (delq nil (lctags-candidate-map 116 | (symbol-function 'lctags-function-make-candidates) 117 | lctags-insert-func-info lctags-insert-func-info 118 | 'function ))) 119 | ) 120 | (setq lctags-params 121 | `((name . "insert function") 122 | (candidates . ,candidates) 123 | (action . lctags-insert-select))) 124 | (lctags-helm-wrap lctags-params lctags-heml-map nil) 125 | ))) 126 | 127 | 128 | 129 | (provide 'lctags-insert-func) 130 | -------------------------------------------------------------------------------- /doc/enum.org: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #+AUTHOR: ifritJP 3 | #+STARTUP: nofold 4 | 5 | #+TITLE: C/C++ の enum 補完 by lctags on emacs 6 | 7 | C/C++ でコーディングしていると、 enum を使うことが多いと思います。 8 | 9 | lctags を利用することで、簡単に enum 補完が出来るようになります。 10 | 11 | 他の補完ツールでも、 12 | prefix を打つことでその prefix に続くシンボルを補完することができますが、 13 | lctags では prefix を打つことなく文法を解釈して補完出来ます。 14 | 15 | lctags 全般の紹介は次を参照してください。 16 | 17 | - C/C++ ソースコードタグシステム lctags の紹介 18 | https://qiita.com/dwarfJP/items/982ef7ee3f3bfd435156 19 | 20 | 21 | * 2 項演算 22 | 23 | 次のように enum 型の値に対して 2 項演算する場合に、 24 | enum 型に合せた enum 値の補完ができます。 25 | 26 | #+BEGIN_SRC c 27 | typedef enum { 28 | eval_val1, 29 | eval_val2, 30 | } eval_t; 31 | extern eval_t func(); 32 | static void sub( eval_t val ) 33 | { 34 | if ( func() == 35 | } 36 | #+END_SRC 37 | 38 | 上記 "func() ==" の箇所で補完すると、 39 | func() の戻り値が enum 型であるため、 40 | その enum 値( ~eval_val1~, ~eval_val2~ )を補完候補として表示します。 41 | 42 | もちろん ~==~ 以外の 2 項演算(例えば ~=~ や ~<=~ 等)でも動作しますし、 43 | 演算対象が関数の戻り値ではなく、enum 型の変数でも補完は可能です。 44 | 45 | lctags は、次の情報を認識して補完に利用しています。 46 | - 2 項演算子 "==" を認識する 47 | - 演算対象が enum 型かどうかを判断する 48 | - enum 型であれば、その enum 値を補完候補としてリストする 49 | 50 | 通常 enum 値は、別ファイルに定義していることが多く、 51 | 何を定義しているかを覚えていられません。 52 | 演算対象から補完候補がリスト出来ると便利です。 53 | 54 | なお、 lctags での補完は C-c C-/ です。 55 | 56 | https://gist.githubusercontent.com/ifritJP/e9bd012e0f49f43db3ef230ee50c3fe6/raw/9dd1b8c31a604300a0f0ed75f1037ec54f5a8145/enum1.gif]] 57 | 58 | * return 59 | 60 | 次のように戻り値が enum 型の関数内で return する場合、 61 | enum 型に合せた enum 値の補完ができます。 62 | 63 | #+BEGIN_SRC c 64 | typedef enum { 65 | eval_val1, 66 | eval_val2, 67 | } eval_t; 68 | static eval_t sub( void ) 69 | { 70 | return 71 | #+END_SRC 72 | 73 | 上記 "return " の箇所で補完すると、 74 | この関数の戻り値が enum 型であるため、 75 | その enum 値( ~eval_val1~, ~eval_val2~ )を補完候補として表示します。 76 | 77 | なお、 lctags での補完は C-c C-/ です。 78 | 79 | https://gist.githubusercontent.com/ifritJP/e9bd012e0f49f43db3ef230ee50c3fe6/raw/9dd1b8c31a604300a0f0ed75f1037ec54f5a8145/enum2.gif]] 80 | 81 | * switch-case 82 | 83 | 次のように enum 値で switch するような case 文では、 84 | enum 型に合せた enum 値の補完ができます。 85 | 86 | #+BEGIN_SRC c 87 | typedef enum { 88 | eval_val1, 89 | eval_val2, 90 | } eval_t; 91 | extern eval_t func(); 92 | static void sub( eval_t val ) 93 | { 94 | switch ( func() ) { 95 | case eval_val1: 96 | break; 97 | case 98 | #+END_SRC 99 | 100 | 上記 "case " の箇所で補完すると、 101 | この switch() の値が enum 型であるため、 102 | その enum 値( ~eval_val1~, ~eval_val2~ )を補完候補として表示します。 103 | 104 | なお、 lctags での補完は C-c C-/ です。 105 | 106 | https://gist.githubusercontent.com/ifritJP/e9bd012e0f49f43db3ef230ee50c3fe6/raw/9dd1b8c31a604300a0f0ed75f1037ec54f5a8145/enum3.gif]] 107 | 108 | * enum 値 109 | 110 | enum 値を、同じ enum 型の他の enum 値に補完できます。 111 | 112 | #+BEGIN_SRC c 113 | typedef enum { 114 | eval_val1, 115 | eval_val2, 116 | } eval_t; 117 | static void sub( eval_t val ) 118 | { 119 | if ( val == eval_val1 ) { 120 | } 121 | } 122 | #+END_SRC 123 | 124 | 上記 ~val == eval_val1~ の ~eval_val1~ の箇所で C-c C-x すると、 125 | ~eval_val1~ と同じ型の enum 値( ~eval_val1~, ~eval_val2~ )を補完候補として表示します。 126 | 127 | なお、 enum 型のシンボルからも補完可能です。 128 | 129 | 例えば上記の場合は、 ~eval_t~ から enum 値( ~eval_val1~, ~eval_val2~ ) に 130 | 補完することが可能です。 131 | 132 | https://gist.githubusercontent.com/ifritJP/e9bd012e0f49f43db3ef230ee50c3fe6/raw/9dd1b8c31a604300a0f0ed75f1037ec54f5a8145/enum4.gif]] 133 | 134 | なお、 lctags でのキーバインドは C-c C-x です。 135 | 136 | * 展開 137 | 138 | enum 型で定義されている値一覧を展開出来ます。 139 | 140 | 例えば enum 型で定義されている enum 値と、 enum 名との紐付けを表示するような場合、 141 | 次のようなコードを書く必要があります。 142 | 143 | #+BEGIN_SRC c 144 | typedef enum { 145 | eval_val1, 146 | eval_val2, 147 | } eval_t; 148 | static void display( void ) 149 | { 150 | printf( "eval_val1 = %d\n", eval_val1 ); 151 | printf( "eval_val2 = %d\n", eval_val2 ); 152 | } 153 | #+END_SRC 154 | 155 | lctags の展開機能を利用することで、 156 | この printf() の部分を一つ一つ書くことなく実現出来ます。 157 | 158 | 159 | まず、次のように enum 型を(~eval_t~)書きます。 160 | 161 | #+BEGIN_SRC c 162 | typedef enum { 163 | eval_val1, 164 | eval_val2, 165 | } eval_t; 166 | static void display( void ) 167 | { 168 | eval_t 169 | } 170 | #+END_SRC 171 | 172 | 書いた enum 型(~eval_t~)の所にカーソルを移動して C-c l G E します。 173 | ここで mini buffer で出力フォームを問合せられるので、次を入力します(*要改行*)。 174 | 175 | #+BEGIN_SRC txt 176 | printf( "%s = %%d\n", %s ); 177 | 178 | #+END_SRC 179 | 180 | これにより、指定したフォームの %s の箇所に enum 値が入った文字列が展開されます。 181 | 182 | [[https://gist.githubusercontent.com/ifritJP/e9bd012e0f49f43db3ef230ee50c3fe6/raw/87d3e8bd6eeadcb01f05994f164825af2f93c8f5/enum5.gif]] 183 | 184 | 上記のような単純展開では実現出来ない処理は、 185 | lctags-expand-enum-and-replace-text 関数に適切な引数を与えることで対応可能です。 186 | -------------------------------------------------------------------------------- /src/test/split.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int s_vals[1]; 4 | #define POINTER(X) (&(X)) 5 | #define VAL5P (&val5) 6 | #define SETVAL6 int * pVal6 = &val6; 7 | int func3( int aaa[1][2], int abcddd[10][1], int* pval1, int val2, int * val3, int* pval4 ) 8 | { 9 | int * pVal = POINTER((*pval1)); 10 | int valval2 = -val2; 11 | int valval3 = *(int *)(val3 + 1); 12 | int * pAAA = &aaa[0][0]; 13 | //int * pValval5 = VAL5P; 14 | //SETVAL6; 15 | abcddd[0][0] = (*pval1); 16 | *pVal = 1; 17 | (*pval4) = 2; 18 | s_vals[0] = 3; 19 | *pAAA = 4; 20 | return 1; 21 | } 22 | 23 | void func2( int val1, int val2, int * val3, int val4, int val5, int val6 ) 24 | { 25 | int abcddd[ 10 ][ 1 ] = { 0 }; 26 | int aaa[ 1 ][ 2 ] = { 0 }; 27 | int bbb[ 1 ][ 2 ] = { 0 }; 28 | 29 | func3( aaa, abcddd, &val1, val2, val3, &val4 ); 30 | 31 | printf( "val = %d, s_vals[0]=%d, aaa[0] = %d, val4 = %d, abcddd[0][0] = %d\n", 32 | val1, s_vals[0], aaa[0][0], val4, abcddd[0][0] ); 33 | } 34 | 35 | 36 | int func( int val1, int val2, int * val3, int val4, int val5, int val6 ) 37 | { 38 | int abcddd[ 10 ][ 1 ] = { 0 }; 39 | int aaa[ 1 ][ 2 ] = { 0 }; 40 | int bbb[ 1 ][ 2 ] = { 0 }; 41 | { 42 | int * pVal = POINTER(val1); 43 | int valval2 = -val2; 44 | int valval3 = *(int *)(val3 + 1); 45 | int * pAAA = &aaa[0][0]; 46 | //int * pValval5 = VAL5P; 47 | //SETVAL6; 48 | abcddd[0][0] = val1; 49 | *pVal = 1; 50 | val4 = 0; 51 | s_vals[0] = 0; 52 | *pAAA = 0; 53 | return 1; 54 | } 55 | } 56 | 57 | #define SYM val 58 | #define RET_SYM return SYM; 59 | #define RET return 60 | int func4( int val ) 61 | { 62 | if ( val ) { 63 | return 1; 64 | } 65 | if ( val == 1 ) { 66 | return val; 67 | } 68 | if ( val == 2 ) { 69 | return val + 2; 70 | } 71 | if ( val == 3 ) { 72 | return SYM; 73 | } 74 | if ( val == 4 ) { 75 | RET 1 ; 76 | } 77 | if ( val == 5 ) { 78 | RET_SYM; 79 | } 80 | return 1; 81 | } 82 | 83 | typedef struct { 84 | int val; 85 | } type_t; 86 | static int func5_1( int* pFuncRet__, type_t* ptyp, type_t typArray[1] ); 87 | int func5( int val ) 88 | { 89 | type_t typ; 90 | type_t typArray[1]; 91 | if ( val == 0 ) { 92 | typ.val = 1; 93 | typArray[0].val = 2; 94 | return 0; 95 | } 96 | if ( val == 1 ) { 97 | int funcRet__ = 0; 98 | if ( func5_1( &funcRet__, &typ, typArray ) ) { 99 | printf( "funcRet__ = %d, typ.val = %d, typArray[0].val = %d\n", 100 | funcRet__, typ.val, typArray[0].val ); 101 | return funcRet__; 102 | } 103 | } 104 | 105 | return 1; 106 | } 107 | 108 | static int func5_1( int* pFuncRet__, type_t* ptyp, type_t typArray[1] ) 109 | { 110 | (*ptyp).val = 1; 111 | typArray[0].val = 2; 112 | return *pFuncRet__= 0, 1; 113 | } 114 | 115 | int func6( int val ) 116 | { 117 | type_t typ; 118 | if ( val == 0 ) { 119 | val = 1; 120 | typ // hoge 121 | /** */ . 122 | val = val; 123 | typ.val = val; 124 | return 0; 125 | } 126 | } 127 | 128 | static int func7( int val ) 129 | { 130 | int index; 131 | for ( index = 0; index < 10; index++ ) { 132 | if ( val == 10 ) { 133 | continue; 134 | } 135 | if ( val == 20 ) { 136 | break; 137 | } 138 | if ( val == 30 ) { 139 | return 0; 140 | } 141 | } 142 | while ( index < 10 ) { 143 | index++; 144 | if ( val == 10 ) { 145 | continue; 146 | } 147 | if ( val == 20 ) { 148 | break; 149 | } 150 | } 151 | do { 152 | index++; 153 | if ( val == 10 ) { 154 | continue; 155 | } 156 | } while ( index < 10 ); 157 | for ( index = 0; index < 10; index++ ) { 158 | if ( val == 20 ) { 159 | break; 160 | } 161 | } 162 | return 1; 163 | } 164 | 165 | static void func8( int val ) 166 | { 167 | if ( val == 1 ) { 168 | return; 169 | } 170 | int * pVal = &val; 171 | } 172 | 173 | 174 | static void func9( type_t * pTyp ) 175 | { 176 | int * pVal = &pTyp->val; 177 | *pVal = 1; 178 | } 179 | 180 | static void func10( void ) { 181 | type_t type; 182 | type_t * pType_; 183 | { 184 | type_t * pType = &type; 185 | int * pVal1 = &type.val; 186 | int * pVal2 = &(type).val; 187 | int * pVal3 = &(type.val); 188 | int * pVal4 = &(&type)->val; 189 | type_t * pType2 = pType_; 190 | } 191 | } 192 | 193 | static void func10__sub( type_t* ptype ) 194 | { 195 | type_t * pType = ptype; 196 | int * pVal1 = &ptype->val; 197 | int * pVal2 = &((*ptype)).val; 198 | int * pVal3 = &(ptype->val); 199 | int * pVal4 = &(ptype)->val; 200 | } 201 | 202 | static void func11( int val ) { 203 | int * pVal = &val; 204 | { 205 | int val; 206 | pVal = &val; 207 | } 208 | } 209 | 210 | static char func12( char val ) 211 | { 212 | if ( val ) { 213 | val++; 214 | return val; 215 | } 216 | return -val; 217 | } 218 | 219 | static void func13( char val ) 220 | { 221 | int val2; 222 | if ( val ) { 223 | val++; 224 | val2 = 1; 225 | } 226 | } 227 | 228 | 229 | int main() 230 | { 231 | int val = 2; 232 | //func( 1, 2, &val, 4, 5, 6 ); 233 | func2( 10, 2, &val, 4, 5, 6 ); 234 | func5( 1 ); 235 | } 236 | 237 | -------------------------------------------------------------------------------- /src/lctags/StatusServer.lua: -------------------------------------------------------------------------------- 1 | local Helper = require( 'lctags.Helper' ) 2 | local Json = require( 'lctags.Json' ) 3 | local log = require( 'lctags.LogCtrl' ) 4 | 5 | local StatusServer = {} 6 | 7 | --local socket = require( "socket" ) 8 | 9 | function StatusServer:setup( name, serverFlag, createFlag ) 10 | name = string.gsub( name, "/", "" ) 11 | self.name = name 12 | if socket then 13 | if serverFlag then 14 | local server = assert(socket.bind("*", 5000)) 15 | 16 | self.client = server:accept() 17 | else 18 | self.client = assert( socket.connect("127.0.0.1", 5000) ) 19 | end 20 | else 21 | if serverFlag then 22 | self:cleanResource() 23 | end 24 | self.requestLock = Helper.createLock( name .. ".requestLock" ) 25 | self.requestQueue = Helper.createMQueue( 26 | name .. "requestStatus", serverFlag or createFlag, self.requestLock ) 27 | self.replyLock = Helper.createLock( name .. ".replyLock" ) 28 | self.replyQueue = Helper.createMQueue( 29 | name .. "replyStatus", serverFlag or createFlag, self.replyLock ) 30 | if not self.requestQueue or not self.replyQueue then 31 | print( "failed to mqueue", serverFlag, createFlag ) 32 | os.exit( 1 ) 33 | end 34 | end 35 | 36 | if serverFlag then 37 | self.statusList = {} 38 | end 39 | 40 | self.writeModeCount = 0 41 | self.readModeCount = 0 42 | 43 | log( 1, "StatusServer:setup", serverFlag ) 44 | end 45 | 46 | function StatusServer:new( name ) 47 | self:setup( name, true ) 48 | while true do 49 | local txt = self:get() 50 | if not txt then 51 | self:cleanResource() 52 | log( 1, "StatusServer:server error end:", txt ) 53 | os.exit( 1 ) 54 | end 55 | local message = Json:convertFrom( txt ) 56 | if not message then 57 | log( 1, "StatusServer:server error:", txt ) 58 | else 59 | --log( 3, "StatusServer: command", message.command, message.value ) 60 | self[ message.command ]( self, message.value ) 61 | end 62 | end 63 | end 64 | 65 | function StatusServer:get() 66 | if socket then 67 | return self.client:receive( '*l' ) 68 | else 69 | return self.requestQueue:get() 70 | end 71 | end 72 | 73 | function StatusServer:request( command, value ) 74 | local json = Json:convertTo( { command = command, value = value } ) 75 | if socket then 76 | self.client:send( json .. "\n" ) 77 | else 78 | self.requestQueue:put( json ) 79 | end 80 | end 81 | 82 | function StatusServer:reply( value ) 83 | local json = Json:convertTo( value ) 84 | if socket then 85 | self.client:send( json .. "\n" ) 86 | else 87 | self.replyQueue:put( json ) 88 | end 89 | end 90 | 91 | function StatusServer:getReply() 92 | local txt 93 | if socket then 94 | txt = self.client:receive( '*l' ) 95 | else 96 | txt = self.replyQueue:get() 97 | end 98 | if not txt then 99 | return nil 100 | end 101 | local obj = Json:convertFrom( txt ) 102 | return obj 103 | end 104 | 105 | function StatusServer:connect( name, createFlag ) 106 | self:setup( name, false, createFlag ) 107 | end 108 | 109 | 110 | function StatusServer:requestNotifyOpenClose( openFlag, readonlyFlag ) 111 | self:request( "notifyOpenClose", { openFlag = openFlag, readonlyFlag = readonlyFlag } ) 112 | end 113 | 114 | function StatusServer:requestEnd() 115 | self:request( "exit" ) 116 | end 117 | 118 | function StatusServer:requestUpdateStatus( name, state ) 119 | if not self.endFlag then 120 | self:request( "updateStatus", { name = name, state = state, time = os.clock() } ) 121 | end 122 | end 123 | 124 | function StatusServer:requestEndStatus( name ) 125 | self.endFlag = ture 126 | self:request( "updateStatus", { name = name, endFlag = true } ) 127 | end 128 | 129 | function StatusServer:cleanResource() 130 | Helper.deleteMQueue( self.name .. "requestStatus" ) 131 | Helper.deleteMQueue( self.name .. "replyStatus" ) 132 | Helper.deleteLock( self.name .. ".requestLock" ) 133 | Helper.deleteLock( self.name .. ".replyLock" ) 134 | end 135 | 136 | function StatusServer:exit() 137 | self:reply( { endFlag = true } ) 138 | self:cleanResource() 139 | log( 1, "StatusServer:server end" ) 140 | os.exit( 0 ) 141 | end 142 | 143 | function StatusServer:searchStatus( info ) 144 | local findIndex 145 | for index, status in ipairs( self.statusList ) do 146 | if status.name == info.name then 147 | return index 148 | end 149 | end 150 | return nil 151 | end 152 | 153 | function StatusServer:updateStatus( info ) 154 | local findIndex = self:searchStatus( info ) 155 | if info.endFlag then 156 | if findIndex then 157 | table.remove( self.statusList, findIndex ) 158 | end 159 | return 160 | end 161 | 162 | if not findIndex then 163 | table.insert( self.statusList, 164 | { name = info.name, basetime = info.time, info = info } ) 165 | findIndex = #self.statusList 166 | end 167 | self.statusList[ findIndex ].info = info 168 | end 169 | 170 | function StatusServer:notifyOpenClose( info ) 171 | local val = 1 172 | 173 | if not info.openFlag then 174 | val = -1 175 | end 176 | 177 | if info.readonlyFlag then 178 | self.readModeCount = self.readModeCount + val 179 | else 180 | self.writeModeCount = self.writeModeCount + val 181 | end 182 | end 183 | 184 | 185 | function StatusServer:getStatus() 186 | self:reply( { readModeCount = self.readModeCount, 187 | writeModeCount = self.writeModeCount, 188 | list = self.statusList } ) 189 | end 190 | 191 | function StatusServer:requestGetStatus() 192 | self:request( "getStatus" ) 193 | return self:getReply() 194 | end 195 | 196 | return StatusServer 197 | -------------------------------------------------------------------------------- /src/lctags/Server.lua: -------------------------------------------------------------------------------- 1 | local Helper = require( 'lctags.Helper' ) 2 | local Json = require( 'lctags.Json' ) 3 | local log = require( 'lctags.LogCtrl' ) 4 | local sqlite3 = require( 'lsqlite3' ) 5 | 6 | local Server = {} 7 | 8 | --local socket = require( "socket" ) 9 | 10 | function Server:setup( name, serverFlag ) 11 | name = string.gsub( name, "/", "" ) 12 | self.name = name 13 | self.serverFlag = serverFlag 14 | if socket then 15 | if serverFlag then 16 | local server = assert(socket.bind("*", 5000)) 17 | 18 | self.client = server:accept() 19 | else 20 | self.client = assert( socket.connect("127.0.0.1", 5000) ) 21 | end 22 | else 23 | if serverFlag then 24 | self:release() 25 | end 26 | self.lockObj = Helper.createLock( name .. "lock" ) 27 | log( 1, "lockObj = ", self.lockObj, name, Helper.createLock ) 28 | self.requestQueue = Helper.createMQueue( name .. "request" ) 29 | self.replyQueue = Helper.createMQueue( name .. "reply" ) 30 | if not self.requestQueue or not self.replyQueue then 31 | os.exit( 1 ) 32 | end 33 | end 34 | log( 1, "setup", serverFlag ) 35 | end 36 | 37 | function Server:release() 38 | if self.serverFlag then 39 | if not socket then 40 | Helper.deleteLock( self.name .. "lock" ) 41 | Helper.deleteMQueue( self.name .. "request" ) 42 | Helper.deleteMQueue( self.name .. "reply" ) 43 | end 44 | end 45 | end 46 | 47 | function Server:new( name, db ) 48 | if not db then 49 | return 50 | end 51 | self.db = db.db 52 | self:setup( name, true ) 53 | while true do 54 | local messageTxt = self:get() 55 | local message = Json:convertFrom( messageTxt ) 56 | if message == nil then 57 | db:commit() 58 | db:close() 59 | self:release() 60 | log( 1, "server end for illegal message" ) 61 | os.exit( 0 ) 62 | end 63 | 64 | --log( 3, "Server: command", message.command, message.value ) 65 | if type( message ) == "table" and message.command then 66 | log( 2, "server command", message.command ) 67 | self[ message.command ]( self, message.value ) 68 | end 69 | end 70 | end 71 | 72 | function Server:get() 73 | if socket then 74 | return self.client:receive( '*l' ) 75 | else 76 | return self.requestQueue:get() 77 | end 78 | end 79 | 80 | function Server:request( command, value ) 81 | local json = Json:convertTo( { command = command, value = value } ) 82 | if socket then 83 | self.client:send( json .. "\n" ) 84 | else 85 | self.requestQueue:put( json ) 86 | end 87 | end 88 | 89 | function Server:reply( value ) 90 | local json = Json:convertTo( value ) 91 | if socket then 92 | self.client:send( json .. "\n" ) 93 | else 94 | self.replyQueue:put( json ) 95 | end 96 | end 97 | 98 | function Server:getReply() 99 | local txt 100 | if socket then 101 | txt = self.client:receive( '*l' ) 102 | else 103 | txt = self.replyQueue:get() 104 | end 105 | local obj = Json:convertFrom( txt ) 106 | return obj 107 | end 108 | 109 | function Server:connect( name ) 110 | self:setup( name, false ) 111 | end 112 | 113 | 114 | function Server:requestEnd() 115 | self:request( "exit" ) 116 | end 117 | 118 | 119 | function Server:requestCommit() 120 | self:request( "commit" ) 121 | end 122 | 123 | function Server:commit() 124 | self.db:commit() 125 | self.db:begin() 126 | end 127 | 128 | function Server:requestExec( stmt ) 129 | self.lockObj:begin() 130 | self:request( "exec", stmt ) 131 | local reply = self:getReply() 132 | self.lockObj:fin() 133 | return reply 134 | end 135 | 136 | function Server:requestTest( ) 137 | self:request( "test" ) 138 | return self:getReply() 139 | end 140 | 141 | function Server:exit() 142 | self.db:commit() 143 | self.db:close() 144 | self:release() 145 | log( 1, "server end" ) 146 | os.exit( 0 ) 147 | end 148 | 149 | function Server:exec( stmt ) 150 | local result = self.db.db:exec( stmt ) 151 | if result ~= sqlite3.OK then 152 | local err = self.db.db:errmsg() 153 | if err ~= "not an error" then 154 | --log( 2, "Server:exec", err ) 155 | self:reply( { err = err } ) 156 | else 157 | self:reply( {} ) 158 | end 159 | else 160 | log( 3, "exec", stmt ) 161 | self:reply( {} ) 162 | end 163 | end 164 | 165 | function Server:inq( query ) 166 | local findItem 167 | local success, message = pcall( 168 | function() 169 | for item in self.db.db:nrows( query ) do 170 | self:reply( { item = item } ) 171 | local contFlag = self:get() 172 | if not contFlag then 173 | break 174 | end 175 | end 176 | end 177 | ) 178 | if not success then 179 | self:reply( { err = message } ) 180 | else 181 | self:reply( { fin = true } ) 182 | end 183 | end 184 | 185 | function Server:requestInq( query, func ) 186 | self.lockObj:begin() 187 | 188 | self:request( "inq", query ) 189 | while true do 190 | local info = self:getReply() 191 | if info.err or info.fin then 192 | if info.err then 193 | log( 1, "requestInq: err", info.err, query ) 194 | os.exit( 1) 195 | end 196 | self.lockObj:fin() 197 | return info 198 | end 199 | self.requestQueue:put( 200 | func( info.item ) and "true" or "false" ) 201 | end 202 | end 203 | 204 | 205 | 206 | function Server:test() 207 | print( "test" ) 208 | for index = 1, 10000 do 209 | local success, message = pcall( 210 | function() 211 | for item in self.db.db:nrows( 212 | "SELECT * FROM etc WHERE keyName = 'projDir' LIMIT 1" ) do 213 | end 214 | end 215 | ) 216 | end 217 | self:reply( {} ) 218 | end 219 | 220 | 221 | return Server 222 | -------------------------------------------------------------------------------- /src/lisp/lctags-split.el: -------------------------------------------------------------------------------- 1 | (defvar lctags-sub-ret-type nil) 2 | 3 | (defun lctags-split-get-args (info) 4 | (xml-get-children (car (xml-get-children info 'args)) 'arg) 5 | ) 6 | 7 | (defun lctags-split-can-directRet (info) 8 | (car (xml-node-children (car (xml-get-children info 'directRet)))) 9 | ) 10 | 11 | 12 | (defun lctags-split-arg-isAddressAccess (arg) 13 | (equal (car (xml-node-children (car (xml-get-children arg 'addressAccess)))) 14 | "true")) 15 | 16 | (defun lctags-split-arg-isDirectReturn (arg) 17 | (equal (car (xml-node-children (car (xml-get-children arg 'directRet)))) 18 | "true")) 19 | 20 | (defun lctags-split-arg-get-name (arg) 21 | (car (xml-node-children (car (xml-get-children arg 'name))))) 22 | 23 | (defun lctags-split-arg-get-argSym (arg) 24 | (car (xml-node-children (car (xml-get-children arg 'argSymbol))))) 25 | 26 | 27 | (defun lctags-split-get-call (info) 28 | (let ((call-txt (car (xml-node-children (car (xml-get-children info 'call)))))) 29 | (when (string-match "^[\n\t]+" call-txt) 30 | (setq call-txt (replace-match "" nil nil call-txt))) 31 | call-txt 32 | )) 33 | (defun lctags-split-get-subroutine (info) 34 | (let ((txt (car (xml-node-children (car (xml-get-children info 'sub_routine)))))) 35 | (when (string-match "^[\n\t]+" txt) 36 | (setq txt (replace-match "" nil nil txt))) 37 | txt 38 | )) 39 | 40 | 41 | (defvar lctags-split-buffer nil) 42 | (defvar lctags-split-target-buffer nil) 43 | (defvar lctags-split-target-pos-mark nil) 44 | 45 | 46 | (defun lctags-execute-split-op (src-buf lctags-buf input lctags-opts) 47 | (let (info) 48 | (if (eq (lctags-execute-op src-buf lctags-buf input nil lctags-opts) 0) 49 | (progn 50 | (setq lctags-split-info (lctags-xml-get lctags-buf 'refactoring_split)) 51 | (if (assoc 'candidate lctags-split-info) 52 | (setq lctags-diag-info nil) 53 | (setq lctags-diag-info (lctags-xml-get-diag lctags-buf)))) 54 | (setq lctags-split-info nil) 55 | (with-current-buffer lctags-buf 56 | (setq lctags-diag-info `((message nil ,(buffer-string)))))) 57 | info 58 | )) 59 | 60 | (defun lctags-execute-split (src-buf lctags-buf input &rest lctags-opts) 61 | (let (split-info subroutine-pos) 62 | (setq split-info (lctags-execute-split-op src-buf lctags-buf input lctags-opts)) 63 | (if lctags-diag-info 64 | (lctags-helm-display-diag) 65 | (setq lctags-split-target-buffer src-buf) 66 | (when lctags-split-target-pos-mark 67 | (set-marker lctags-split-target-pos-mark nil) 68 | (setq lctags-split-target-pos-mark nil)) 69 | (setq lctags-split-target-pos-mark (point-marker)) 70 | (lctags-get-unique-buffer 'lctags-split-buffer "*lctags-split*" t) 71 | (lctags-switch-to-buffer-other-window lctags-split-buffer) 72 | (setq split-info (lctags-xml-get lctags-buf 'refactoring_split)) 73 | 74 | (with-current-buffer lctags-split-buffer 75 | (insert "/* please edit 'x' or 'o' and symbol and order of following items,\n and push C-c C-c to update. 76 | format: 77 | :[xor]:argName:orgName 78 | x is to pass value. 79 | o is to pass address of value. 80 | r is to pass value and to return directly. 81 | ---------------- 82 | ") 83 | (when (lctags-split-can-directRet split-info) 84 | (insert (format ":%s:indirect-return\n" 85 | (if (equal (lctags-split-can-directRet split-info) 86 | "true") 87 | "x" "o")))) 88 | (dolist (arg (lctags-split-get-args split-info)) 89 | (insert (format ":%s:%s:%s\n" 90 | (cond 91 | ((lctags-split-arg-isDirectReturn arg) "r") 92 | ((lctags-split-arg-isAddressAccess arg) "o") 93 | (t "x")) 94 | (lctags-split-arg-get-argSym arg) 95 | (lctags-split-arg-get-name arg) 96 | arg))) 97 | (insert "----------------\n") 98 | (c++-mode) 99 | (insert "*/\n") 100 | (setq subroutine-pos (point)) 101 | (insert "//======= call ======\n") 102 | (insert (lctags-split-get-call split-info)) 103 | (insert "\n//======= sub routine ======\n") 104 | (insert (lctags-split-get-subroutine split-info)) 105 | (local-set-key (kbd "C-c C-c") 'lctags-split-retry) 106 | (indent-region subroutine-pos (point)) 107 | (beginning-of-buffer)) 108 | ))) 109 | 110 | 111 | 112 | (defun lctags-split-at (&optional direct-return &rest ignore-list) 113 | (interactive) 114 | (let ((buffer (lctags-get-process-buffer t))) 115 | (lctags-execute-split (current-buffer) buffer (buffer-string) "split-at" 116 | (buffer-file-name (current-buffer)) 117 | (number-to-string (lctags-get-line)) 118 | (number-to-string (- (lctags-get-column) 1)) "-i" 119 | (when lctags-sub-ret-type "--lctags-subRet") 120 | (when lctags-sub-ret-type lctags-sub-ret-type) 121 | direct-return 122 | (when ignore-list "-split-param-list") 123 | (mapconcat (lambda (X) X) 124 | ignore-list "," )) 125 | )) 126 | 127 | (defun lctags-split-retry () 128 | (interactive) 129 | (let ((back-pos (point)) 130 | ignore-list direct-return) 131 | (save-excursion 132 | (beginning-of-buffer) 133 | (while (re-search-forward "^:[xor]:" nil t) 134 | (let (symbol pos param endpos) 135 | (setq pos (point)) 136 | (end-of-line) 137 | (setq endpos (point)) 138 | (setq param (buffer-substring-no-properties (- pos 2) endpos)) 139 | (re-search-backward ":" nil t) 140 | (setq symbol (buffer-substring-no-properties (1+ (point)) endpos)) 141 | (if (equal symbol "indirect-return") 142 | (when (string-match "^x:" param) 143 | (setq direct-return "--lctags-directRet")) 144 | (setq ignore-list (append ignore-list (list param))))))) 145 | (with-current-buffer lctags-split-target-buffer 146 | (goto-char lctags-split-target-pos-mark) 147 | (apply 'lctags-split-at direct-return ignore-list) 148 | ) 149 | (goto-char back-pos) 150 | )) 151 | 152 | (provide 'lctags-split) 153 | -------------------------------------------------------------------------------- /src/lisp/httpd.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding:utf-8 3 | 4 | from BaseHTTPServer import HTTPServer 5 | from BaseHTTPServer import BaseHTTPRequestHandler 6 | import urlparse 7 | import sys 8 | import os 9 | import subprocess 10 | import json 11 | import lctags_httpd_var 12 | 13 | lctags_commnad = "lctags" 14 | 15 | 16 | 17 | args = [] 18 | for arg in sys.argv: 19 | if arg.startswith( "-lctags=" ): 20 | lctags_commnad = arg[ len( "-lctags=" ): ] 21 | else: 22 | args.append( arg ) 23 | 24 | if len( args ) != 3: 25 | print( "usage: %s [-lctags=path] port dbpath" %os.path.basename( args[0] ) ) 26 | print( "" ) 27 | print( " path: path of lctags" ) 28 | print( " port: port of httpd" ) 29 | print( " dbpath: path of lctags.sqlite3" ) 30 | exit( 1 ) 31 | 32 | httpPort = int( args[1] ) 33 | dbPath = os.path.abspath( args[2] ) 34 | 35 | 36 | 37 | contentRoot = os.path.join( os.path.abspath( os.path.dirname( args[0] ) ), "html" ) 38 | 39 | suffix2TypeMap = { '.html': "text/html", 40 | ".js": "text/json", 41 | ".css": "text/css" } 42 | 43 | def execCmd( cmd ): 44 | popen = subprocess.Popen( cmd, shell=True, stdout = subprocess.PIPE ) 45 | return popen.communicate()[0] 46 | 47 | 48 | 49 | jsonObj = json.loads( execCmd( 50 | "%s inq projDir --lctags-form json --lctags-db %s" %(lctags_commnad, dbPath ) ) ) 51 | projDir = jsonObj[ "lctags_result" ][ "projDir" ][0][ "path" ] 52 | confInfo = { 'db': dbPath, 'projDir': projDir } 53 | confId2InfoMap = { '0': confInfo } 54 | 55 | 56 | class SimpleHttpd( BaseHTTPRequestHandler ): 57 | def __init__(self, *args ): 58 | BaseHTTPRequestHandler.__init__(self, *args) 59 | 60 | def exeLctags( self, confInfo, query ): 61 | api = query.get( "command" )[0] 62 | dbPath = confInfo[ "db" ] 63 | command = "%s --lctags-db '%s' inq %s " %(lctags_commnad, dbPath, api) 64 | apiInfo = lctags_httpd_var.apiInfoMap.get( api ) 65 | if not apiInfo: 66 | return "" 67 | for param in apiInfo[ "param" ]: 68 | if isinstance( param, str ): 69 | if param.startswith( "?" ): 70 | param = query.get( param[ 1: ] )[0] 71 | elif isinstance( param, list ): 72 | val = param[ 0 ] 73 | func = param[ 1 ] 74 | param = func( query.get( val[1:] )[0] ) 75 | command = "%s %s" %(command, param ) 76 | 77 | return execCmd( command ) 78 | 79 | 80 | def responseLocalFile( self, path ): 81 | fileObj = open( os.path.join( contentRoot, path ) ) 82 | self.send_response(200) 83 | suffix = os.path.splitext( path )[1] 84 | self.send_header("Content-Type", suffix2TypeMap.get( suffix ) ) 85 | content = fileObj.read() 86 | fileObj.close() 87 | return content 88 | 89 | 90 | 91 | def do_GET(self): 92 | parsedUrl = urlparse.urlparse(self.path) 93 | path = parsedUrl[ 2 ] 94 | query = urlparse.parse_qs( parsedUrl[ 4 ] ) 95 | if path == "/lctags": 96 | self.send_response(302) 97 | self.send_header("Location", "/lctags/contents/index.html") 98 | self.end_headers() 99 | elif path.startswith( "/lctags/contents/" ): 100 | content = self.responseLocalFile( path[ len( "/lctags/contents/" ): ] ) 101 | 102 | self.send_header( "Content-Length", len( content ) ) 103 | self.end_headers() 104 | 105 | self.wfile.write( content ) 106 | elif path.startswith( "/lctags/gen/" ): 107 | content = self.responseLocalFile( path[ len( "/lctags/gen/" ): ] ) 108 | 109 | confId = query.get( "confId" )[0] 110 | confInfo = confId2InfoMap.get( confId ) 111 | query[ "projDir" ] = [ confInfo[ "projDir" ] ] 112 | for key, val in query.items(): 113 | content = content.replace( "$%s$" %key, val[0] ) 114 | 115 | self.send_header( "Content-Length", len( content ) ) 116 | self.end_headers() 117 | self.wfile.write( content ) 118 | elif path.startswith( "/lctags/start" ): 119 | cookie = query.get( "cookie" )[0] 120 | self.send_response(302) 121 | self.send_header("Location", 122 | "/lctags/gen/file-list.html?confId=%s" %cookie) 123 | self.end_headers() 124 | elif path.startswith( "/lctags/inq" ): 125 | confId = query.get( "confId" )[0] 126 | confInfo = confId2InfoMap.get( confId ) 127 | self.send_response(200) 128 | 129 | content = self.exeLctags( confInfo, query ) 130 | self.send_header( "Content-Type", "text/json" ) 131 | self.send_header( "Content-Length", len( content ) ) 132 | self.end_headers() 133 | 134 | self.wfile.write( content ) 135 | elif path.startswith( "/lctags/get" ): 136 | command = query.get( "command" )[0] 137 | 138 | if command == "cookies": 139 | obj = {} 140 | cookieList = [] 141 | obj[ "list" ] = cookieList 142 | 143 | for confId, confInfo in confId2InfoMap.items(): 144 | info = {} 145 | info[ "cookie" ] = confId 146 | info[ "db" ] = confInfo.get( "db" ) 147 | cookieList.append( info ) 148 | 149 | content = json.dumps( obj ) 150 | 151 | self.send_response(200) 152 | self.send_header( "Content-Type", "text/json" ) 153 | self.send_header( "Content-Length", len( content ) ) 154 | self.end_headers() 155 | 156 | self.wfile.write( content ) 157 | return 158 | 159 | 160 | server = HTTPServer( ('', httpPort), SimpleHttpd ) 161 | print( "start lctags httpd -- %s %s" %(httpPort, dbPath ) ) 162 | server.serve_forever() 163 | -------------------------------------------------------------------------------- /src/lctags/OutputCtrl.lua: -------------------------------------------------------------------------------- 1 | local Util = require( 'lctags.Util' ) 2 | local log = require( 'lctags.LogCtrl' ) 3 | local Helper = require( 'lctags.Helper' ) 4 | 5 | local obj = {} 6 | 7 | 8 | function obj.dot( 9 | db, targetId, allIdList, id2BaseIdSetMap, relIf, 10 | browseFlag, outputFile, imageFormat ) 11 | if not imageFormat then 12 | imageFormat = "svg" 13 | end 14 | 15 | local fileHandle 16 | if not outputFile then 17 | if browseFlag then 18 | outputFile = Helper.getTempFilename( "lctags" ) .. "." .. imageFormat 19 | else 20 | outputFile = "lctags_graph." .. imageFormat 21 | end 22 | end 23 | local dotFile = outputFile .. ".dot" 24 | fileHandle = io.open( dotFile, "w" ) 25 | log( 2, "dotFile", dotFile ) 26 | 27 | if not fileHandle then 28 | log( 1, "failed to open image file" ) 29 | os.exit( 1 ) 30 | end 31 | 32 | fileHandle:write( "digraph relation {\n" ) 33 | fileHandle:write( 34 | 'rankdir = "' .. (relIf.reverseFlag and "RL" or "LR") .. '";\n' ) 35 | 36 | local dir2FileIdListMap = {} 37 | local fileId2idMap = {} 38 | local isFileFlag = true 39 | local dirNum = 0 40 | local sameDirNum = 0 41 | for index, id in ipairs( allIdList ) do 42 | local fileId = relIf:getFileId( id ) 43 | local dir 44 | if not fileId then 45 | fileId = 0 46 | dir = "" 47 | else 48 | local fileInfo = db:getFileInfo( fileId ) 49 | dir = string.gsub( fileInfo.path, "^(.*)/[^/]+$", "%1" ) 50 | end 51 | local list = dir2FileIdListMap[ dir ] 52 | if not list then 53 | list = {} 54 | dir2FileIdListMap[ dir ] = list 55 | dirNum = dirNum + 1 56 | else 57 | sameDirNum = sameDirNum + 1 58 | end 59 | table.insert( list, fileId ) 60 | 61 | if fileId ~= id then 62 | isFileFlag = false 63 | end 64 | 65 | if not fileId2idMap[ fileId ] then 66 | fileId2idMap[ fileId ] = {} 67 | end 68 | 69 | table.insert( fileId2idMap[ fileId ], id ) 70 | end 71 | 72 | local outputNode = function( id ) 73 | local color = '' 74 | if targetId == id then 75 | color = 'color = "red"' 76 | else 77 | color = 'color = "green"' 78 | relIf:mapBaseFor( 79 | id, 80 | function( item ) 81 | color = '' 82 | return false 83 | end ) 84 | end 85 | 86 | fileHandle:write( string.format( 87 | '"%d:%s" [tooltip="%s", %s];\n', 88 | id, relIf:getName( id ), relIf:getTooltip( id ), color ) ) 89 | end 90 | 91 | if isFileFlag then 92 | if dirNum >= 2 and sameDirNum >= 2 then 93 | local index = 0 94 | for dirPath, fileIdList in pairs( dir2FileIdListMap ) do 95 | if dirPath == "" then 96 | log( 2, "dot output id dirPath is ''" ) 97 | --outputNode( id ) 98 | else 99 | index = index + 1 100 | fileHandle:write( string.format( "subgraph cluster_0%d {", index ) ) 101 | fileHandle:write( string.format( 'label = "%s"; fontcolor = "red";', 102 | dirPath ) ) 103 | for index, fileId in pairs( fileIdList ) do 104 | outputNode( fileId ) 105 | end 106 | fileHandle:write( "}" ) 107 | end 108 | end 109 | else 110 | for index, id in ipairs( allIdList ) do 111 | outputNode( id ) 112 | end 113 | end 114 | else 115 | local fileClusterFunc = function( fileId ) 116 | local idList = fileId2idMap[ fileId ] 117 | if fileId ~= 0 then 118 | fileHandle:write( string.format( "subgraph cluster_%d {", fileId ) ) 119 | fileHandle:write( 120 | string.format( 'label = "%s"; fontcolor = "black";\n', 121 | db:getFileInfo( fileId ).path ) ) 122 | end 123 | 124 | for index, id in ipairs( idList ) do 125 | outputNode( id ) 126 | end 127 | 128 | if fileId ~= 0 then 129 | fileHandle:write( "}" ) 130 | end 131 | end 132 | -- id が file を示さない場合、file でグルーピングする 133 | if dirNum >= 2 and sameDirNum >= 2 then 134 | for dirPath, fileIdList in pairs( dir2FileIdListMap ) do 135 | if dirPath == "" then 136 | for index, fileId in pairs( fileIdList ) do 137 | fileClusterFunc( fileId ) 138 | end 139 | else 140 | fileHandle:write( 141 | string.format( "subgraph cluster_0%d {", fileIdList[ 1 ] ) ) 142 | fileHandle:write( 143 | string.format( 'label = "%s"; fontcolor = "red";\n', dirPath ) ) 144 | 145 | for index, fileId in pairs( fileIdList ) do 146 | fileClusterFunc( fileId ) 147 | end 148 | 149 | fileHandle:write( "}" ) 150 | end 151 | end 152 | else 153 | for fileId, idList in pairs( fileId2idMap ) do 154 | fileClusterFunc( fileId ) 155 | end 156 | end 157 | end 158 | 159 | for id, baseIdSet in pairs( id2BaseIdSetMap ) do 160 | for baseId in pairs( baseIdSet ) do 161 | local baseTxt = tonumber( baseId ) .. ':' .. relIf:getName( baseId ) 162 | local dstTxt = tonumber( id ) .. ':' .. relIf:getName( id ) 163 | fileHandle:write( 164 | string.format( 165 | '"%s" -> "%s" ;\n', 166 | relIf.reverseFlag and baseTxt or dstTxt, 167 | relIf.reverseFlag and dstTxt or baseTxt ) ) 168 | end 169 | end 170 | fileHandle:write( "}\n" ) 171 | fileHandle:close() 172 | 173 | os.execute( string.format( 174 | "dot -T%s -o %s %s", imageFormat, outputFile, dotFile ) ) 175 | os.remove( dotFile ) 176 | 177 | if browseFlag then 178 | -- ブラウズ中に DB の書き込みが出来ないので、ここで close する。 179 | -- イマイチ。。。 180 | db:close() 181 | os.execute( "firefox " .. outputFile ) 182 | os.remove( outputFile ) 183 | end 184 | end 185 | 186 | 187 | function obj.txt( 188 | db, targetId, allIdList, id2BaseIdSetMap, relIf, fileHandle ) 189 | 190 | if not fileHandle then 191 | log( 1, "failed to open image file" ) 192 | os.exit( 1 ) 193 | end 194 | 195 | 196 | pathList = {} 197 | for index, baseId in pairs( allIdList ) do 198 | table.insert( pathList, relIf:getTooltip( baseId ) ) 199 | end 200 | 201 | table.sort( pathList ) 202 | for index, path in pairs( pathList ) do 203 | if path ~= "" then 204 | Util:printLocateDirect( fileHandle, db, "path", path, 1, false ) 205 | end 206 | end 207 | end 208 | 209 | function obj.form( info ) 210 | io.stdout:write( "" ) 211 | obj.formSub( info ) 212 | io.stdout:write( "" ) 213 | end 214 | 215 | function obj.formSub( info ) 216 | for key, val in pairs( info ) do 217 | io.stdout:write( string.format( "<%s>", key ) ) 218 | if type( val ) == "table" then 219 | obj.formSub( val ) 220 | elseif type( val ) == "number" then 221 | io.stdout:write( string.format( "%g", val ) ) 222 | else 223 | io.stdout:write( Util:convertXmlTxt( val ) ) 224 | end 225 | io.stdout:write( string.format( "", key ) ) 226 | end 227 | end 228 | 229 | return obj 230 | -------------------------------------------------------------------------------- /src/lctags/Writer.lua: -------------------------------------------------------------------------------- 1 | local Writer = {} 2 | 3 | local XML = {} 4 | Writer.XML = XML 5 | 6 | local function convertXmlTxt( txt ) 7 | if txt == nil or txt == "" then 8 | return "" 9 | end 10 | if type( txt ) == "number" then 11 | return string.format( "%g", txt ) 12 | end 13 | txt = string.gsub( txt, '&', "&" ) 14 | txt = string.gsub( txt, '>', ">" ) 15 | txt = string.gsub( txt, '<', "<" ) 16 | txt = string.gsub( txt, '"', """ ) 17 | txt = string.gsub( txt, "'", "'" ) 18 | return txt 19 | end 20 | 21 | function XML:open( stream ) 22 | self.stream = stream 23 | self.elementList = {} 24 | self.depth = 0 25 | return self 26 | end 27 | 28 | function XML:startParent( name, arrayFlag ) 29 | self:startElement( name ) 30 | end 31 | 32 | function XML:startElement( name ) 33 | table.insert( self.elementList, name ) 34 | self.stream:write( string.format( '<%s>', name ) ) 35 | self.depth = self.depth + 1 36 | end 37 | 38 | function XML:endElement() 39 | local name = table.remove( self.elementList ) 40 | self.stream:write( string.format( '', name ) ) 41 | self.depth = self.depth - 1 42 | if self.depth == 0 then 43 | self.stream:write( '\n' ) 44 | elseif self.depth < 0 then 45 | error( "illegal depth" ) 46 | end 47 | end 48 | 49 | function XML:writeValue( val ) 50 | self.stream:write( convertXmlTxt( val ) ) 51 | end 52 | 53 | function XML:write( name, val ) 54 | self:startElement( name ) 55 | self:writeValue( val ) 56 | self:endElement( name ) 57 | end 58 | 59 | function XML:fin() 60 | end 61 | 62 | 63 | local JSON = {} 64 | Writer.JSON = JSON 65 | 66 | local function convertJsonTxt( txt ) 67 | if txt == nil or txt == "" then 68 | return "" 69 | end 70 | txt = string.gsub( txt, '"', '\\"' ) 71 | txt = string.gsub( txt, '\\', '\\\\' ) 72 | txt = string.gsub( txt, '\n', '\\n' ) 73 | return txt 74 | end 75 | 76 | 77 | function JSON:open( stream ) 78 | self.stream = stream 79 | self.layerQueue = {} 80 | self:startLayer() 81 | 82 | 83 | return self 84 | end 85 | 86 | function JSON:getLayerInfo() 87 | if #self.layerQueue == 0 then 88 | return nil 89 | end 90 | return self.layerQueue[ #self.layerQueue ] 91 | end 92 | 93 | function JSON:startLayer( arrayFlag, madeByArrayFlag ) 94 | local info = {} 95 | info.state = 'none' -- none, named, valued, termed 96 | info.arrayFlag = arrayFlag 97 | info.name = self.prevName 98 | info.madeByArrayFlag = madeByArrayFlag 99 | info.elementNameSet = {} 100 | info.parentFlag = true 101 | 102 | table.insert( self.layerQueue, info ) 103 | 104 | self.stream:write( arrayFlag and "[" or "{" ) 105 | end 106 | 107 | function JSON:endLayer() 108 | if #self.layerQueue == 0 then 109 | error( "illegal depth" ) 110 | end 111 | 112 | while #self.layerQueue > 0 do 113 | local info = self:getLayerInfo() 114 | if info.arrayFlag then 115 | self.stream:write( string.format( ']' ) ) 116 | else 117 | self.stream:write( string.format( '}' ) ) 118 | end 119 | table.remove( self.layerQueue ) 120 | local parentInfo = self:getLayerInfo() 121 | if parentInfo and parentInfo.madeByArrayFlag then 122 | ; 123 | else 124 | break 125 | end 126 | end 127 | if #self.layerQueue == 0 then 128 | self.stream:write( '\n' ) 129 | end 130 | end 131 | 132 | function JSON:equalLayerState( state ) 133 | return self.layerQueue[ #self.layerQueue ].state == state 134 | end 135 | 136 | function JSON:isArrayLayer( state ) 137 | return self.layerQueue[ #self.layerQueue ].arrayFlag 138 | end 139 | 140 | 141 | function JSON:setLayerState( state ) 142 | self.layerQueue[ #self.layerQueue ].state = state 143 | end 144 | 145 | function JSON:getLayerName() 146 | return self.layerQueue[ #self.layerQueue ].name 147 | end 148 | 149 | function JSON:addElementName( name ) 150 | local info = self:getLayerInfo() 151 | local nameSet = info.elementNameSet 152 | if not info.arrayFlag and nameSet[ name ] then 153 | error( "exist same name: " .. name ) 154 | end 155 | nameSet[ name ] = true 156 | end 157 | 158 | function JSON:startParent( name, arrayFlag ) 159 | self:addElementName( name ) 160 | 161 | if self:equalLayerState( 'termed' ) or self:equalLayerState( 'named' ) or 162 | self:equalLayerState( 'valued' ) 163 | then 164 | self.stream:write( "," ) 165 | elseif self:equalLayerState( 'none' ) then 166 | ; 167 | end 168 | 169 | local parentInfo = self:getLayerInfo() 170 | if not arrayFlag and parentInfo and parentInfo.arrayFlag then 171 | self:startLayer( false, true ) 172 | end 173 | 174 | self.stream:write( string.format( '"%s": ', name ) ) 175 | self:startLayer( arrayFlag ) 176 | self.prevName = name 177 | end 178 | 179 | function JSON:startElement( name ) 180 | self:addElementName( name ) 181 | 182 | if self:equalLayerState( 'termed' ) then 183 | self.stream:write( "," ) 184 | elseif self:equalLayerState( 'named' ) then 185 | error( 'illegal layer state' ) 186 | elseif self:equalLayerState( 'none' ) then 187 | ; 188 | end 189 | if self:isArrayLayer( state ) then 190 | self:startLayer( false, true ) 191 | end 192 | 193 | local info = self:getLayerInfo() 194 | 195 | if info.openElement then 196 | error( 'illegal openElement' ) 197 | end 198 | info.openElement = true 199 | 200 | self.stream:write( string.format( '"%s": ', name ) ) 201 | self:setLayerState( 'named' ) 202 | self.prevName = name 203 | end 204 | 205 | function JSON:endElement() 206 | if self:equalLayerState( 'none' ) or self:equalLayerState( 'termed' ) then 207 | self:endLayer() 208 | elseif self:equalLayerState( 'valued' ) then 209 | local info = self:getLayerInfo() 210 | if info.openElement then 211 | info.openElement = false 212 | end 213 | if self:getLayerInfo().madeByArrayFlag then 214 | self:endLayer() 215 | end 216 | else 217 | error( 'illegal layer state ' .. self:getLayerName() ) 218 | end 219 | self:setLayerState( 'termed' ) 220 | end 221 | 222 | function JSON:writeValue( val ) 223 | local txt 224 | local typeId = type( val ) 225 | if typeId == "number" then 226 | txt = string.format( "%g", val ) 227 | elseif typeId == "boolean" then 228 | txt = val and "true" or "false"; 229 | else 230 | txt = string.format( '"%s"', convertJsonTxt( val ) ) 231 | end 232 | 233 | self.stream:write( txt ) 234 | self:setLayerState( 'valued' ) 235 | end 236 | 237 | function JSON:write( name, val ) 238 | self:startElement( name ) 239 | self:writeValue( val ) 240 | self:endElement() 241 | end 242 | 243 | function JSON:fin() 244 | if self:equalLayerState( 'none' ) or self:equalLayerState( 'termed' ) then 245 | self:endLayer() 246 | else 247 | error( 'illegal' ) 248 | end 249 | end 250 | 251 | return Writer 252 | -------------------------------------------------------------------------------- /src/lisp/lctags-dispatch.el: -------------------------------------------------------------------------------- 1 | (defvar lctags-dispatch-prev-window-conf nil) 2 | 3 | (defvar lctags-dispatch-buf-name "lctags dispatcher") 4 | 5 | (defvar lctags-dispatch-menu-info 6 | '((:name "list" :bind "l" 7 | :submenu 8 | ((:name "def-at" :bind "d" :action lctags-def-at) 9 | (:name "def-search" :bind "s" :action lctags-def-pickup-symbol) 10 | (:name "ref-at" :bind "r" :action lctags-ref-at) 11 | (:name "call-at" :bind "c" :action lctags-call-at) 12 | (:name "callee-at" :bind "C" :action lctags-callee-at) 13 | (:name "inc" :bind "i" :action lctags-list-inc-this-file) 14 | (:name "incSrc" :bind "I" :action lctags-list-incSrc-this-file) 15 | (:name "file" :bind "f" :action lctags-find-file) 16 | )) 17 | (:name "graph" :bind "g" 18 | :submenu 19 | ((:name "setCookie" :bind "c" :action lctags-servlet-set-cookie) 20 | (:name "setCookie-at" :bind "a" :action lctags-servlet-set-cookie-of-symbol-at) 21 | (:name "caller" :bind "r" :action lctags-graph-caller-at) 22 | (:name "callee" :bind "e" :action lctags-graph-callee-at) 23 | (:name "symbol" :bind "s" :action lctags-graph-symbol-at) 24 | (:name "inc" :bind "i" :action lctags-graph-inc) 25 | (:name "incSrc" :bind "I" :action lctags-graph-incSrc) 26 | )) 27 | (:name "generate" :bind "G" 28 | :submenu 29 | ((:name "dump-member-at" :bind "m" 30 | :action lctags-generate-to-dump-member-at) 31 | (:name "convert-enumName-at" :bind "e" 32 | :action lctags-generate-to-convert-enumName-at) 33 | (:name "form-enum" :bind "E" 34 | :action lctags-expand-enum-format-at) 35 | )) 36 | (:name "insert" :bind "i" 37 | :submenu 38 | ((:name "insert-to-call-function" :bind "c" 39 | :action lctags-insert-call-func) 40 | )) 41 | (:name "highlight" :bind "h" 42 | :submenu 43 | ((:name "highlight-at" :bind "h" :action lctags-highlight-at) 44 | (:name "rescan" :bind "r" :action lctags-highlight-rescan) 45 | (:name "grep" :bind "g" :action lctags-highlight-grep-at) 46 | (:name "clear" :bind "c" :action lctags-highlight-clear) 47 | )) 48 | (:name "refactoring" :bind "r" 49 | :submenu 50 | ((:name "sub-routine" :bind "s" 51 | :action lctags-split-at) 52 | (:name "rename" :bind "r" 53 | :action lctags-rename-at) 54 | )) 55 | (:name "misc" :bind "m" :submenu 56 | ((:name "expand-macro" :bind "e" :action lctags-expand-macro) 57 | (:name "grep-cursor" :bind "g" :action lctags-grep-cursor))) 58 | (:name "update" :bind "u" :submenu 59 | ((:name "this file" :bind "u" :action lctags-update-this-file) 60 | (:name "this directory" :bind "d" :action lctags-update-this-directory) 61 | (:name "all" :bind "A" :action lctags-update-all))))) 62 | 63 | 64 | (defvar lctags-dispatch-menu-info-current nil) 65 | 66 | 67 | (defun lctags-dispatch-menu-get-name (info) 68 | (plist-get info :name)) 69 | 70 | (defun lctags-dispatch-menu-get-bind (info) 71 | (plist-get info :bind)) 72 | 73 | (defun lctags-dispatch-menu-get-submenu (info) 74 | (plist-get info :submenu)) 75 | 76 | (defun lctags-dispatch-menu-get-action (info) 77 | (plist-get info :action)) 78 | 79 | (defun lctags-dispatch-menu-get-keymap (info) 80 | (plist-get info :keymap)) 81 | 82 | (defun lctags-dispatch-menu-set-keymap (info keymap) 83 | (plist-put info :keymap keymap)) 84 | 85 | (defun lctags-dispatch-mode-exit (&optional action param) 86 | (set-window-configuration lctags-dispatch-prev-window-conf) 87 | (kill-buffer (get-buffer lctags-dispatch-buf-name)) 88 | (when action 89 | (if param 90 | (funcall action param) 91 | (funcall action))) 92 | ) 93 | 94 | (defun lctags-dispatch-build-keymap (menu-info) 95 | (let ((map (make-sparse-keymap))) 96 | (define-key map (kbd "C-g") 97 | (lambda () 98 | (interactive) 99 | (lctags-dispatch-mode-exit nil) 100 | )) 101 | (mapcar 102 | (lambda (menu) 103 | (define-key map (eval `(kbd ,(lctags-dispatch-menu-get-bind menu))) 104 | (if (lctags-dispatch-menu-get-submenu menu) 105 | (eval `(lambda () 106 | (interactive) 107 | (lctags-dispatch-redraw ',(lctags-dispatch-menu-get-submenu menu)))) 108 | (eval `(lambda (param) 109 | (interactive "P") 110 | (lctags-dispatch-mode-exit 111 | ',(lctags-dispatch-menu-get-action menu) param)))))) 112 | menu-info) 113 | map 114 | )) 115 | 116 | 117 | (defun lctags-dispatch-redraw (menu-info) 118 | (font-lock-mode nil) 119 | (let ((buffer-read-only nil) 120 | (old-point (point)) 121 | (is-first (zerop (buffer-size))) 122 | (actions-p nil)) 123 | (erase-buffer) 124 | (use-local-map (lctags-dispatch-build-keymap menu-info)) 125 | 126 | (lctags-dispatch-draw-menu lctags-dispatch-menu-info menu-info) 127 | 128 | (delete-trailing-whitespace) 129 | (setq mode-name "lctags-dispatcher" major-mode 'lctags-dispatcher) 130 | ) 131 | (setq buffer-read-only t) 132 | (fit-window-to-buffer) 133 | (beginning-of-buffer)) 134 | 135 | 136 | (defun lctags-dispatch-mode () 137 | "" 138 | (interactive) 139 | (setq lctags-dispatch-prev-window-conf 140 | (current-window-configuration)) 141 | (let ((buf (get-buffer-create lctags-dispatch-buf-name))) 142 | (delete-other-windows) 143 | (split-window-vertically) 144 | (other-window 1) 145 | (switch-to-buffer buf) 146 | (setq lctags-dispatch-menu-info-current lctags-dispatch-menu-info) 147 | (lctags-dispatch-redraw lctags-dispatch-menu-info)) 148 | ) 149 | 150 | (defface lctags-dispatch-valid-bind-face 151 | '((t 152 | :foreground "red")) 153 | "valid bind face") 154 | (defvar lctags-dispatch-valid-bind-face 'lctags-dispatch-valid-bind-face) 155 | 156 | (defface lctags-dispatch-invalid-bind-face 157 | '((t 158 | :foreground "gray30")) 159 | "valid bind face") 160 | (defvar lctags-dispatch-invalid-bind-face 'lctags-dispatch-invalid-bind-face) 161 | 162 | 163 | (defun lctags-dispatch-draw-menu (menu-info current-info &optional prefix ) 164 | (mapcar (lambda (menu) 165 | (when (> (+ (current-column) 166 | (length prefix) 167 | (length (lctags-dispatch-menu-get-name menu)) 5) 168 | (window-width)) 169 | (insert "\n")) 170 | (insert (format "%s%s: %s " 171 | (or prefix "") 172 | (if (eq menu-info current-info) 173 | (propertize 174 | (lctags-dispatch-menu-get-bind menu) 175 | 'face 'lctags-dispatch-valid-bind-face) 176 | (propertize 177 | (lctags-dispatch-menu-get-bind menu) 178 | 'face 'lctags-dispatch-invalid-bind-face)) 179 | (lctags-dispatch-menu-get-name menu) 180 | )) 181 | (when (lctags-dispatch-menu-get-submenu menu) 182 | (insert "\n") 183 | (lctags-dispatch-draw-menu 184 | (lctags-dispatch-menu-get-submenu menu) 185 | current-info 186 | (concat prefix " ")) 187 | (insert "\n") 188 | )) 189 | menu-info)) 190 | 191 | 192 | (provide 'lctags-dispatch) 193 | -------------------------------------------------------------------------------- /doc/callgraph.org: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #+AUTHOR: ifritJP 3 | #+STARTUP: nofold 4 | #+OPTIONS: ^:{} 5 | 6 | #+TITLE: C言語/C++ コードを解析してインタラクティブなコールグラフ表示 by lctags 7 | 8 | lctags で解析した C言語/C++ コードの情報を基に、 9 | インタラクティブなコールグラフ表示に対応しました。 10 | 11 | [[https://raw.githubusercontent.com/ifritJP/lctags/master/doc/callgraph.png]] 12 | 13 | 従来からコールグラフ表示を対応していましたが、 14 | それは事前に設定したコールグラフの深さ以内の全コールを辿るグラフを静的に生成するもので、 15 | この方法だと次の問題がありました。 16 | 17 | - 辿りたい関数コール以外のパスまで展開されてしまう 18 | - すぐに巨大なグラフになってしまう 19 | - 設定された深さの全コールを解析&グラフ作成するため時間がかかる 20 | 21 | この問題を解決するため、 22 | インタラクティブに関数コールを展開可能なコールグラフ表示に対応しました。 23 | 24 | このコールグラフは次の機能を持ちます。 25 | 26 | - 動的に関数コールを展開/格納可能 27 | - 最低限のコール情報しかアクセスしないため反応が早い 28 | - 関数ポインタを利用した関数コールを展開可能 29 | - ブラウザで選択した関数定義を emacs で表示 30 | 31 | lctags 全般の紹介は次を参照してください。 32 | 33 | - C/C++ ソースコードタグシステム lctags の紹介 34 | https://qiita.com/dwarfJP/items/982ef7ee3f3bfd435156 35 | 36 | * サンプル 37 | 38 | https://ifritjp.github.io/sample/lctags/func-call-graph.html 39 | 40 | ノードをクリックしてください。 41 | 42 | このサンプルはコール情報をデータとして持っていますが、 43 | 実際にはノード展開毎に lctags にコール情報を問い合わせています。 44 | 45 | * 構成 46 | 47 | コールグラフは D3.js を利用してブラウザ上に svg で表示します。 48 | D3.js と svg に対応したブラウザを用意してください。 49 | イマドキのブラウザなら大抵対応しています。 50 | 51 | このコールグラフは、ブラウザで表示するため HTTPD を必要とします。 52 | lctags では emacs を HTTPD とします。 53 | 54 | *emacs との連携機能を省いた python 版の HTTPD を作成しました。記事の末尾を参照してください。* 55 | 56 | 57 | * 使用方法 58 | 59 | ここではコールグラフを表示するための手順を説明します。 60 | 61 | ** emacs の設定 62 | 63 | emacs を HTTPD として利用するため、 64 | simple-httpd(https://github.com/skeeto/emacs-http-server) を emacs に組込んでください。 65 | 66 | simple-httpd は emacs の package-install に対応しているので、簡単に組込めます。 67 | 68 | 組込み後、次を実行すれば emacs が HTTPD となります。 69 | 70 | #+BEGIN_SRC lisp 71 | (httpd-start) 72 | #+END_SRC 73 | 74 | ** プロジェクトの登録 75 | 76 | コールグラフを表示したいプロジェクトのソースを、emacs で開いてください。 77 | 78 | ここで C-c l を実行すると、次のバッファが表示されます。 79 | 80 | #+BEGIN_SRC txt 81 | l: list 82 | d: def-at s: def-search r: ref-at c: call-at C: callee-at 83 | i: inc I: incSrc f: file 84 | g: graph 85 | c: setCookie r: caller e: callee s: symbol i: inc 86 | I: incSrc 87 | G: generate 88 | m: dump-member-at e: convert-enumName-at E: form-enum 89 | i: insert 90 | c: insert-to-call-function 91 | h: highlight 92 | h: highlight-at r: rescan g: grep c: clear 93 | r: refactoring 94 | s: sub-routine r: rename 95 | m: misc 96 | e: expand-macro g: grep-cursor 97 | u: update 98 | u: this file d: this directory A: all 99 | #+END_SRC 100 | 101 | ここで g c を入力してください。 102 | これによって、現在のプロジェクトが HTTPD に登録されます。 103 | 104 | ** ブラウザでアクセス 105 | 106 | D3.js と svg に対応したブラウザで、以降の手順に沿って操作してください。 107 | 108 | *** アクセス URL 109 | 110 | 次の URL にブラウザでアクセスしてください。 111 | 112 | #+BEGIN_SRC txt 113 | http://xxx.xxx.xxx.xxx:8080/lctags 114 | #+END_SRC 115 | 116 | ここで、 xxx.xxx.xxx.xxx は emacs を動かしている PC のアドレスです。 117 | emacs を動かしている PC とブラウザを動かしている PC が同じ場合は、 118 | localhost で OK です。 119 | 120 | 8080 は HTTPD のポートです。 121 | 122 | simple-httpd のデフォルトポートは 8080 ですが、 123 | 設定によっては別のポートを利用している可能性があります。 124 | 125 | 上記 URL にアクセスすると、 126 | 入力フォームとプロジェクトのパスを示したリンクが表示されているはずです。 127 | ここでは、パスのリンクをクリックしてください。 128 | 129 | 上記の手順で複数のプロジェクトを登録している場合は、 130 | このリンクがプロジェクト分表示されます。 131 | 132 | *** ディレクトリ、ファイル、関数選択 133 | 134 | プロジェクトを選択すると、 135 | そのプロジェクトのディレクトリリストが表示されます。 136 | コールグラフを確認したいソースを含むディレクトリを選択してください。 137 | 138 | ディレクトリを選択すると、ディレクトリに含まれるソースファイルリストが表示されます。 139 | 確認したい関数を含むソーフファイルを選択してください。 140 | 141 | ソースファイルを選択すると、ソースファイル内に含まれる関数リストが表示されます。 142 | 確認したい関数を選択してください。 143 | 144 | *** コールグラフ 145 | 146 | 関数を選択すると、コールグラフ表示画面になります。 147 | 148 | 最初は選択した関数のノードが左端に表示されます。 149 | このノードをクリックすることで、その関数からコールしている関数が展開されます。 150 | 151 | 辿りたい関数のノードをクリックしていくことで、所望のコールグラフを得られます。 152 | 153 | このコールグラフは次の機能を持ちます。 154 | 155 | - 動的に関数コールを展開/格納可能 156 | - ノードをクリックで展開し、再度クリックで格納します。 157 | - 展開したノードは赤く表示されます。 158 | - 格納したノードは緑で表示されます。 159 | - 関数名の色を状態に応じて変更 160 | - コールグラフ上に同じ関数が存在する場合、その関数名を緑で表示します。 161 | - 同じ関数が存在し、かつ展開済みの場合、その関数名を青で表示します。 162 | - 関数が関数ポインタの場合、その関数名の背景を赤で表示します。 163 | - なお、色の変化はノードを展開した時に反映します。 164 | - 関数のノードを右クリックすると、その関数の定義箇所を emacs で表示 165 | - 右クリックしたノードの関数が外部関数だった場合は、定義箇所不明なため表示しません。 166 | - ツリーのリンクを右クリックすると、そのリンクの箇所を emacs で表示 167 | - リンクの候補が複数ある場合は、最初に見つかった箇所を表示します。 168 | - たとえば、 callee モードで複数関数呼び出ししている場合、 169 | その中の 1 つを表示します。 170 | - コールグラフの空白部分をドラッグした時の動きを切り替え可能 171 | - move 172 | - モードをすることで、コールグラフを移動 173 | - expandResion 174 | - 選択したノードを展開する 175 | - closeResion 176 | - 選択したノードを格納する 177 | 178 | **** 関数ポインタの動的関数コール 179 | 180 | 関数ポインタを利用した動的関数コールでは、 181 | 具体的にどの関数がコールされるかが分かりません。 182 | もちろん、ソースコードを静的解析することである程度は 183 | 動的に実行される関数を特定することは可能です。 184 | しかし、それには非常に多くの解析時間を要します。 185 | 186 | そこでこのコールグラフでは、 lctags による動的関数コール解析ではなく、 187 | ユーザによる動的関数コール特定機能を提供しています。 188 | 189 | 具体的には、 190 | ユーザが lctags.conf で次の関数をカスタマイズすることで、 191 | 動的関数コールのコール先関数を特定することができます。 192 | 193 | #+BEGIN_SRC lua 194 | function conf:getIndirectFuncList( symbol ) 195 | return {} 196 | end 197 | #+END_SRC 198 | 199 | この関数は、動的引数コールしている関数型の typedef 名を引数 symbol に持ちます。 200 | 201 | この symbol に応じて、コール先の関数名配列を返すことで、 202 | それを動的関数コールの呼び出し先関数として処理します。 203 | 204 | 例えば次のようなソースで動的関数コールしている場合、 205 | 206 | #+BEGIN_SRC lua 207 | typedef void (test_indirect_t)(void); 208 | 209 | static void test_indirect( void ) 210 | { 211 | } 212 | 213 | void sub( test_indirect_t * pFunc) { 214 | pFunc(); 215 | } 216 | 217 | static void foo() 218 | { 219 | sub( test_indirect ); 220 | } 221 | #+END_SRC 222 | 223 | foo() のコールグラフは次のようなコールグラフとなります。 224 | 225 | #+BEGIN_SRC txt 226 | foo --> sub --> test_indirect_t 227 | #+END_SRC 228 | 229 | このとき test_indirect_t のノードをクリックすると、 230 | 動的関数コールの呼び出し先を特定するために 231 | getIndirectFuncList() が呼び出されます。 232 | 233 | そして getIndirectFuncList( symbol, mode ) の symbol には、 234 | ::test_indirect_t が与えられます。 235 | test_indirect_t の関数型に対応する関数名は test_indirect なので、 236 | 次のようにすることで動的関数コールの呼び出し先を指定できます。 237 | mode 引数は、 "callee", "caller" 等のアクセスモードを示します。 238 | 239 | #+BEGIN_SRC lua 240 | function conf:getIndirectFuncList( symbol, mode ) 241 | if symbol == "::test_indirect_t" then 242 | return { "test_indirect" } 243 | end 244 | return {} 245 | end 246 | #+END_SRC 247 | 248 | これにより、次のようにコールグラフが展開されます。 249 | 250 | #+BEGIN_SRC txt 251 | foo --> sub --> test_indirect_t --> test_indirect 252 | #+END_SRC 253 | 254 | この動的関数コール特定機能はテスト段階のため、 255 | 関数仕様等を変更する可能性が高いです。 256 | 257 | 258 | なお、lctags.conf はプロジェクトディレクトリ内で次のコマンドを実行することで、 259 | 雛形が作成されます。 260 | 261 | #+BEGIN_SRC txt 262 | lctags copyConf 263 | #+END_SRC 264 | 265 | * D3.js のレイアウトについて 266 | 267 | 今回はコールグラフに D3.js の tree レイアウトを利用しました。 268 | 269 | tree レイアウトによって、関数コールの構造が直感的に分かると思います。 270 | 271 | 当初は force レイアウトを利用しようと思っていたのですが、 272 | プロトタイプを作成してみると複雑な関数コールではリンクが絡み合ってしまい、 273 | 使い物になりませんでした。 274 | 275 | force レイアウトは見た目が面白いのですが、 276 | 関数コールのような複雑な関係を持つデータの可視化には向いていないようでした。 277 | 278 | ただ、force レイアウトでは、 279 | ループしている関数コールなどが直感的に分かるという利点もあるため、 280 | 複雑なレイアウトでも絡み合わない制御が出来れば、 281 | tree レイアウト以上に良い結果を得られると思います。 282 | 283 | force レイアウトのプロトタイプは、lctags に含めてあります。 284 | 興味のある方は動かしてみてください。 285 | 286 | * python 版 HTTPD について 287 | 288 | コールグラフ確認サーバを立ててチームでコールグラフ機能を共有する場合、 289 | emacs が HTTPD だと色々と不便です。 290 | 291 | そこで、 emacs 連携機能を省いた python 版の HTTPD を作成しました。 292 | 293 | #+BEGIN_SRC txt 294 | $ python httpd.py [-lctags=path] port dbpath 295 | #+END_SRC 296 | 297 | httpd.py は src/lisp/httpd.py に格納しています。 298 | httpd.py は 2.x 系 python を利用します。 299 | -------------------------------------------------------------------------------- /src/lisp/lctags-highlight.el: -------------------------------------------------------------------------------- 1 | (defvar lctags-search-token-color-list 2 | '("OrangeRed4" "OrangeRed3" "dark green" "dark magenta" "dark blue" 3 | "yellow4" "DeepSkyBlue4" "gray34")) 4 | 5 | (defface lctags-search-token-color-default 6 | `((t 7 | :background ,(car lctags-search-token-color-list))) 8 | "default highlight face") 9 | 10 | (defvar lctags-search-token-color-default 'lctags-search-token-color-default) 11 | 12 | 13 | (setq lctags-search-token-color nil) 14 | (setq lctags-search-token-color-first t) 15 | 16 | (setq lctags-highlight-locationSet-list nil) 17 | (setq lctags-highlight-overlay-list nil) 18 | (setq lctags-highlight-overlay-mark-list nil) 19 | 20 | 21 | (defun lctags-goto-line-column (line column) 22 | (let (pos) 23 | (goto-line line) 24 | (setq pos (point)) 25 | (forward-char (1- column)) 26 | (while (not (eq (lctags-get-column) column)) 27 | (forward-char -1)) 28 | )) 29 | 30 | (defun lctags-get-point-at-line-column (line column) 31 | (let ((now (point)) pos) 32 | (setq now (point)) 33 | (lctags-goto-line-column line column) 34 | (setq pos (point)) 35 | (goto-char now) 36 | pos)) 37 | 38 | (defun lctags-location-item-get-kind (item) 39 | (lctags-xml-get-val item 'kind)) 40 | 41 | (defun lctags-location-item-get-symbol (item) 42 | (lctags-xml-get-val item 'symbol)) 43 | 44 | (defun lctags-location-item-get-file (item) 45 | (lctags-xml-get-val item 'file)) 46 | 47 | (defun lctags-location-item-get-line (item) 48 | (string-to-number (lctags-xml-get-val item 'line)) 49 | ) 50 | 51 | (defun lctags-location-item-get-column (item) 52 | (string-to-number (lctags-xml-get-val item 'column)) 53 | ) 54 | 55 | (defun lctags-location-item-get-end-line (item) 56 | (string-to-number (lctags-xml-get-val item 'endLine)) 57 | ) 58 | 59 | (defun lctags-location-item-get-end-column (item) 60 | (string-to-number (lctags-xml-get-val item 'endColumn)) 61 | ) 62 | 63 | (defun lctags-location-item-get-point (item) 64 | (lctags-get-point-at-line-column (lctags-location-item-get-line item) 65 | (lctags-location-item-get-column item))) 66 | 67 | (defun lctags-location-item-get-end-point (item) 68 | (lctags-get-point-at-line-column (lctags-location-item-get-end-line item) 69 | (lctags-location-item-get-end-column item))) 70 | 71 | 72 | 73 | (defun lctags-highlight-clear () 74 | (interactive) 75 | (dolist (overlay lctags-highlight-overlay-list) 76 | (delete-overlay overlay)) 77 | (setq lctags-highlight-overlay-list nil) 78 | (setq lctags-highlight-overlay-mark-list nil) 79 | (setq lctags-search-token-color nil) 80 | (setq lctags-highlight-locationSet-list nil) 81 | (setq lctags-search-token-color-first t) 82 | (dolist (overlay (overlays-in (point-min) (point-max))) 83 | (when (string-match "^lctags-" (symbol-name (overlay-get overlay 'face))) 84 | (delete-overlay overlay)))) 85 | 86 | 87 | (defun lctags-highlight-at () 88 | (interactive) 89 | (lctags-highlight-at-op)) 90 | 91 | (defun lctags-highlight-make-face (&optional color) 92 | (when (not lctags-search-token-color) 93 | (setq lctags-search-token-color lctags-search-token-color-list) 94 | (setq lctags-search-token-color-first (not lctags-search-token-color-first))) 95 | (when (not color) 96 | (setq color (read-color (format "face-color?(default %s): " 97 | (car lctags-search-token-color) ) 98 | nil t nil))) 99 | (when (or (eq color t) (equal color "")) 100 | (setq color (car lctags-search-token-color)) 101 | (setq lctags-search-token-color (cdr lctags-search-token-color))) 102 | (setq face (make-face (make-symbol (format "lctags-search-token-face-%s" color)))) 103 | (set-face-attribute face nil 104 | :background color 105 | ;;:overline lctags-search-token-color-first 106 | :underline lctags-search-token-color-first) 107 | face 108 | ) 109 | 110 | (defun lctags-highlight-at-op (&optional face) 111 | (let ((buffer (lctags-get-process-buffer t)) 112 | (cur-buf (current-buffer)) 113 | highlight-info overlay color mark locationSet-list) 114 | (when (or (not face) (eq face 'auto)) 115 | (setq face (lctags-highlight-make-face nil))) 116 | (lctags-execute-xml cur-buf buffer 117 | (buffer-string) 118 | 'highlight-info 'ref 'locationSet 119 | "ref-at-all" 120 | (buffer-file-name) 121 | (number-to-string (lctags-get-line)) 122 | (number-to-string (lctags-get-column)) "-i") 123 | (cond 124 | (lctags-diag-info 125 | (lctags-helm-display-diag)) 126 | (t 127 | (setq locationSet-list (lctags-xml-get-list highlight-info 'locationSet)) 128 | (dolist (locationSet locationSet-list) 129 | (dolist (location (lctags-xml-get-list locationSet 'location)) 130 | (when (equal (lctags-location-item-get-file location) 131 | (buffer-file-name)) 132 | (setq overlay 133 | (make-overlay (lctags-get-point-at-line-column 134 | (lctags-location-item-get-line location) 135 | (lctags-location-item-get-column location)) 136 | (lctags-get-point-at-line-column 137 | (lctags-location-item-get-end-line location) 138 | (lctags-location-item-get-end-column location)))) 139 | (setq mark (point-marker)) 140 | (if (not lctags-highlight-overlay-list) 141 | (progn 142 | (setq lctags-highlight-overlay-list (list overlay)) 143 | (setq lctags-highlight-overlay-mark-list 144 | (list (list mark face))) 145 | ) 146 | (add-to-list 'lctags-highlight-overlay-list overlay) 147 | (add-to-list 'lctags-highlight-overlay-mark-list 148 | (list mark face)) 149 | ) 150 | (overlay-put overlay 'face face) 151 | )) 152 | (setq face (lctags-highlight-make-face t))) 153 | (setq lctags-highlight-locationSet-list 154 | (append locationSet-list lctags-highlight-locationSet-list)) 155 | (save-excursion 156 | (let ((location (car (lctags-xml-get-list (car locationSet-list) 'location)))) 157 | (lctags-goto-line-column (lctags-location-item-get-line location) 158 | (lctags-location-item-get-column location)) 159 | (lctags-highlight-grep-at) 160 | )) 161 | (pop-to-buffer cur-buf) 162 | )))) 163 | 164 | 165 | (defun lctags-highlight-rescan () 166 | (interactive) 167 | (save-excursion 168 | (let ((list (copy-sequence lctags-highlight-overlay-mark-list))) 169 | (lctags-highlight-clear) 170 | (dolist (info list) 171 | (let ((mark (nth 0 info)) 172 | (face (nth 1 info))) 173 | (goto-char mark) 174 | (lctags-highlight-at-op face)))))) 175 | 176 | (defun lctags-highlight-grep-at () 177 | "カーソル位置のハイライト箇所の grep バッファを作成する" 178 | (interactive) 179 | (let ((pos (point)) 180 | (dir default-directory) 181 | (cur-buf (current-buffer)) 182 | target-locationSet) 183 | (dolist (locationSet lctags-highlight-locationSet-list) 184 | (dolist (location (lctags-xml-get-list locationSet 'location)) 185 | (when (and (equal (lctags-location-item-get-file location) 186 | (buffer-file-name)) 187 | (when (and (<= (lctags-location-item-get-point location) pos) 188 | (> (lctags-location-item-get-end-point location) pos)) 189 | (setq target-locationSet locationSet) 190 | ))))) 191 | (if (not target-locationSet) 192 | (message "not found") 193 | (let ((buffer (get-buffer-create "*lctags-highlight-grep*")) 194 | line begin-pos) 195 | (with-current-buffer buffer 196 | (setq buffer-read-only nil) 197 | (setq default-directory dir) 198 | (erase-buffer) 199 | (insert (format "grep for \"%s\".\n\n" 200 | (lctags-location-item-get-symbol 201 | (car (lctags-xml-get-list target-locationSet 'location))))) 202 | (dolist (location (lctags-xml-get-list target-locationSet 'location)) 203 | (setq line (lctags-location-item-get-line location)) 204 | (insert (format "%s:%d: %s\n" 205 | (file-relative-name (lctags-location-item-get-file location) 206 | default-directory) 207 | line 208 | (with-current-buffer cur-buf 209 | (save-excursion 210 | (goto-line line) 211 | (beginning-of-line) 212 | (setq begin-pos (point)) 213 | (end-of-line) 214 | (buffer-substring begin-pos (point))) 215 | ) 216 | ))) 217 | (grep-mode) 218 | (run-hooks 'grep-setup-hook) 219 | ) 220 | (lctags-switch-to-buffer-other-window buffer) 221 | (with-current-buffer buffer 222 | (fit-window-to-buffer nil (/ (frame-height) 3))) 223 | (beginning-of-buffer) 224 | )))) 225 | 226 | 227 | (provide 'lctags-highlight) 228 | -------------------------------------------------------------------------------- /src/test/hoge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | #define NNNN 6 | #include <./hoge.h> 7 | 8 | #ifdef AAA 9 | #endif 10 | #if BBB 11 | #endif 12 | 13 | #define ARRAY_LENGTH 10 14 | typedef int int_t; 15 | 16 | typedef enum { enum_val1, 17 | enum_val2, 18 | enum_val3 = enum_val1 + 1000 19 | } enum_t; 20 | 21 | typedef struct zzzz ZZZZ; 22 | struct zzzz { 23 | int aaaa; 24 | #include 25 | int cccc; 26 | va_list argp; 27 | }; 28 | 29 | namespace iiii { 30 | #include 31 | #if 0 32 | #endif 33 | } 34 | 35 | typedef struct TEST { 36 | int aaaa; 37 | } TEST; 38 | 39 | typedef ZZZZ (Callback_t)( TEST * pTest ); 40 | 41 | struct yyyy; 42 | typedef struct yyyy YYYY; 43 | struct yyyy { 44 | int kkkk; 45 | Callback_t * pCallback; 46 | }; 47 | 48 | typedef struct { 49 | char charValue; 50 | char * charValueP; 51 | char charValueA[ ARRAY_LENGTH ]; 52 | char * charValueAP[ ARRAY_LENGTH ]; 53 | char charValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 54 | char * charValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 55 | 56 | short shortValue; 57 | short * shortValueP; 58 | short shortValueA[ ARRAY_LENGTH ]; 59 | short * shortValueAP[ ARRAY_LENGTH ]; 60 | short shortValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 61 | short * shortValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 62 | 63 | int intValue; 64 | int * intValueP; 65 | int intValueA[ ARRAY_LENGTH ]; 66 | int * intValueAP[ ARRAY_LENGTH ]; 67 | int intValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 68 | int * intValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 69 | 70 | long longValue; 71 | long * longValueP; 72 | long longValueA[ ARRAY_LENGTH ]; 73 | long * longValueAP[ ARRAY_LENGTH ]; 74 | long longValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 75 | long * longValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 76 | 77 | long long longLongValue; 78 | long long * longLongValueP; 79 | long long longLongValueA[ ARRAY_LENGTH ]; 80 | long long * longLongValueAP[ ARRAY_LENGTH ]; 81 | long long longLongValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 82 | long long * longLongValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 83 | } struct_value_t; 84 | 85 | typedef struct { 86 | char charValue; 87 | char * charValueP; 88 | char charValueA[ ARRAY_LENGTH ]; 89 | char * charValueAP[ ARRAY_LENGTH ]; 90 | char charValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 91 | char * charValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 92 | 93 | short shortValue; 94 | short * shortValueP; 95 | short shortValueA[ ARRAY_LENGTH ]; 96 | short * shortValueAP[ ARRAY_LENGTH ]; 97 | short shortValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 98 | short * shortValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 99 | 100 | int intValue; 101 | int * intValueP; 102 | int intValueA[ ARRAY_LENGTH ]; 103 | int * intValueAP[ ARRAY_LENGTH ]; 104 | int intValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 105 | int * intValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 106 | 107 | long longValue; 108 | long * longValueP; 109 | long longValueA[ ARRAY_LENGTH ]; 110 | long * longValueAP[ ARRAY_LENGTH ]; 111 | long longValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 112 | long * longValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 113 | 114 | long long longLongValue; 115 | long long * longLongValueP; 116 | long long longLongValueA[ ARRAY_LENGTH ]; 117 | long long * longLongValueAP[ ARRAY_LENGTH ]; 118 | long long longLongValueA2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 119 | long long * longLongValueAP2[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 120 | } struct_value2_t; 121 | 122 | 123 | 124 | void test( int argInt[ ARRAY_LENGTH ], int_t argInt2[ ARRAY_LENGTH ] ); 125 | 126 | 127 | void test( int argInt[ ARRAY_LENGTH ], int_t argInt2[ ARRAY_LENGTH ] ) 128 | { 129 | int_t array2Int[ ARRAY_LENGTH ][ ARRAY_LENGTH ]; 130 | struct_value_t stVal; 131 | struct_value_t * stValP; 132 | struct_value_t stValA[ ARRAY_LENGTH ]; 133 | struct_value_t * stValPA[ ARRAY_LENGTH ]; 134 | struct_value2_t stVal2, stVal3; 135 | stVal.longValue = 0; 136 | stVal2.longValue = 0; 137 | } 138 | 139 | 140 | class TestClass { 141 | 142 | public: 143 | TestClass() { } 144 | 145 | public: 146 | int func() { 147 | return 0; 148 | } 149 | }; 150 | 151 | namespace ns1 { 152 | 153 | int g_global = 0; 154 | static int s_static = 0; 155 | 156 | typedef struct { 157 | Callback_t * pCallback; 158 | struct { 159 | int efgh; 160 | } abcd; 161 | } struct_func_t; 162 | 163 | namespace ns2 { 164 | typedef struct { 165 | Callback_t * pCallback; 166 | struct { 167 | int z1; 168 | int z2; 169 | }; 170 | union { 171 | int z3; 172 | int z4; 173 | }; 174 | } struct_func_t; 175 | 176 | static int s_static2 = 0; 177 | 178 | /** 179 | * func0 comment 180 | * 181 | * ほげ 182 | */ 183 | int func0( ZZZZ (func)( TEST * pClass ) ) { 184 | char buf[ 1 ]; 185 | func( NULL ); 186 | return 0; 187 | } 188 | 189 | /** 190 | * func1 comment 191 | * 192 | * あげ 193 | */ 194 | int func1( Callback_t callback ) { 195 | char buf[ 1 ] = { enum_val1 }; 196 | callback( NULL ); 197 | return 0; 198 | } 199 | 200 | /** func2 comment */ 201 | static ZZZZ func2( TEST * pTest ) { 202 | char buf[ 2 ]; 203 | static int ps_local = s_static; 204 | g_global = s_static; 205 | ZZZZ aaa; 206 | return aaa; 207 | } 208 | 209 | int func5( struct_func_t * pClass ) { 210 | char buf[5]; 211 | pClass-> pCallback( NULL ); 212 | pClass->z2 = 1; 213 | TestClass aaa; 214 | return 0; 215 | } 216 | 217 | /** func3 comment */ 218 | int func3( Callback_t * pCallback ) { 219 | char buf[50]; 220 | { 221 | char buf[100]; 222 | func1( func2 ); 223 | { 224 | char buf[100]; 225 | func3( func2 ); 226 | } 227 | } 228 | { 229 | char buf[10]; 230 | func2( NULL ); 231 | (*pCallback)( NULL ); 232 | func0( func2 ); 233 | func5( NULL ); 234 | } 235 | ::ns1::struct_func_t val; 236 | val.abcd.efgh = 1; 237 | return enum_val1; 238 | } 239 | 240 | int func4( TestClass * pTest ) { 241 | char buf[50]; 242 | pTest->func(); 243 | return 0; 244 | } 245 | 246 | 247 | } 248 | 249 | namespace ns31 { 250 | 251 | void sub( char ** ppArg ) { 252 | for ( ; *ppArg != NULL; ppArg++ ) { 253 | printf( "%s\n", *ppArg ); 254 | int aa; 255 | aa = 0; 256 | } 257 | } 258 | 259 | void sub2() { 260 | ns2::func3( ns2::func2 ); 261 | } 262 | } 263 | 264 | struct yyyy sub2() { 265 | YYYY aYYYYY; 266 | aYYYYY.kkkk = 0; 267 | aYYYYY.pCallback( NULL ); 268 | return aYYYYY; 269 | } 270 | } 271 | 272 | namespace ns4 { 273 | int values[] = { 274 | ns1::g_global 275 | }; 276 | } 277 | 278 | template class TEMP 279 | { 280 | typedef T1 tmp_t; 281 | public: 282 | const tmp_t s_val; 283 | 284 | TEMP(T1 val ) : s_val( val ) 285 | {} 286 | 287 | T2 aaaaaa(const T1 &t) const 288 | { 289 | return t; 290 | } 291 | 292 | T2 bbbbbb(const T1 &t) const 293 | { 294 | return aaaaaa( t); 295 | } 296 | }; 297 | 298 | namespace { 299 | int rootval; 300 | 301 | void func() 302 | { 303 | TEMP tmp(1); 304 | TEMP tmp2(1); 305 | tmp.aaaaaa(2); 306 | 307 | if ( 1 ) { 308 | rootval = 1; 309 | ns1::sub2(); 310 | } 311 | } 312 | } 313 | 314 | typedef int INT_t; 315 | 316 | static int s_vals[] = { enum_val1, enum_val2 }; 317 | 318 | class TestClass2 319 | { 320 | public: 321 | ~TestClass2() 322 | { 323 | enum { INN }; 324 | YYYY aYYYYY = { 0 }; 325 | int val = INN; 326 | int val2[] = { enum_val1 }; 327 | val = 0; 328 | } 329 | 330 | #define POINTER(X) (&(X)) 331 | #define VAL5P (&val5) 332 | #define SETVAL6 int * pVal6 = &val6; 333 | int * func( int val1, int val2, int * val3, int val4, int val5, int val6 ) 334 | { 335 | int abcddd[ 10 ][ 1 ] = { 0 }; 336 | int aaa[ 1 ][ 2 ] = { 0 }; 337 | if ( 1 ) { 338 | } 339 | else{ 340 | { 341 | int * pVal = POINTER(val1); 342 | int valval2 = -val2; 343 | int valval3 = *(int *)(val3 + 1); 344 | int * pAAA = &aaa[0][0]; 345 | //int * pValval5 = VAL5P; 346 | //SETVAL6; 347 | abcddd[0][0] = val1; 348 | *pVal = 1; 349 | val4 = 2; 350 | s_vals[0] = 3; 351 | *pAAA = 4; 352 | return NULL; 353 | } 354 | } 355 | } 356 | } CLASS2, CLASS3; 357 | -------------------------------------------------------------------------------- /doc/subroutine.org: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #+AUTHOR: ifritJP 3 | #+STARTUP: nofold 4 | 5 | #+TITLE: lctags を使って C/C++ ソースコードをリファクタリング(サブルーチン化編) 6 | 7 | コーディングしていると、関数が肥大化していくことが良くあります。 8 | 9 | 関数の肥大化は、メンテナンス性や可読性が落ちる原因になるため、 10 | リファクタリングで関数内の処理を分割するサブルーチン化作業が必要です。 11 | 12 | サブルーチン化作業はほとんど機械的に出来ますが、 13 | 分割する関数の前後処理に気をつけないと、バグってしまうこともあります。 14 | 15 | lctags は、そんなサブルーチン化作業をサポートします。 16 | 17 | 18 | lctags 全般の紹介は次を参照してください。 19 | 20 | - C/C++ ソースコードタグシステム lctags の紹介 21 | https://qiita.com/dwarfJP/items/982ef7ee3f3bfd435156 22 | 23 | 24 | * 事前準備 25 | 26 | lctags のサブルーチン化処理は、ブロック文 {} を対象としています。 27 | よってブロックでない処理をサブルーチン化する場合は、事前にブロック化しておく必要があります。 28 | ブロック化は単に {} で括るだけです。 29 | 30 | * 使用方法 31 | 32 | emacs でソースファイルを開き、サブルーチン化したいブロックの先頭にカーソルを合せ、 33 | 次を実行します。 34 | 35 | M-x lctags-split-it 36 | 37 | あるいは C-c l でメニューを開いて、 r s でも可能です。 38 | 39 | これにより、ブロック内の処理を解析してサブルーチンを生成し、 40 | 新しいバッファに生成したサブルーチンと、そのサブルーチンをコールするコードが 41 | 表示されます。 42 | 43 | 後は、この表示されたコードに置き換えればサブルーチン化作業が完了します。 44 | 45 | * デモ 46 | 47 | 次のリンクは、lctags を使って emacs から C 言語の関数をサブルーチン化する操作のデモ動画です。 48 | 49 | [[https://youtu.be/_9xPcL5VNVM]] 50 | 51 | * チュートリアル 52 | 53 | ** 準備 54 | 55 | lctags の DB を作成し、次の内容のソースコードを登録しておきます。 56 | 57 | #+BEGIN_SRC C 58 | typedef struct { 59 | int val; 60 | } type_t; 61 | int func6( int val ) 62 | { 63 | type_t typ; 64 | if ( val == 0 ) { 65 | val = 1; 66 | typ.val = val; 67 | return 0; 68 | } 69 | return 1; 70 | } 71 | #+END_SRC 72 | 73 | ** サブルーチン化 74 | 75 | 上記ソースコードを開き、 ~if ( val == 0 ) {~ の "{" の箇所にカーソルを合せます。 76 | 77 | ここで C-c l と入力し、lctags メニューを開きます。 78 | lctags メニューでは、 refactoring の 'r' を押し、さらに subroutine の 's' を押します。 79 | 80 | これにより、対象ブロックの解析とサブルーチンの生成が行なわれ、次の内容のバッファが開かれます。 81 | 82 | #+BEGIN_SRC C 83 | /* please edit 'x' or 'o' and symbol and order of following items, 84 | and push C-c C-c to update. 85 | format: 86 | :[xor]:argName:orgName 87 | x is to pass value. 88 | o is to pass address of value. 89 | r is to pass value and to return directly. 90 | ---------------- 91 | :o:indirect-return 92 | :o:pTyp:typ 93 | :o:pVal:val 94 | ---------------- 95 | */ 96 | //======= call ====== 97 | { 98 | int funcRet__ = 0; 99 | if ( func6__sub( &funcRet__, &typ, &val ) ) { 100 | return funcRet__; 101 | } 102 | } 103 | //======= sub routine ====== 104 | static int func6__sub( int* pFuncRet__, type_t* pTyp, int* pVal ) 105 | { 106 | (*pVal) = 1; 107 | pTyp->val = (*pVal); 108 | return *pFuncRet__= 0, 1; 109 | } 110 | #+END_SRC 111 | 112 | ~=== sub routin ===~ 以降は、サブルーチン化した処理です。 113 | この処理をソースコードにコピーします。 114 | 115 | ~=== call ===~ 以降は、サブルーチン化した関数を呼び出す処理です。 116 | この処理を、 "if ( val == 0 ) {" の "{" の箇所に置き換えます。 117 | 118 | 119 | 置き換えると次のようになります。 120 | #+BEGIN_SRC C 121 | static int func6__sub( int* pFuncRet__, type_t* pTyp, int* pVal ) 122 | { 123 | (*pVal) = 1; 124 | pTyp->val = (*pVal); 125 | return *pFuncRet__= 0, 1; 126 | } 127 | int func6( int val ) 128 | { 129 | type_t typ; 130 | if ( val == 0 ) { 131 | int funcRet__ = 0; 132 | if ( func6__sub( &funcRet__, &typ, &val ) ) { 133 | return funcRet__; 134 | } 135 | } 136 | } 137 | #+END_SRC 138 | 139 | これでサブルーチン化は完成です。 140 | 141 | ** 生成するサブルーチンの説明 142 | 143 | 念のためサブルーチン化後の処理について説明しておくと、 144 | ~func6__sub()~ は戻り値が int 型の関数です。 145 | 戻り値が 0 以外の時は、サブルーチンの呼び出し側を return で終了させます。 146 | その際、戻り値は ~funcRet__~ に格納されています。 147 | 148 | また、上記処理を良く見ると、サブルーチンに渡している val が気になるのではないかと思います。 149 | 150 | サブルーチン化した処理では、 val を値渡しではなく、アドレス渡しにしています。 151 | 152 | これは何故かというと、処理内で val に対して代入を行なっているためです。 153 | 154 | 例えば、 func6() の処理が、 155 | 次のように ~if~ のブロック処理を抜けた後に val を return するような処理だった場合、 156 | 157 | #+BEGIN_SRC C 158 | int func6( int val ) 159 | { 160 | type_t typ; 161 | if ( val == 0 ) { 162 | val = 1; 163 | typ.val = val; 164 | } 165 | return val; 166 | } 167 | #+END_SRC 168 | 169 | ~if~ のブロックをサブルーチン化する場合は、 val をアドレス渡しする必要があります。 170 | 171 | ただ、今回の場合はブロックの後に val を参照していないので、 172 | 本来は val は値渡しでも問題ありません。 173 | 174 | それにもかかわらず val をアドレス渡ししています。 175 | 176 | これは、サブルーチン化の処理を安全方向に振るためです。 177 | 178 | 将来、この func6() 関数をさらに変更して、 179 | ~if~ ブロックの後に val を参照するかもしれません。 180 | また、サブルーチン化した ~func6__sub()~ 処理を変更して、 181 | 戻り値が 0 になることもあるかもしれません。 182 | 183 | そのような場合に備えて、アドレス渡しにしています。 184 | 185 | ** 値渡しの制御 186 | 187 | もしも、このアドレス渡しが気になる場合は、次の方法で値渡しにすることが可能です。 188 | 189 | 生成されたバッファの上部に表示されている次の箇所を注目してください。 190 | 191 | #+BEGIN_SRC TXT 192 | /* please edit 'x' or 'o' and symbol and order of following items, 193 | and push C-c C-c to update. 194 | format: 195 | :[xor]:argName:orgName 196 | x is to pass value. 197 | o is to pass address of value. 198 | r is to pass value and to return directly. 199 | ---------------- 200 | :o:indirect-return 201 | :o:pTyp:typ 202 | :o:pVal:val 203 | ---------------- 204 | */ 205 | #+END_SRC 206 | 207 | この ~:o:pVal:val~ を ~:x:pVal:val~ に編集し C-c C-c すると、 208 | val が値渡しになったサブルーチンコードが生成されます。 209 | 210 | 211 | #+BEGIN_SRC C 212 | { 213 | int funcRet__ = 0; 214 | if ( func6__sub( &funcRet__, &typ, val ) ) { 215 | return funcRet__; 216 | } 217 | } 218 | //======= sub routine ====== 219 | static int func6__sub( int* pFuncRet__, type_t* pTyp, int pVal ) 220 | { 221 | pVal = 1; 222 | pTyp->val = pVal; 223 | pTyp->val = pVal; 224 | return *pFuncRet__= 0, 1; 225 | } 226 | #+END_SRC 227 | 228 | ** 引数名の変更 229 | 230 | サブルーチン化したブロックの引数名は、元の変数と同じ名前になります。 231 | この変数名を違う名前に変更できます。 232 | 233 | #+BEGIN_SRC TXT 234 | /* please edit 'x' or 'o' and symbol and order of following items, 235 | and push C-c C-c to update. 236 | format: 237 | :[xor]:argName:orgName 238 | x is to pass value. 239 | o is to pass address of value. 240 | r is to pass value and to return directly. 241 | ---------------- 242 | :o:indirect-return 243 | :o:pTyp:typ 244 | :o:pVal:val 245 | ---------------- 246 | */ 247 | #+END_SRC 248 | 249 | バッファ上部に出力されている ~:o:pVal:val~ の pVal の部分を変更し C-c C-c すると、 250 | 引数が変更した名前になります。 251 | 252 | ** アドレス渡しの変数を戻り値に 253 | 254 | ブロック内で変更されている変数は、サブルーチン化の際にアドレス渡しの引数になります。 255 | この変数を戻り値とすることで、引数は値渡しに出来ます。 256 | 257 | アドレス渡しの変数を戻り値にするには、 ~:o:pVal:val~ の o の部分を r とし、 258 | C-c C-c することで更新されます。 259 | 260 | ** return 文を持つブロック 261 | 262 | reutrn 文を持つブロックをサブルーチン化すると、 263 | その return 文は ~return *pFuncRet__= 0, 1;~ のようになります。 264 | 265 | この return 文が気になる場合は、元ブロックの return 文の形にすることができます。 266 | 267 | 元ブロックの return 文の形に変更するには、 ~:o:indirect-return~ の o の部分を x とし、 268 | C-c C-c することで更新されます。 269 | 270 | この場合、サブルーチンの呼び出し側は、戻り値から return するかどうかを判別する必要があります。 271 | 272 | 273 | * カスタマイズ 274 | 275 | #+BEGIN_SRC C 276 | static int func7( int val ) 277 | { 278 | int index; 279 | for ( index = 0; index < 10; index++ ) { 280 | if ( val == 10 ) { 281 | continue; 282 | } 283 | if ( val == 20 ) { 284 | break; 285 | } 286 | if ( val == 30 ) { 287 | return 0; 288 | } 289 | } 290 | return 1; 291 | } 292 | #+END_SRC 293 | 294 | 上記ソースの for 文のブロックをサブルーチン化すると、 295 | 次のようになります。 296 | 297 | #+BEGIN_SRC C 298 | /* please edit 'x' or 'o' of following items, 299 | and push C-c C-c to update. 300 | x: val 301 | */ 302 | //======= call ====== 303 | { 304 | int funcRet__ = 0; 305 | int result__ = func7__sub( &funcRet__, val ); 306 | if ( result__ == 1 ) { return funcRet__; } 307 | else if ( result__ == 2 ) { break; } 308 | else if ( result__ == 3 ) { continue; } 309 | } 310 | 311 | //======= sub routine ====== 312 | static int func7__sub( int* pFuncRet__, int val ) 313 | { 314 | if ( val == 10 ) { 315 | return 3; 316 | } 317 | if ( val == 20 ) { 318 | return 2; 319 | } 320 | if ( val == 30 ) { 321 | return *pFuncRet__= 0, 1; 322 | } 323 | return 0; 324 | } 325 | #+END_SRC 326 | 327 | ここで、 ~func7__sub()~ 内の return 3 や return、 328 | 呼び出し側の ~result__ == 1~ や ~result__ == 2~ 等の即値が気になると思います。 329 | 330 | C では、即値は使わず define や enum 等を宣言して使用するのが定石とされています。 331 | 332 | そこで、 lctags ではこの値をカスタマイズする方法を提供しています。 333 | 334 | emacs では、次のように lctags-sub-ret-type を設定するだけです。 335 | 336 | #+BEGIN_SRC lisp 337 | (setq lctags-sub-ret-type 338 | "subMod_t/subModNone/subModReturn/subModBreak/subModContinue") 339 | #+END_SRC 340 | 341 | この設定をした際の上記処理のサブルーチン化結果は次の通りです。 342 | 343 | #+BEGIN_SRC C 344 | /* please edit 'x' or 'o' of following items, 345 | and push C-c C-c to update. 346 | x: val 347 | */ 348 | //======= call ====== 349 | { 350 | int funcRet__ = 0; 351 | subMod_t result__ = func7__sub( &funcRet__, val ); 352 | if ( result__ == subModReturn ) { return funcRet__; } 353 | else if ( result__ == subModBreak ) { break; } 354 | else if ( result__ == subModContinue ) { continue; } 355 | } 356 | 357 | //======= sub routine ====== 358 | static subMod_t func7__sub( int* pFuncRet__, int val ) 359 | { 360 | if ( val == 10 ) { 361 | return subModContinue; 362 | } 363 | if ( val == 20 ) { 364 | return subModBreak; 365 | } 366 | if ( val == 30 ) { 367 | return *pFuncRet__= 0, subModReturn; 368 | } 369 | return subModNone; 370 | } 371 | #+END_SRC 372 | 373 | 374 | lctags-sub-ret-type は、次の書式で定義します。 375 | 376 | "type/val0/val1/val2/val3" 377 | 378 | type は、サブルーチン化した関数の戻り値の型。 379 | 上記の例では ~func7_sub()~ の int が該当します。 380 | 381 | val0 〜 val3 は、戻り値の 0 〜 3 までの名前を指定します。 382 | 383 | それぞれの値は、以下の通りです。 384 | 385 | | 数値 | 意味 | 386 | |------+-------------------------------------| 387 | | 0 | サブルーチン実行後、処理継続 | 388 | | 1 | サブルーチン実行後、return で終了 | 389 | | 2 | サブルーチン実行後、処理を break | 390 | | 3 | サブルーチン実行後、処理を continue | 391 | 392 | 393 | * 制限 394 | 395 | サブルーチン化対象のブロックが次の条件に当て嵌る場合、サブルーチン化できません。 396 | - マクロを利用し、そのマクロ内で return している。 397 | - アドレス渡しする変数を、マクロ内で使用している。 398 | - goto 文を使用している。 399 | 400 | また、マクロ内で 2 項演算子を利用していると、 401 | 左にある変数はアドレスアクセスが必要なものだと判断します。 402 | これは、 lctags の制限というよりは libclang の制限からくるものです。 403 | 404 | 何故ならば、libclang ではマクロ内で 2 項演算子が行なわれている場合に、 405 | その演算子の種別を特定する手段がないためです。 406 | -------------------------------------------------------------------------------- /src/lctags/Json.lua: -------------------------------------------------------------------------------- 1 | local log = require( 'lctags.LogCtrl' ) 2 | 3 | local JsonStream = {} 4 | function JsonStream:new( stream ) 5 | local obj = { 6 | stream = stream, 7 | index = 1 8 | } 9 | setmetatable( obj, { __index = JsonStream } ) 10 | return obj 11 | end 12 | 13 | function JsonStream:fillBuf() 14 | if not self.stream then 15 | return false 16 | end 17 | self.buf = self.stream:read( 1000 * 10 ) 18 | if not self.buf then 19 | self:clear() 20 | return false 21 | end 22 | self.index = 1 23 | return true 24 | end 25 | 26 | function JsonStream:readChar( num ) 27 | local txt = "" 28 | for index = 1, num do 29 | if not self.buf or #self.buf < self.index then 30 | if not self:fillBuf() then 31 | return nil 32 | end 33 | end 34 | self.index = self.index + 1 35 | txt = txt .. self.buf:sub( self.index - 1, self.index - 1 ) 36 | end 37 | return txt 38 | end 39 | 40 | function JsonStream:readToPattern( func, pattern, index ) 41 | local txt = self.buf:sub( index, index ) 42 | index = index + 1 43 | while true do 44 | if not self.buf then 45 | return txt 46 | end 47 | local endIndex = string.find( self.buf, pattern, index ) 48 | if endIndex then 49 | self.index = endIndex + 1 50 | local finishFlag 51 | local token 52 | finishFlag, token, index = func( index, endIndex ) 53 | if token and #token > 0 then 54 | txt = txt .. token 55 | end 56 | if finishFlag then 57 | return txt 58 | end 59 | else 60 | -- バッファ内では文字列確定しない 61 | if #self.buf >= index then 62 | txt = txt .. string.sub( self.buf, index ) 63 | end 64 | self:fillBuf() 65 | index = 1 66 | end 67 | end 68 | end 69 | 70 | function JsonStream:pushToken( token ) 71 | self.pushedToken = token 72 | end 73 | 74 | function JsonStream:clear() 75 | self.stream = nil 76 | end 77 | 78 | function JsonStream:readToken() 79 | if self.pushedToken then 80 | local token = self.pushedToken 81 | self.pushedToken = nil 82 | return token 83 | end 84 | 85 | local token = self:readTokenSub() 86 | return token 87 | end 88 | 89 | function JsonStream:readTokenSub() 90 | if not self.stream then 91 | return nil 92 | end 93 | while true do 94 | if not self.buf or #self.buf < self.index then 95 | if not self:fillBuf() then 96 | return nil 97 | end 98 | end 99 | local index = string.find( self.buf, "[^%s]", self.index ) 100 | if index then 101 | local oneChar = string.sub( self.buf, index, index ) 102 | if string.find( oneChar, "[%[%]%{%},:]" ) then 103 | self.index = index + 1 104 | return oneChar 105 | elseif string.find( oneChar, "t" ) then 106 | self.index = index 107 | if self:readChar( 4 ) == "true" then 108 | return "true" 109 | end 110 | log( 1, "illegal format -- true", self.buf ) 111 | return nil 112 | elseif string.find( oneChar, "f" ) then 113 | self.index = index 114 | if self:readChar( 5 ) == "false" then 115 | return "false" 116 | end 117 | log( 1, "illegal format -- false", self.buf ) 118 | return nil 119 | elseif string.find( oneChar, "n" ) then 120 | self.index = index 121 | if self:readChar( 4 ) == "null" then 122 | return "null" 123 | end 124 | log( 1, "illegal format -- null", self.buf ) 125 | return nil 126 | elseif string.find( oneChar, "[%d%-]" ) then 127 | -- 数値 128 | return self:readToPattern( 129 | function( startIndex, endIndex ) 130 | if not string.find( self.buf, "[,}%]]", endIndex ) then 131 | log( 1, "illegal format -- ", 132 | string.sub( self.buf, startIndex, endIndex ) ) 133 | return true, nil 134 | end 135 | self:pushToken( string.sub( self.buf, endIndex, endIndex ) ) 136 | if startIndex == endIndex then 137 | return true, "" 138 | end 139 | return true, string.sub( self.buf, startIndex, endIndex - 1 ) 140 | end, '[^%d%-%.eE%+]', index ) 141 | elseif oneChar == '"' then 142 | -- 文字列 143 | return self:readToPattern( 144 | function( startIndex, endIndex ) 145 | return self:readString( startIndex, endIndex ) 146 | end, '["\\]', index ) 147 | else 148 | log( 1, "illegal format -- ", oneChar ) 149 | return nil 150 | end 151 | else 152 | self.buf = nil 153 | end 154 | end 155 | end 156 | 157 | function JsonStream:readString( startIndex, endIndex ) 158 | oneChar = string.sub( self.buf, endIndex, endIndex ) 159 | if oneChar == '"' then 160 | -- 文字列確定 161 | return true, string.sub( self.buf, startIndex, endIndex ) 162 | end 163 | -- \ 164 | local txt = "" 165 | if endIndex > startIndex then 166 | txt = string.sub( self.buf, startIndex, endIndex - 1 ) 167 | end 168 | oneChar = string.sub( self.buf, endIndex + 1, endIndex + 1 ) 169 | if oneChar == '\\' then 170 | txt = txt .. '\\' 171 | elseif oneChar == '"' then 172 | txt = txt .. '"' 173 | elseif oneChar == '/' then 174 | txt = txt .. '/' 175 | elseif oneChar == 'b' then 176 | txt = txt .. "\b" 177 | elseif oneChar == 'f' then 178 | txt = txt .. '\f' 179 | elseif oneChar == 'n' then 180 | txt = txt .. '\n' 181 | elseif oneChar == 'r' then 182 | txt = txt .. '\r' 183 | elseif oneChar == 't' then 184 | txt = txt .. '\t' 185 | elseif oneChar == 'u' then 186 | log( 1, "not supprot" ) 187 | os.exit( 1 ) 188 | end 189 | return false, txt, self.index + 1 190 | end 191 | 192 | 193 | local Json = {} 194 | 195 | function Json:fromText( text ) 196 | if not text then 197 | return nil 198 | end 199 | local obj = { 200 | read = function() 201 | local txt = text 202 | text = nil 203 | return txt 204 | end, 205 | } 206 | return Json:fromStream( obj ) 207 | end 208 | 209 | function Json:fromStream( stream ) 210 | local jsonStream = JsonStream:new( stream ) 211 | 212 | local json = { 213 | jsonStream = jsonStream, 214 | keyList = {}, 215 | } 216 | 217 | setmetatable( json, { __index = Json } ) 218 | 219 | return json 220 | end 221 | 222 | function Json:convertFrom( txt ) 223 | local json = Json:fromText( txt ) 224 | if not json then 225 | return nil 226 | end 227 | return json:readValue() 228 | end 229 | 230 | function Json:convertTo( obj ) 231 | local txt = "" 232 | local typeId = type( obj ) 233 | if typeId == "table" then 234 | if obj[ 1 ] then 235 | txt = "[" 236 | for index, val in ipairs( obj ) do 237 | if index ~= 1 then 238 | txt = txt .. ", " 239 | end 240 | txt = txt .. self:convertTo( val ) 241 | end 242 | return txt .. "]" 243 | end 244 | txt = "{" 245 | local index = 0 246 | for key, val in pairs( obj ) do 247 | index = index + 1 248 | if index ~= 1 then 249 | txt = txt .. ", " 250 | end 251 | txt = txt .. string.format( '"%s": %s', 252 | key, self:convertTo( val ) ) 253 | end 254 | return txt .. "}" 255 | end 256 | 257 | if typeId == "string" then 258 | if string.find( obj, '\"', 1, true ) then 259 | ; --print( "find " ) 260 | end 261 | obj = string.gsub( obj, "\n", "\\n" ) 262 | obj = string.gsub( obj, '%"', '"' ) 263 | obj = string.gsub( obj, '\\\\', '\\' ) 264 | return '"' .. obj .. '"' 265 | end 266 | 267 | if typeId == "number" then 268 | return tostring( obj ) 269 | end 270 | 271 | if obj then 272 | return "true" 273 | end 274 | 275 | if obj == false then 276 | return "false" 277 | end 278 | 279 | return "null" 280 | end 281 | 282 | 283 | function Json:readValue( func ) 284 | if not func then 285 | func = function( val ) 286 | return val 287 | end 288 | end 289 | 290 | local token = self.jsonStream:readToken() 291 | if not token then 292 | self.jsonStream:clear() 293 | return nil 294 | end 295 | if token:sub( 1, 1 ) == '"' then 296 | return token:sub( 2, #token - 1 ) 297 | end 298 | if token == "true" then 299 | return true 300 | end 301 | if token == "false" then 302 | return false 303 | end 304 | if token == "null" then 305 | return nil 306 | end 307 | if token == "[" then 308 | local array = {} 309 | local length = 0 310 | while true do 311 | local nextToken = self.jsonStream:readToken() 312 | if nextToken == nil then 313 | return array 314 | end 315 | if nextToken == "]" then 316 | return array 317 | elseif length > 0 then 318 | if nextToken ~= "," then 319 | log( 1, "nothing ',' in array" ) 320 | return nil 321 | end 322 | else 323 | self.jsonStream:pushToken( nextToken ) 324 | end 325 | table.insert( self.keyList, string.format( "[%d]", length ) ) 326 | local value = self:readValue( func ) 327 | table.remove( self.keyList ) 328 | if value then 329 | table.insert( array, value ) 330 | length = length + 1 331 | end 332 | end 333 | end 334 | if token == "{" then 335 | local obj = {} 336 | local length = 0 337 | while true do 338 | local nextToken = self.jsonStream:readToken() 339 | if nextToken == nil then 340 | return nil 341 | end 342 | if nextToken == "}" then 343 | return func( obj, self.keyList, "object" ) 344 | end 345 | if length > 0 then 346 | if nextToken ~= ',' then 347 | log( 1, "nothing ',' in obj" ) 348 | return nil 349 | end 350 | nextToken = self.jsonStream:readToken() 351 | end 352 | if nextToken:sub( 1, 1 ) ~= '"' then 353 | log( 1, "nothing key in obj", nextToken ) 354 | return nil 355 | end 356 | 357 | local key = nextToken:sub( 2, #nextToken - 1 ) 358 | nextToken = self.jsonStream:readToken( nextToken ) 359 | if nextToken ~= ":" then 360 | log( 1, "nothing ':' after", key ) 361 | return nil 362 | end 363 | table.insert( self.keyList, key ) 364 | local value = self:readValue( func ) 365 | table.remove( self.keyList ) 366 | if value ~= nil then 367 | obj[ key ] = value 368 | length = length + 1 369 | end 370 | end 371 | end 372 | return tonumber( token ) 373 | end 374 | 375 | 376 | return Json 377 | -------------------------------------------------------------------------------- /src/test.lua: -------------------------------------------------------------------------------- 1 | -- -*- coding:utf-8 -*- 2 | -- Copyright (C) 2017 ifritJP 3 | 4 | local clang = require( 'libclanglua.if' ) 5 | 6 | local function getFileLocation( cursor ) 7 | local location = cursor:getCursorLocation() 8 | return clang.getFileLocation( 9 | location.__ptr, clang.core.clang_getFileLocation ) 10 | end 11 | 12 | 13 | local useFastFlag = false 14 | local prevFile = nil 15 | local function visitFuncMain( cursor, parent, exInfo ) 16 | local cxfile, line, column, offset = getFileLocation( cursor ) 17 | local cursorKind = cursor:getCursorKind() 18 | local txt = cursor:getCursorSpelling() 19 | print( string.format( 20 | "%s %s %s(%d)", 21 | string.rep( " ", exInfo.depth ), txt, 22 | clang.getCursorKindSpelling( cursorKind ), cursorKind ), 23 | cursor:hashCursor(), 24 | cxfile and cxfile:getFileName(), line, column, os.clock() ) 25 | return 2 26 | 27 | -- local cursorKind = cursor:getCursorKind() 28 | -- local txt = cursor:getCursorSpelling() 29 | 30 | -- print( string.format( 31 | -- "%s %s %s(%d)", 32 | -- string.rep( " ", exInfo.depth ), txt, 33 | -- clang.getCursorKindSpelling( cursorKind ), cursorKind ) ) 34 | 35 | -- if not exInfo.curFunc then 36 | -- if changeFileFlag ~= nil then 37 | -- if changeFileFlag then 38 | -- local cxfile, line = getFileLocation( cursor ) 39 | -- print( "change file 2:", cxfile and cxfile:getFileName() or "", line ) 40 | -- end 41 | -- else 42 | -- local cxfile, line = getFileLocation( cursor ) 43 | -- if (cxfile and prevFile and not prevFile:isEqual( cxfile ) ) or 44 | -- (cxfile ~= prevFile and (not cxfile or not prevFile)) 45 | -- then 46 | -- print( "change file:", cxfile and cxfile:getFileName() or "", line ) 47 | -- prevFile = cxfile 48 | -- end 49 | -- end 50 | -- end 51 | 52 | -- if cursorKind == clang.CXCursorKind.FunctionDecl.val or 53 | -- cursorKind == clang.CXCursorKind.CXXMethod.val 54 | -- then 55 | -- exInfo.curFunc = cursor 56 | -- exInfo.depth = exInfo.depth + 1 57 | -- if useFastFlag then 58 | -- clang.visitChildrenFast( cursor, visitFuncMain, exInfo, nil ) 59 | -- else 60 | -- cursor:visitChildren( visitFuncMain, exInfo ) 61 | -- end 62 | -- exInfo.depth = exInfo.depth - 1 63 | -- exInfo.curFunc = nil 64 | -- elseif cursorKind == clang.CXCursorKind.Namespace.val or 65 | -- cursorKind == clang.CXCursorKind.ClassDecl.val or 66 | -- cursorKind == clang.CXCursorKind.CompoundStmt.val or 67 | -- clang.isStatement( cursorKind ) 68 | -- then 69 | -- exInfo.depth = exInfo.depth + 1 70 | -- if useFastFlag then 71 | -- clang.visitChildrenFast( cursor, visitFuncMain, exInfo, nil ) 72 | -- else 73 | -- cursor:visitChildren( visitFuncMain, exInfo ) 74 | -- end 75 | -- exInfo.depth = exInfo.depth - 1 76 | -- end 77 | -- return 1 78 | end 79 | 80 | 81 | local function visitFuncMainFast( cursor, parent, exInfo, appendInfo ) 82 | local changeFileFlag = appendInfo[ 1 ] 83 | -- local cursorKind = cursor:getCursorKind() 84 | -- local txt = cursor:getCursorSpelling() 85 | -- print( string.format( 86 | -- "%s %s %s(%d)", 87 | -- string.rep( " ", exInfo.depth ), txt, 88 | -- clang.getCursorKindSpelling( cursorKind ), cursorKind ) ) 89 | -- return 2 90 | local cursorKind = cursor:getCursorKind() 91 | local txt = cursor:getCursorSpelling() 92 | 93 | print( string.format( 94 | "%s %s %s(%d)", 95 | string.rep( " ", exInfo.depth ), txt, 96 | clang.getCursorKindSpelling( cursorKind ), cursorKind ) ) 97 | 98 | if not exInfo.curFunc then 99 | if changeFileFlag then 100 | local cxfile, line = getFileLocation( cursor ) 101 | print( "change file 3:", cxfile and cxfile:getFileName() or "", line ) 102 | end 103 | end 104 | 105 | if cursorKind == clang.CXCursorKind.FunctionDecl.val or 106 | cursorKind == clang.CXCursorKind.CXXMethod.val 107 | then 108 | exInfo.curFunc = cursor 109 | exInfo.depth = exInfo.depth + 1 110 | 111 | local result, list = clang.getChildrenList( cursor, nil, 1 ) 112 | for index, info in ipairs( list ) do 113 | visitFuncMainFast( info[ 1 ], info[ 2 ], exInfo, info[ 3 ] ) 114 | end 115 | 116 | exInfo.depth = exInfo.depth - 1 117 | exInfo.curFunc = nil 118 | elseif cursorKind == clang.CXCursorKind.Namespace.val or 119 | cursorKind == clang.CXCursorKind.ClassDecl.val or 120 | cursorKind == clang.CXCursorKind.CompoundStmt.val or 121 | clang.isStatement( cursorKind ) 122 | then 123 | exInfo.depth = exInfo.depth + 1 124 | 125 | local result, list = clang.getChildrenList( cursor, nil, 1 ) 126 | for index, info in ipairs( list ) do 127 | visitFuncMainFast( info[ 1 ], info[ 2 ], exInfo, info[ 3 ] ) 128 | end 129 | 130 | exInfo.depth = exInfo.depth - 1 131 | end 132 | return 1 133 | end 134 | 135 | 136 | local function dumpCursorTU( transUnit ) 137 | print( transUnit ) 138 | 139 | print( transUnit:getTranslationUnitSpelling() ) 140 | 141 | local root = transUnit:getTranslationUnitCursor() 142 | print( root ) 143 | 144 | local cursor = transUnit:getCursor( 145 | transUnit:getLocation( 146 | transUnit:getFile( transUnit:getTranslationUnitSpelling() ), 29, 1 ) ) 147 | print( "pos = ", cursor:getCursorSpelling() ) 148 | print( "parent = ", clang.getCursorKindSpelling( 149 | cursor:getCursorSemanticParent():getCursorKind() ) ) 150 | 151 | 152 | if useFastFlag == 1 then 153 | clang.visitChildrenFast( root, visitFuncMain, { depth = 0 }, nil, 2 ) 154 | elseif useFastFlag == 2 then 155 | clang.visitChildrenFast2( 156 | root, visitFuncMain, { depth = 0 }, 157 | { clang.core.CXCursor_MacroDefinition }, 158 | { clang.core.CXCursor_FieldDecl }, 159 | { transUnit:getFile( transUnit:getTranslationUnitSpelling() ) }, 2 ) 160 | else 161 | root:visitChildren( visitFuncMain, { depth = 0 } ) 162 | end 163 | end 164 | 165 | local function dumpCursor( clangIndex, path, options, unsavedFileTable ) 166 | local args = clang.mkcharPArray( options ) 167 | local unsavedFileArray = clang.mkCXUnsavedFileArray( unsavedFileTable ) 168 | print( "createTranslationUnitFromSourceFile start", os.clock() ) 169 | 170 | local unitArray = clang.mkCXTranslationUnitArray( nil, 1 ) 171 | local code = clangIndex:parseTranslationUnit2( 172 | path, args:getPtr(), args:getLength(), 173 | unsavedFileArray:getPtr(), unsavedFileArray:getLength(), 174 | clang.core.CXTranslationUnit_ForSerialization + 175 | clang.core.CXTranslationUnit_DetailedPreprocessingRecord, 176 | unitArray:getPtr() ) 177 | 178 | if code ~= clang.core.CXError_Success then 179 | print( "failed to parseTranslationUnit2", code, clang.core.CXError_Success ) 180 | os.exit( 1 ) 181 | end 182 | 183 | local transUnit = clang.CXTranslationUnit:new( unitArray:getItem( 0 ) ) 184 | 185 | print( "createTranslationUnitFromSourceFile end", os.clock() ) 186 | 187 | dumpCursorTU( transUnit ) 188 | end 189 | 190 | -- for key, val in pairs( clang.core ) do 191 | -- print( key, val ) 192 | -- end 193 | 194 | local clangIndex = clang.createIndex( 1, 1 ) 195 | 196 | -- if arg[ 1 ] then 197 | -- local optList = { "-M" } 198 | -- for index, opt in ipairs( arg ) do 199 | -- table.insert( optList, opt ) 200 | -- end 201 | -- dumpCursor( clangIndex, "test/hoge.cpp", optList, nil ) 202 | 203 | -- os.exit( 1 ) 204 | -- end 205 | 206 | -- print( string.format( "%s %s %s %s", arg[-1], arg[0], "-Itest", 207 | -- "-I/usr/lib/llvm-3.8/lib/clang/3.8.0/include" ) ) 208 | 209 | -- local pipe = io.popen( 210 | -- string.format( "%s %s %s %s", arg[-1], arg[0], "-Itest", 211 | -- "-I/usr/lib/llvm-3.8/lib/clang/3.8.0/include" ) ) 212 | -- while true do 213 | -- local txt = pipe:read( '*l' ) 214 | -- if not txt then 215 | -- break 216 | -- end 217 | -- for path in string.gmatch( txt, "[^%s\\]+" ) do 218 | -- if not string.find( path, ":$" ) then 219 | -- print( "file", path ) 220 | -- end 221 | -- end 222 | -- end 223 | 224 | useFastFlag = 1 225 | -- print( "start", os.clock() ) 226 | -- dumpCursor( clangIndex, "../external/luasqlite3/lsqlite3_fsl09x/sqlite3.c", { "-Itest" } ) 227 | -- print( "end", os.clock() ) 228 | 229 | print( "start", os.clock() ) 230 | -- dumpCursor( clangIndex, "test/inc1.cpp", { "-Itest", "-std=c++11", "-I/usr/lib/llvm-3.8/lib/clang/3.8.0/include", "-include-pch", "field.pch" } ) 231 | -- dumpCursor( clangIndex, "test/inc1.cpp", { "-Itest", "-std=c++11", "-I/usr/lib/llvm-3.8/lib/clang/3.8.0/include", "-include-pch", "inc1.pch" } ) 232 | -- dumpCursor( clangIndex, "test/inc1.cpp", 233 | -- { "-Itest", "-std=c++1z", "-I/usr/lib/llvm-3.8/lib/clang/3.8.0/include", 234 | -- --"-include-pch", "inc1.h.pch", "-include-pch", "inc2.h.pch" } ) 235 | -- --"-include-pch", "inc2.h.pch" } ) 236 | -- "-include-pch", ".lctags/pch/@/proj/test/inc1.h.pch" } ) 237 | -- --}) 238 | print( "end", os.clock() ) 239 | 240 | 241 | useFastFlag = 2 242 | print( "start", os.clock() ) 243 | dumpCursor( clangIndex, "test/class.cpp", 244 | { "-Itest", "-std=c++11", "-I/usr/lib/llvm-3.8/lib/clang/3.8.0/include", 245 | "-include-pch", ".lctags/pch/@/proj/test/class.h.pch.c++11" 246 | } ) 247 | print( "end", os.clock() ) 248 | 249 | -- useFastFlag = 1 250 | -- print( "start", os.clock() ) 251 | -- dumpCursor( clangIndex, "test/hoge.cpp", 252 | -- { "-Itest", "-I/usr/lib/llvm-3.8/lib/clang/3.8.0/include" } ) 253 | -- print( "end", os.clock() ) 254 | 255 | -- -- print( "start", os.clock() ) 256 | -- -- dumpCursor( clangIndex, "swig/libClangLua_wrap.c", 257 | -- -- { "-I/usr/lib/llvm-3.8/include", "-I/usr/include/lua5.3", 258 | -- -- "-I/usr/lib/llvm-3.8/lib/clang/3.8.0/include" } ) 259 | -- -- print( "end", os.clock() ) 260 | 261 | -- local unsavedFile = clang.core.CXUnsavedFile() 262 | -- unsavedFile.Filename = "test/hoge.cpp" 263 | -- unsavedFile.Contents = [[ 264 | -- int func() { 265 | -- return 1; 266 | -- } 267 | -- ]] 268 | -- unsavedFile.Length = #unsavedFile.Contents 269 | 270 | -- print( "start", os.clock() ) 271 | -- dumpCursor( clangIndex, "test/hoge.cpp", { "-Itest" }, { unsavedFile } ) 272 | -- print( "end", os.clock() ) 273 | 274 | -------------------------------------------------------------------------------- /src/lisp/lctags-servlet.el: -------------------------------------------------------------------------------- 1 | (require 'lctags) 2 | (require 'simple-httpd) 3 | (require 'lctags-const) 4 | 5 | ;; (httpd-start) 6 | 7 | ;; http://xxx.xxx.xxx.xxx:8080/lctags/start?db=path&target=target&conf=conf 8 | ;; http://xxx.xxx.xxx.xxx:8080/lctags/inq 9 | ;; ((file-name-directory (find-library-name "lctags")) 10 | ;; (locate-library "lctags") 11 | 12 | (defvar lctags-servlet-content-dir nil) 13 | 14 | ;; lctags.el の html 以下を /lctags/contents にマッピング 15 | (let ((dir (file-name-directory (locate-library "lctags"))) 16 | content) 17 | (setq lctags-servlet-content-dir (expand-file-name "html" dir)) 18 | (httpd-def-file-servlet lctags/contents lctags-servlet-content-dir)) 19 | 20 | (defservlet lctags text/json (path query req) 21 | "When servlet is accessed for '/lctags', servlet returns index.html. " 22 | (httpd-serve-root t lctags-servlet-content-dir "index.html")) 23 | 24 | (defservlet lctags/start text/json (path query req) 25 | (lctags-servlet-start path query req) 26 | ) 27 | 28 | (defservlet lctags/inq text/json (path query req) 29 | (lctags-servlet-handle path query req) 30 | ) 31 | 32 | (defservlet lctags/get text/json (path query req) 33 | (lctags-servlet-get-handle path query req) 34 | ) 35 | 36 | (defservlet lctags/gen text/json (path query req) 37 | (lctags-servlet-gen path query req) 38 | ) 39 | 40 | 41 | ;; (clrhash lctags-servlet-cookie-hash) 42 | ;; (clrhash lctags-servlet-cookie-hash-key) 43 | (defvar lctags-servlet-cookie-hash (make-hash-table) 44 | "cookie -> val map") 45 | (defvar lctags-servlet-cookie-hash-key (make-hash-table :test 'equal) 46 | "key -> val map") 47 | (defvar lctags-servlet-last-cookie nil) 48 | 49 | 50 | ;;(gethash 0 lctags-servlet-cookie-hash) 51 | 52 | (defun lctags-servlet-set-cookie-of-symbol-at () 53 | (interactive) 54 | (let ((src-buf (current-buffer)) 55 | tmp-buf result symbol) 56 | (with-temp-buffer 57 | (setq tmp-buf (current-buffer)) 58 | (with-current-buffer src-buf 59 | (lctags-execute-op (current-buffer) tmp-buf (buffer-string) nil 60 | (list "cursor-at" (buffer-file-name) 61 | (number-to-string (lctags-get-line)) 62 | (number-to-string (lctags-get-column)) "-i"))) 63 | (setq result (lctags-xml-get (current-buffer) 'cursor))) 64 | (setq symbol (list (lctags-xml-get-val result 'fullName) 65 | (string-to-number (lctags-xml-get-val result 'nsId)))) 66 | (when symbol 67 | (lctags-servlet-set-cookie symbol)) 68 | )) 69 | 70 | (defun lctags-servlet-set-cookie (&optional targetSymbol) 71 | (interactive) 72 | (let ((db-info (lctags-get-db (current-buffer)))) 73 | (setq lctags-servlet-last-cookie 74 | (lctags-servlet-make-cookie (plist-get db-info :db) 75 | (plist-get db-info :target) 76 | (plist-get db-info :conf) 77 | targetSymbol)))) 78 | 79 | (defun lctags-servlet-hash-key (db-path target conf-path) 80 | (format "%s$%s$%s" db-path target conf-path)) 81 | 82 | (defun lctags-servlet-make-cookie (db-path target conf-path &optional targetSymbol) 83 | (when (not db-path) 84 | (let ((src-buf (current-buffer))) 85 | (with-temp-buffer 86 | (lctags-execute-op2 src-buf (current-buffer) nil nil "dump" "dbPath") 87 | (beginning-of-buffer) 88 | (when (re-search-forward "^\\(.+\\)$") 89 | (setq db-path (match-string-no-properties 1))) 90 | ))) 91 | (let ((key (lctags-servlet-hash-key db-path target conf-path)) 92 | cookie val) 93 | (setq val (gethash key lctags-servlet-cookie-hash-key)) 94 | (if val 95 | (progn 96 | (plist-put val :targetSymbol targetSymbol) 97 | (setq cookie (plist-get val :cookie))) 98 | (setq cookie (hash-table-count lctags-servlet-cookie-hash)) 99 | (setq val (list :db db-path :table target :conf conf-path :cookie cookie 100 | :targetSymbol targetSymbol)) 101 | (puthash cookie val lctags-servlet-cookie-hash) 102 | (puthash key val lctags-servlet-cookie-hash-key) 103 | 104 | ;; lctags の準備 105 | (with-temp-buffer 106 | (let ((lctags-db db-path) 107 | (lctags-target target) 108 | (lctags-conf conf-path)) 109 | (lctags-execute-op2 (current-buffer) (current-buffer) nil nil 110 | "inq" "projDir" "--lctags-form" "json") 111 | (plist-put val :projDir 112 | (plist-get (car (lctags-json-get (current-buffer) :projDir)) :path)) 113 | (lctags-execute-op2 (current-buffer) (current-buffer) nil nil "prepare")))) 114 | cookie)) 115 | 116 | (defun lctags-servlet-start (path query req) 117 | (let ((db (cadr (assoc "db" query))) 118 | (target (cadr (assoc "target" query))) 119 | (conf (cadr (assoc "conf" query))) 120 | (cookie (cadr (assoc "cookie" query))) 121 | (jumpCallGraph (cadr (assoc "jumpCallGraph" query))) 122 | (jumpLastSymbol (cadr (assoc "jumpLastSymbol" query))) 123 | location conf-info symbol) 124 | (when cookie 125 | (setq cookie (string-to-number cookie))) 126 | (when jumpLastSymbol 127 | (setq jumpCallGraph t) 128 | (setq cookie lctags-servlet-last-cookie)) 129 | (if (not cookie) 130 | (setq cookie (lctags-servlet-make-cookie db target conf)) 131 | (setq conf-info (gethash cookie lctags-servlet-cookie-hash)) 132 | (setq symbol (plist-get conf-info :targetSymbol))) 133 | (if (and jumpCallGraph symbol) 134 | (setq location (format "/lctags/gen/func-call-graph.html?confId=%s&nsId=%d&name=%s" 135 | cookie (cadr symbol) (car symbol))) 136 | (setq location (format "/lctags/gen/file-list.html?confId=%s" cookie))) 137 | (httpd-send-header t "text/plain" 302 138 | ;;:Set-Cookie (format "confId=%s; path=/lctags;" cookie) 139 | :Location location) 140 | )) 141 | 142 | (defun lctags-servlet-display-at () 143 | (interactive) 144 | (lctags-servlet-set-cookie) 145 | 146 | 147 | ) 148 | 149 | (defun lctags-servlet-open-pos ( sym &rest arg ) 150 | (apply 'lctags-execute-op2 (current-buffer) (current-buffer) nil nil 151 | "--lctags-form json" arg) 152 | (let* ((result (lctags-json-get (current-buffer) 153 | (intern (format ":%s" sym)))) 154 | def-info) 155 | (dolist (def result) 156 | (let ((info (lctags-json-val def :info))) 157 | (when (or (not def-info) 158 | (eq (lctags-json-val info :hasBodyFlag) t)) 159 | (setq def-info info)))) 160 | (httpd-send-header t "text/json" 200) 161 | (let* ((info (lctags-json-val def-info :info)) 162 | (path (lctags-json-val def-info :path)) 163 | (line (lctags-json-val def-info :line)) 164 | (column (lctags-json-val def-info :column))) 165 | (when path 166 | (find-file path) 167 | (lctags-goto-line-column line column) 168 | (recenter) 169 | )) 170 | )) 171 | 172 | (defun lctags-servlet-conv-param (val query) 173 | (if (string-match "^\\?" val) 174 | (cadr (assoc (substring val 1) query)) 175 | val)) 176 | 177 | (defun lctags-servlet-handle (path query req) 178 | (let* ((cookie (string-to-number 179 | (or (cadr (assoc "confId" query)) "-1"))) 180 | (conf-info (gethash cookie lctags-servlet-cookie-hash)) 181 | (command (cadr (assoc "command" query))) 182 | result) 183 | (if (not conf-info) 184 | (httpd-error t 404 "not found cookie") 185 | (with-temp-buffer 186 | (let* ((lctags-db (plist-get conf-info :db)) 187 | (lctags-target (plist-get conf-info :target)) 188 | (lctags-conf (plist-get conf-info :conf)) 189 | (command-info (cadr (assoc command lctags-servlet-api-info-table))) 190 | (command-func (plist-get command-info :func)) 191 | param-list) 192 | (if (not command-info) 193 | (httpd-error t 500 (format "not found command -- %s" command)) 194 | (setq param-list 195 | (mapcar (lambda (val) 196 | (cond ((stringp val) 197 | (lctags-servlet-conv-param val query)) 198 | ((listp val) 199 | (let ((param (lctags-servlet-conv-param 200 | (car val) query)) 201 | (func (cadr val))) 202 | (funcall func param))) 203 | ((functionp val) 204 | (httpd-error t 500 (format "illegal param -- %s" val))) 205 | (t 206 | val))) 207 | (plist-get command-info :param))) 208 | (if command-func 209 | (apply command-func param-list) 210 | (apply 'lctags-execute-op2 211 | (current-buffer) (current-buffer) nil nil 212 | "inq" (if (plist-get command-func :command) 213 | (plist-get command-func :command) 214 | command) 215 | param-list))) 216 | (setq result (buffer-string)))) 217 | (insert result) 218 | (httpd-send-header t "text/json" 200) 219 | ))) 220 | 221 | 222 | 223 | (defun lctags-servlet-get-handle (path query req) 224 | (let* ((command (cadr (assoc "command" query))) 225 | result) 226 | (with-temp-buffer 227 | (cond ((equal command "cookies") 228 | (let* (obj list) 229 | (maphash (lambda (cookie val) 230 | (setq obj (json-new-object)) 231 | (setq obj (json-add-to-object obj "db" 232 | (plist-get val :db))) 233 | (setq obj (json-add-to-object obj "target" 234 | (plist-get val :target))) 235 | (setq obj (json-add-to-object obj "conf" 236 | (plist-get val :conf))) 237 | (setq obj (json-add-to-object obj "targetSymbol" 238 | (car (plist-get val :targetSymbol)))) 239 | (setq obj (json-add-to-object obj "targetNsId" 240 | (cadr (plist-get val :targetSymbol)))) 241 | (setq obj (json-add-to-object obj "cookie" cookie)) 242 | (setq list (cons obj list)) 243 | ) 244 | lctags-servlet-cookie-hash) 245 | 246 | (insert (json-encode (json-add-to-object (json-new-object) 247 | "list" (vconcat list)))) 248 | )) 249 | (t 250 | (httpd-error t 500 (format "not found command -- %s" command)) 251 | )) 252 | (setq result (buffer-string))) 253 | (insert result) 254 | (httpd-send-header t "text/json" 200) 255 | )) 256 | 257 | 258 | (defun lctags-servlet-gen (path query req) 259 | (let* ((command (cadr (assoc "command" query))) 260 | (content-path (expand-file-name (substring path (length "/lctags/gen/")) 261 | lctags-servlet-content-dir)) 262 | (cookie (string-to-number 263 | (or (cadr (assoc "confId" query)) "-1"))) 264 | (conf-info (gethash cookie lctags-servlet-cookie-hash)) 265 | (proj-dir (plist-get conf-info :projDir)) 266 | result) 267 | (if (not conf-info) 268 | (httpd-error t 404 "not found cookie") 269 | (with-temp-buffer 270 | (set-buffer-multibyte nil) 271 | (insert-file-contents content-path) 272 | (dolist (rep-info (cons (list 'projDir proj-dir) 273 | (copy-sequence query))) 274 | (beginning-of-buffer) 275 | (replace-string (format "$%s$" (car rep-info)) 276 | (cadr rep-info) nil (point-min) (point-max))) 277 | (setq result (buffer-string))) 278 | (insert result) 279 | (httpd-send-header t (httpd-get-mime (file-name-extension content-path)) 280 | 200 281 | :Last-Modified 282 | (httpd-date-string (nth 4 (file-attributes content-path)))))) 283 | ) 284 | 285 | (provide 'lctags-servlet) 286 | --------------------------------------------------------------------------------