diff --git a/README.md b/README.md index 1bc4397f..ee2b8e23 100644 --- a/README.md +++ b/README.md @@ -23,26 +23,29 @@ the issue.) * Working: * Autocompletion of properties of built-in types such as int, float, double, etc. - * Autocompletion of __traits, scope, and extern arguments - * Autocompletion of enums + * Autocompletion of __traits, scope, and extern arguments. + * Autocompletion of enums. * Autocompletion of class, struct, and interface instances. - * Display of call tips for functions, constructors, and variables of function type - * alias declarations - * Public imports - * Finding the declaration location of a symbol at the cursor - * *import* statement completions - * Display of documentation comments in function call tips - * *alias this* - * *auto* declarations (Mostly) - * *with* statements + * Display of call tips for functions, constructors, and variables of function type. + * *alias* declarations. + * Public imports. + * Finding the declaration location of a symbol at the cursor. + * *import* statement completions. + * Display of documentation comments in function call tips. + * *alias this*. + * *auto* declarations (mostly). + * *with* statements. * Simple UFCS suggestions for concrete types and fundamental types. + * ImportC modules (if the environment variable `DMD` or the `dmd` found in the + `PATH`, resolves to a dmd compiler/wrapper that supports `-Hf`). * Not working: * UFCS completion for templates, literals, aliased types, UFCS function arguments, and '.' chaining with other UFCS functions. - * UFCS calltips - * Autocompletion of declarations with template arguments (This will work to some extent, but it won't do things like replace T with int) - * Determining the type of an enum member when no base type is specified, but the first member has an initializer - * auto functions (which can then propagate the failure to auto declarations) - * That one feature that you *REALLY* needed + * UFCS calltips. + * Autocompletion of declarations with template arguments (This will work to some extent, but it won't do things like replace T with int). + * Determining the type of an enum member when no base type is specified, but the first member has an initializer. + * *auto* functions (which can then propagate the failure to auto declarations). + * *import* statement completion with ImportC modules. + * That one feature that you *REALLY* needed. # Setup ### General diff --git a/dsymbol/src/dsymbol/modulecache.d b/dsymbol/src/dsymbol/modulecache.d index ca1ba9ff..ee4dfdd8 100644 --- a/dsymbol/src/dsymbol/modulecache.d +++ b/dsymbol/src/dsymbol/modulecache.d @@ -148,7 +148,10 @@ struct ModuleCache */ DSymbol* cacheModule(string location) { + import std.file : tempDir; + import std.process : execute; import std.stdio : File; + import std.uuid : randomUUID; assert (location !is null); @@ -162,7 +165,29 @@ struct ModuleCache recursionGuard.insert(&cachedLocation.data[0]); - File f = File(cachedLocation); + string generatedHeaderFile; + scope (exit) + { + if (generatedHeaderFile.length && generatedHeaderFile.exists) + generatedHeaderFile.remove; + } + + const bool isImportC = cachedLocation.extension.among(".c", ".h", ".i") > 0; + if (isImportC) + { + generatedHeaderFile = buildPath(tempDir, "dcd_" ~ randomUUID.toString ~ ".di"); + const dmdResult = execute([dmd, "-o-", "-Hf" ~ generatedHeaderFile, cachedLocation]); + if (dmdResult.status != 0) + { + warningf( + "Generating .di file for ImportC file \"%s\" failed with status code %d: %s", + cachedLocation, dmdResult.status, dmdResult.output, + ); + return null; + } + } + + File f = File(isImportC ? generatedHeaderFile : cachedLocation); immutable fileSize = cast(size_t) f.size; if (fileSize == 0) return null; @@ -301,13 +326,18 @@ struct ModuleCache // no exact matches and no .di/package.d matches either else if (!alternative.length) { - string dotDi = buildPath(path, moduleName) ~ ".di"; - string dotD = dotDi[0 .. $ - 1]; - string withoutSuffix = dotDi[0 .. $ - 3]; + string withoutSuffix = buildPath(path, moduleName); + string dotD = withoutSuffix ~ ".d"; + string dotDi = dotD ~ "i"; + string dotC = withoutSuffix ~ ".c"; + string dotH = withoutSuffix ~ ".h"; + string dotI = withoutSuffix ~ ".i"; if (existsAnd!isFile(dotD)) return istring(dotD); // return early for exactly matching .d files - else if (existsAnd!isFile(dotDi)) - alternative = dotDi; + else if (existsAnd!isFile(dotDi)) alternative = dotDi; + else if (existsAnd!isFile(dotC)) alternative = dotC; + else if (existsAnd!isFile(dotH)) alternative = dotH; + else if (existsAnd!isFile(dotI)) alternative = dotI; else if (existsAnd!isDir(withoutSuffix)) { string packagePath = buildPath(withoutSuffix, "package.di"); @@ -402,7 +432,7 @@ private: { if (f.name.existsAnd!isFile) { - if (!f.name.extension.among(".d", ".di") || f.name.baseName.startsWith(".#")) + if (!f.name.extension.among(".d", ".di", ".c", ".h", ".i") || f.name.baseName.startsWith(".#")) continue; cacheModule(f.name); } @@ -522,3 +552,13 @@ else version (Posix) assert(!existsAnd!isFile(`/bin`)); } } + +private @safe +string dmd() +{ + import std.process : environment; + + if ("DMD" in environment) + return environment["DMD"]; + return "dmd"; +}