1 /** 2 * Entry-point to serve-d 3 * 4 * Replaces std.stdio stdout with stderr so writeln calls don't accidentally 5 * write to the RPC output. 6 * 7 * Handles all command line arguments, possibly modifying global state variables 8 * when enabling serve-d specific protocol extensions are requested. 9 * 10 * Handles all the request/notification dispatch, calls (de)serialization of 11 * given JSON parameters and return values and responds back to the RPC. 12 * 13 * Performs periodic GC cleanup and invokes the fiber scheduler, pushing 14 * incoming RPC requests as tasks to the fiber scheduler. 15 */ 16 module app; 17 18 import core.thread; 19 import core.sync.mutex; 20 21 import fs = std.file; 22 import io = std.stdio; 23 import std.algorithm; 24 import std.conv; 25 import std.datetime.stopwatch; 26 import std.experimental.logger; 27 import std.functional; 28 import std.getopt; 29 import std.json; 30 import std.path; 31 import std..string; 32 import std.traits; 33 34 import served.io.http_wrap; 35 import served.lsp.filereader; 36 import served.lsp.jsonrpc; 37 import served.types; 38 import served.utils.fibermanager; 39 import served.utils.trace; 40 import served.utils.translate; 41 42 import painlessjson; 43 44 static import served.extension; 45 46 void printVersion(io.File output = io.stdout) 47 { 48 import Compiler = std.compiler; 49 import OS = std.system; 50 51 static if (__traits(compiles, { 52 import workspaced.info : BundledDependencies, WorkspacedVersion = Version; 53 })) 54 import workspaced.info : BundledDependencies, WorkspacedVersion = Version; 55 else 56 import source.workspaced.info : BundledDependencies, WorkspacedVersion = Version; 57 import source.served.info; 58 59 output.writefln("serve-d v%(%s.%)%s with workspace-d v%(%s.%)", Version, 60 VersionSuffix.length ? text('-', VersionSuffix) : VersionSuffix, WorkspacedVersion); 61 output.writefln("Included features: %(%s, %)", IncludedFeatures); 62 // There will always be a line which starts with `Built: ` forever, it is considered stable. If there is no line, assume version 0.1.2 63 output.writefln("Built: %s", __TIMESTAMP__); 64 output.writeln("with compiler ", Compiler.name, " v", 65 Compiler.version_major.to!string, ".", Compiler.version_minor.to!string, 66 " on ", OS.os.to!string, " ", OS.endian.to!string); 67 output.writefln(BundledDependencies); 68 } 69 70 int main(string[] args) 71 { 72 debug globalLogLevel = LogLevel.trace; 73 else globalLogLevel = LogLevel.info; 74 75 bool printVer; 76 string[] features; 77 string[] provides; 78 string lang = "en"; 79 bool wait; 80 81 void setLogLevel(string option, string level) 82 { 83 switch (level) 84 { 85 static foreach (levelName; __traits(allMembers, LogLevel)) 86 { 87 case levelName: 88 globalLogLevel = __traits(getMember, LogLevel, levelName); 89 return; 90 } 91 default: 92 throw new GetOptException( 93 "Unknown value for log level, supported values: " 94 ~ [__traits( 95 allMembers, LogLevel)].join(", ")); 96 } 97 } 98 99 void setLogFile(string option, string file) 100 { 101 sharedLog = new FileLogger(file, LogLevel.all, CreateFolder.no); 102 } 103 104 //dfmt off 105 auto argInfo = args.getopt( 106 "r|require", "Adds a feature set that is required. Unknown feature sets will intentionally crash on startup", &features, 107 "p|provide", "Features to let the editor handle for better integration", &provides, 108 "v|version", "Print version of program", &printVer, 109 "logfile", "Output all log into the given file instead of stderr", &setLogFile, 110 "loglevel", "Change the log level for output logging (" ~ [__traits(allMembers, LogLevel)].join("|") ~ ")", &setLogLevel, 111 "lang", "Change the language of GUI messages", &lang, 112 "wait", "Wait for a second before starting (for debugging)", &wait); 113 //dfmt on 114 if (wait) 115 Thread.sleep(2.seconds); 116 if (argInfo.helpWanted) 117 { 118 if (printVer) 119 printVersion(); 120 defaultGetoptPrinter("workspace-d / vscode-language-server bridge", argInfo.options); 121 return 0; 122 } 123 if (printVer) 124 { 125 printVersion(); 126 return 0; 127 } 128 129 if (lang.length >= 2) // ja-JP -> ja, en-GB -> en, etc 130 currentLanguage = lang[0 .. 2]; 131 if (currentLanguage != "en") 132 info("Setting language to ", currentLanguage); 133 134 foreach (feature; features) 135 if (!IncludedFeatures.canFind(feature.toLower.strip)) 136 { 137 io.stderr.writeln(); 138 io.stderr.writeln( 139 "FATAL: Extension-requested feature set '" ~ feature 140 ~ "' is not in this version of serve-d!"); 141 io.stderr.writeln("---"); 142 io.stderr.writeln("HINT: Maybe serve-d is outdated?"); 143 io.stderr.writeln(); 144 return 1; 145 } 146 trace("Features fulfilled"); 147 148 foreach (provide; provides) 149 { 150 // don't forget to update README.md if adding stuff! 151 switch (provide) 152 { 153 case "http": 154 letEditorDownload = true; 155 trace("Interactive HTTP downloads handled via editor"); 156 break; 157 case "implement-snippets": 158 import served.commands.code_actions : implementInterfaceSnippets; 159 160 implementInterfaceSnippets = true; 161 trace("Auto-implement interface supports snippets"); 162 break; 163 case "context-snippets": 164 import served.commands.complete : doCompleteSnippets; 165 166 doCompleteSnippets = true; 167 trace("Context snippets handled by serve-d"); 168 break; 169 case "test-runner": 170 import served.commands.test_provider : doTrackTests; 171 172 doTrackTests = true; 173 trace("Discoverying & emitting unittests for language client"); 174 break; 175 default: 176 warningf("Unknown --provide flag '%s' provided. Maybe serve-d is outdated?", provide); 177 break; 178 } 179 } 180 181 printVersion(io.stderr); 182 183 return lspRouter.run() ? 0 : 1; 184 }