www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - a "go to definition" tool like visual studio for D

Since D havenot a good IDE, How tedious to find the definition of windows api?
How to quickly determine which windows header must import? 
So I write this. 

/*  a 'go to definition' tool fo D programming language
	Placed into public domain.
	1 auto open import module in scite
	2 open and send D keywords to dhelp.chm (thanks Vladimir Panteleev,
	3 go to definition of identifier, like visual studio, and open the file in
scite, go to the line of definition 

Author: yidabu, D china, http://bbs.yidabu.com/forum-10-1.html
Date:	20070706
DMD version:	1.018
Dhelp version:	0.1
Thread: http://bbs.yidabu.com/thread-631-1.html  (Chinese)
	1 download ini.d from http://www.dprogramming.com/ini.php (thanks Chris Miller)
	2 download win32 header from http://www.prowiki.org/wiki4d/wiki.cgi?WindowsAPI
	3 download dfl from http://www.dprogramming.com/dfl.php (thanks Chris Miller)
	4 compile instruction:	dmd dhelp.d shellapi.d -O -release	

	step1 first run dhelp.exe
	will create dhelp.ini and dkeys.txt, edit the config to fit you need.
		searchdir=directory to search definition
		searchexclude=regular expression pattern of excluded search path
		scite=fullpath of scite.exe
		pattern=regular expression pattern of search definition, except for
		dchm=fullpath of dchm.chm
		dkeys.txt = D and phobos keywords
	step2 add to d.properties of scite
		command.3.*.d=yourpath\dhelp.exe $(FilePath) $(CurrentWord)
		command.name.3.*.d=go to definition
	step3 add to SciTEUser.properties of scite
	args[1] = fullpath of current D sourcr file
	args[2] = selected keyword
exe version:	http://down.yidabu.com/d/dhelp.exe
todo:	how to keep dhelp.chm window ?	

import ydb.ini;	
import std.file;		
import std.regexp;
import std.string;
import std.stream : File;
import std.utf : toUTF16z;
import win32.shellapi : ShellExecuteW;		
import win32.winuser;		//for SW_SHOWMAXIMIZED
import win32.winbase;		//for FreeLibrary,LoadLibraryW
import win32.winnt;		//HANDLE
import std.path;
import std.c.stdio;		//for getch();
import dfl.application;	//for startupPath

import std.typetuple;		//dllimport
import std.traits;		//dllimport

//for htmlhelp, from htmlhelp.h
const uint HH_DISPLAY_TOPIC  =      0x0000;
const uint HH_HELP_FINDER    =      0x0000;  // WinHelp equivalent
const uint HH_DISPLAY_TOC    =      0x0001;  
const uint HH_DISPLAY_INDEX  =      0x0002;  
const uint HH_DISPLAY_SEARCH =      0x0003;  
const uint HH_SET_WIN_TYPE   =      0x0004;
const uint HH_GET_WIN_TYPE   =      0x0005;
const uint HH_GET_WIN_HANDLE =      0x0006;
const uint HH_ENUM_INFO_TYPE =      0x0007;  // Get Info type name, call
repeatedly to enumerate, -1 at end
const uint HH_SET_INFO_TYPE  =      0x0008;  // Add Info type to filter.
const uint HH_SYNC           =      0x0009;
const uint HH_DISPLAY_TEXT_POPUP  = 0x000E;  // display string resource id or
text in a popup window
const uint HH_HELP_CONTEXT        = 0x000F;  // display mapped numeric value in
const uint HH_TP_HELP_CONTEXTMENU = 0x0010;  // text popup help, same as
const uint HH_TP_HELP_WM_HELP     = 0x0011;  // text popup help, same as

pragma(lib,"ydb.lib");		//for ydb.ini, you can comment this and add ini.d to
compile instruction
pragma(lib,"dfl_debug.lib");	//for dfl.application

int main(string[] args)
	int result;
		assert(args.length > 2, "args length < 3, now exit");
		Dhelp sf = new Dhelp(args[1], args[2]);
		result = sf.dhelp();
		getch();	//for keep dhelp.chm window only, need a better solution
	catch(Exception e)
		printf("catch %.*s\n",e.msg);
		result = 1;
	return result;

class Dhelp
	// from dhelp.ini
	string searchdir;		//directory to search
	string searchexclude;	//regular expression of excluded path
	string scite;			//fullpath of scite.exe
	string dkeys;		//
	//regular expression of search pattern	
	string pattern = r"^\s*([\w\[\]*]+\s+)+([\w\[\]*]+\s*,\s*)*(searchword)\s*[:,{;=(]";		
	string dchm;			//full path of D help chm
	string exedir;
	string inipath;
	string dkeyspath;		//fullpath of dkeys.txt
	Ini ini;
	string filename;		//current d source file full path;
	string searchword;		//identifier to search
	string postfixword;	//postfix of searchword
	this(string filename, string searchword)
		dkeys = r" abstract alias align asm assert auto body bool break byte case
cast catch cdouble cent cfloat char class const continue creal dchar debug
default delegate delete deprecated do double else enum export extern false
final finally float for foreach  function goto idouble if ifloat import in
inout int interface invariant ireal is lazy long macro mixin module new null
out override package pragma private protected public real ref return scope
short static struct super switch synchronized template this throw true try
typedef typeid typeof ubyte ucent uint ulong union unittest ushort version void
volatile wchar while with string wstring dstring

loader   path  demangle 
 invariant time      utf 
 etc switch  format  syserror dmain2 arraycat 
 com bind  testgc  outofmemory typetuple zip 
 ctype math2 random regexp  signals  fenv 
 object  asserterror traits memset linuxextern typeinfo trace 
 boxer date string intrinsic locale stdlib  stdio 
  stdint windows  moduleinit adi unittest socket 
 deh2  switcherr internal  phobos   
 uni   system gclinux arraycast   
 qsort cpuid bitarray compiler conv std charset pthread 
 aaA obj openrj gcstats zlib cast iunknown gc 
  llmath array stdarg   registry dateparse 
 gcstub  outbuffer perf cover gcold linux  
 gcbits   md5  winsock file stat 
 math socketstream cmath2  win32 thread crc32 gcx 
 alloca gamma uri aApplyR base64 stream process mmfile 
 qsort2 stddef cstream metastrings

opCmp opEquals factory find getHash equals compare swap 
 encodeLength decodeLength Tuple append 
 prepend BoundFunc opIndex init opAnd 
 opOr opXor opSub opAndAssign opOrAssign opXorAssign opSubAssign opCatAssign 
 unboxable boxArray boxArrayToArguments toInt setSourceDir setDestDir setMerge
isalnum isalpha iscntrl 
 isdigit islower ispunct isspace isupper isxdigit isgraph isprint 
 isascii tolower toupper fetestexcept feraiseexcept feclearexcept fesetround
 fegetenv fesetenv fegetexceptflag fesetexceptflag feholdexcept feupdateenv
setlocale acos 
 asin atan atan2 acosh asinh 
 atanh exp exp2 expm1 frexp 
 ilogb ldexp log10 log1p log2 modf 
 scalbn scalbln cbrt fabs hypot pow sqrt erf 
 erfc lgamma tgamma ceil floor nearbyint lrint 
 llrint lround llround trunc fmod remainder remquo 
 copysign nan nextafter nexttoward fdim fmax fmin isgreater isgreaterequal
isless islessequal islessgreater isunordered tmpnam fopen 
 _fsopen freopen fseek ftell fgets fgetc fflush fclose 
 fputs fputc _fputchar ungetc fread fwrite 
 fprintf vfprintf vprintf sprintf vsprintf scanf fscanf sscanf 
 setbuf setvbuf remove rename perror fgetpos fsetpos getw 
 putw ferror feof clearerr rewind 
 fileno unlink fdopen filesize tempnam _wtmpnam fgetws 
 fputws wprintf fwprintf vwprintf vfwprintf swprintf vswprintf wscanf 
 fwscanf swscanf fgetwc fputwc ungetwc  fwide div alloca calloc unsetenv atof 
 itoa mblen memcpy memmove strcpy strncpy strncat strcoll 
 strncmp strxfrm memchr strchr strcspn strpbrk strrchr strspn 
 strstr strtok memset strerror strlen strcmp strcat memcmp 
 memicmp parse toISO8601YearWeek YearFromTime inLeapYear MonthFromTime
DateFromTime WeekDay 
 UTCtoLocalTime LocalTimetoUTC toString toUTCString toDateString toTimeString
 toDosFileTime demangle getSize 
 getTimes exists getAttributes isfile isdir chdir mkdir rmdir 
 listdir toMBSz addRoot removeRoot addRange removeRange 
 hasPointers hasNoPointers setTypeInfo malloc realloc extend capacity
 bsf bsr bt btc btr bts bswap inp 
 outp conj rndtol rndtonl isnan isfinite isnormal issubnormal isinf signbit
 poly sum printDigest digestToString finish opSlice opIndexAssign getField
 hasField getRecordsContainingField reserve fill0 alignSize 
 spread getExt getName getBaseName getDirName getDrive 
 defaultExt addExt isabs join fncharmatch fnmatch expandTilde system 
 execv  rfind split search opCall 
 replace replaceOld emit connect disconnect getProtocolByName getProtocolByType 
 getServiceByName getHostByName getHostByAddr isSet 
 bind listen shutdown send sendTo receive receiveFrom 
 getOption setOption select readBlock writeBlock 
 writef writefln fwritef fwritefln readln readExact 
 readString readStringW vreadf writeExact writeLine writeLineW writeString
writeStringW copyFrom seekSet position source 
 create readBOM fixBO fixBlockBO writeBOM iswhite atoi 
 toStringz capitalize 
 capwords repeat splitlines stripl chomp 
 chop ljustify zfill replaceSlice insert count expandtabs 
 entab maketrans translate format sformat 
 inPattern countchars removechars squeeze succ isNumeric 
 soundex abbrev column wrap isEmail isURL 
 wait setPriority isUniLower isUniUpper toUniLower toUniUpper isUniAlpha
decodeComponent encodeComponent isValidDchar stride toUCSindex 
 toUTFindex validate toUTF8 toUTF16 toUTF32 fromMBSz 
 addMember deleteMember adler32 crc32 compress uncompress ";
		exedir = Application.startupPath();		//dfl.application.	
		inipath = exedir ~ r"\dhelp.ini";
		dkeyspath = exedir ~ r"\dkeys.txt";
		if (std.file.exists(dkeyspath)) 
			dkeys = cast(string) std.file.read(dkeyspath);
			std.file.write(dkeyspath, dkeys);
		string t;		//temp 
		ini = new Ini(inipath);
		if (ini["config"] is null) ini.addSection("config");
		if ( ini["config"]["searchdir"] is null ) ini["config"]["searchdir"] = "";
		if ( ini["config"]["searchexclude"] is null ) ini["config"]["searchexclude"]
= "";
		if ( ini["config"]["scite"] is null )  ini["config"]["scite"] = "";
		t = ini["config"]["pattern"];
		if (t is null || t.length < 10 ) ini["config"]["pattern"] = pattern;
		if ( ini["config"]["dchm"] is null ) ini["config"]["dchm"] = "";
		searchdir = ini["config"]["searchdir"];
		assert( searchdir && searchdir.length > 2 && std.file.exists(searchdir),
"searchdir not exists" );
		t = ini["config"]["searchexclude"];
		if ( t && t.length > 2 ) searchexclude = t;
		scite = ini["config"]["scite"];
		assert(scite && scite.length > 2 && std.file.exists(scite), "scite not
		t = ini["config"]["pattern"];
		if ( t && t.length > 10 ) pattern = t;
		t = ini["config"]["dchm"];
		if (t && t.length > 5 && std.file.exists(t)) dchm = t;
		assert(filename && std.file.exists(filename), "not exists" ~ filename);
		assert(searchword && searchword.length > 2, "searchword length < 3");
		if (std.string.find(searchword,".") == 0) searchword = searchword[1..$];
//.word replace to word		
		this.filename = filename;
		this.searchword = searchword;

		auto m = std.regexp.search(searchword, r"\w+$");
		assert(m,"wrong searchword " ~ searchword);
		postfixword = m.match(0);			
		printf("filename %.*s \n",filename);
		printf("searchword %.*s \n",searchword);		
		//~ printf("postfix of searchword %.*s \n",postfixword);		
		delete ini;
	int dhelp()
		int result;
		string src;			//source of file
		string searchpath;		//path of import module
		string pattern2;		//regular expression pattern of searchword
			//if a import module
			if ( std.string.find(searchword,".") > 0 )
				src = cast(string) std.file.read(filename);
				string p = r"^\s*import.*?[\W]" ~ std.string.replace(searchword,".",r"\.")
~ r"\s*[,:;]";
				if (std.regexp.search(src, p, "mig"))
					searchpath = std.string.replace(searchword, ".",r"\");
			//if dhelp.chm exists, check if searchword is a d keyword
			if (dchm && !searchpath)
				DllImport!("hhctrl.ocx", "HtmlHelpW",
					void* function(void* hwndCaller, wchar* pszFile, uint uCommand, uint
dwData)) HtmlHelp;						
				//if a d keywords, 
				if (std.regexp.search(dkeys, r"\s" ~ postfixword ~ r"\s"))
				{	//open dhelp.chm
					//how to keep the window of dhelp.chm?
					HtmlHelp(cast(void*)0,toUTF16z(dchm), HH_DISPLAY_INDEX ,
					debug printf("a D keyword %.*s \n", postfixword);		
					return 0;
			bool callback(DirEntry* de)
				if (de.isdir)
					if ( searchexclude && std.regexp.search(de.name, searchexclude,"i") ) 
						return true;
					std.file.listdir(de.name, &callback);
					if ( std.string.icmp(getExt(de.name),"d") ) 
						return true;		// .d file only
					if ( searchpath )	//if searchword is a import module 
						if (std.string.ifind(de.name, searchpath ~ ".d") != -1 )//open import file
							string dirname = std.path.getDirName(de.name);
							string basename = std.path.getBaseName(de.name);
							ShellExecuteW(null, toUTF16z("open"), toUTF16z(scite), toUTF16z("
-open:" ~ basename), toUTF16z(dirname), SW_SHOWMAXIMIZED);
							return false;
							return true;//continue search
					pattern2 = std.string.replace( pattern,"searchword",postfixword);
					int i;		//line index
					std.stream.File src = new std.stream.File(de.name);
						if (auto m = std.regexp.search(src.readLine(), pattern2))
							string dirname = std.path.getDirName(de.name);
							string basename = std.path.getBaseName(de.name);					
							ShellExecuteW(null, toUTF16z("open"), toUTF16z(scite), toUTF16z("-open:"
~ basename ~ " -goto:" ~ std.string.toString(i)), toUTF16z(dirname),
							printf("%.*s %.*s %d \n", de.name, m.match(0), i);					

			  return true;

			std.file.listdir(searchdir, &callback);
		catch(Exception e)
			printf("dhelp catch %.*s\n", e.msg);
			result = 1;
			printf("end \n");			
		return result;		
		printf("dhelp unittest begin \n");
		Dhelp sf = new Dhelp();
		string pattern;
		void test(string s, string searchword, bool direction = true)
			if (std.string.find(searchword,".") >= 0) return;	//not test module here
			string pattern = std.string.replace(sf.pattern, "searchword", searchword);
			//~ debug printf("pattern %.*s \n",pattern);
			auto m = std.regexp.search(s,pattern);
			if (!direction)
				assert(!m, searchword);
				printf("ok, not matched %.*s \n", searchword);
				assert(m && (m.match(3) == searchword), searchword);
				printf("ok, matched %.*s \n", m.match(1));

		test("DWORD numread;", "numread");
		test(" const FVIRTKEY  = 1;", "FVIRTKEY");
		test(" const CREATEPROCESS_MANIFEST_RESOURCE_ID                 =
		test("HWND CreateDialogA(HINSTANCE h, LPCSTR n, HWND w, DLGPROC f)",
		test("interface IEnumFORMATETC : public IUnknown {", "IEnumFORMATETC");
		test("struct BYTE_SIZEDARR {", "BYTE_SIZEDARR");
		test("struct GUID {     ", "GUID");
		test("result.lenght = MultiByteToWideChar(", "MultiByteToWideChar", false);
		test("	ERROR_DS_DUP_SCHEMA_ID_GUID,", "GUID", false);
		test(" char[] getGuid (),", "Guid", false);
		test("struct BYTE_SIZEDARR {", "BYTE", false);
		test("interface IEnumFORMATETC : public IUnknown {", "IUnknown",false);
		test("extern IID IID_IGlobalInterfaceTable;", "IID", false);
		test("int DlgDirListA(HWND, LPSTR, int, int, UINT);", "UINT", false);
		test("HWND CreateDialogA(HINSTANCE h, LPCSTR n, HWND w, DLGPROC f)",
"HINSTANCE", false);
		printf("dhelp unittest end \n\n");

//dynamic load dll
//by oldrev
private static class ModuleManager
	private static HANDLE[string]	m_modules;

	private this()

	static public ~this()
		foreach(h; m_modules)

	private static HANDLE registerModule(string name)
		string lname = tolower(name);
		HANDLE h = LoadLibraryW(toUTF16z(lname));
		if(h is null)
			throw new Exception("Failed to load DLL: " ~ name);
		m_modules[lname] = h;
		return h;

	public static HANDLE getHandle(string name)
		return m_modules[name];

	public static ProcType getSymbol(ProcType)(string moduleName, string procName)
		HANDLE handle = null;	//winnt
		if(moduleName in m_modules)
			handle = m_modules[moduleName];
			handle = registerModule(moduleName);

		assert(handle !is null);
		ProcType proc = cast(ProcType)GetProcAddress(handle,
		if(proc is null)
			throw new Exception("Cannot to get the address of " ~ procName);
		return proc;

struct DllImport(string ModuleName, string ProcName, FT)
	extern(Windows) alias ReturnType!(FT)
		function(ParameterTypeTuple!(FT))	FunctionType;
	alias DllImport!(ModuleName, ProcName, FT) SelfType;
	//FIXME: avoid the CTFE?
	private FunctionType m_funcPtr = null;
	public ReturnType!(FunctionType) opCall(ParameterTypeTuple!(FunctionType) args)
		if(m_funcPtr is null)
			m_funcPtr = ModuleManager.getSymbol!(FunctionType)(ModuleName, ProcName);
		return m_funcPtr(args);

yidabu <yidabu.nospam gmail.com>
D China:
Jul 05 2007