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 }