1 #include "ide.h"
2 
3 namespace Upp {
4 	bool IsSystemThemeDark();
5 };
6 
RunArgs()7 void Ide::RunArgs() {
8 	WithRunLayout<TopWindow> dlg;
9 	CtrlLayoutOKCancel(dlg, "Run options");
10 	dlg.Sizeable().Zoomable();
11 
12 #ifndef PLATFORM_POSIX
13 	dlg.consolemode.Hide();
14 	dlg.console_label.Hide();
15 #endif
16 
17 #ifndef PLATFORM_WIN32
18 	dlg.advanced_label.Hide();
19 	dlg.disable_uhd.Hide();
20 	dlg.darkmode.Hide();
21 #endif
22 
23 #ifdef PLATFORM_WIN32
24 	dlg.darkmode.SetLabel(IsSystemThemeDark() ? "Run in light mode" : "Run in dark mode");
25 #endif
26 
27 	SelectDirButton dir_browse("Run in folder");
28 	dir_browse.Attach(dlg.dir);
29 	dlg.dir = rundir.ToWString();
30 
31 	dlg.arg <<= runarg;
32 
33 	{
34 		StringStream ss(recent_runarg);
35 		dlg.arg.SerializeList(ss);
36 	}
37 
38 	SaveFileButton stdout_browse("Save STDOUT as");
39 	stdout_browse.Type("Text files (*.txt)", "*.txt").AllFilesType();
40 	stdout_browse.Attach(dlg.stdout_file);
41 
42 	{
43 		StringStream ss(recent_stdout_file);
44 		dlg.stdout_file.SerializeList(ss);
45 		dlg.stdout_file <<= stdout_file;
46 	}
47 
48 	dlg.runmode <<= runmode;
49 	dlg.external = runexternal;
50 	dlg.consolemode = consolemode;
51 	dlg.utf8 <<= console_utf8;
52 	dlg.runmode <<= dlg.Breaker(222);
53 	dlg.disable_uhd <<= disable_uhd;
54 	dlg.darkmode <<= darkmode;
55 
56 	auto Ins = [&](bool file) {
57 		int l, h;
58 		dlg.arg.GetSelection(l, h);
59 		String s = file ? SelectFileOpen("All files\t*.*") : SelectDirectory();
60 		dlg.arg.SetSelection(l, h);
61 		if(s.GetCount()) {
62 			if(s.Find(' ') >= 0)
63 				s = '\"' + s + '\"';
64 			dlg.arg.Insert(s);
65 		}
66 	};
67 	dlg.ifile.SetImage(CtrlImg::File());
68 	dlg.ifile << [&] { Ins(true); };
69 	dlg.idir.SetImage(CtrlImg::Dir());
70 	dlg.idir << [&] { Ins(false); };
71 
72 	for(;;) {
73 		bool b = ~dlg.runmode == RUN_FILE;
74 		dlg.stdout_file_lbl.Enable(b);
75 		dlg.stdout_file.Enable(b);
76 		int rm = ~dlg.runmode;
77 		dlg.stdout_file.Enable(rm == RUN_FILE || rm == RUN_FILE_CONSOLE);
78 		dlg.utf8.Enable(rm != RUN_WINDOW);
79 		switch(dlg.Run()) {
80 		case IDOK:
81 			rundir  = ~dlg.dir;
82 			runarg  = ~dlg.arg;
83 			runmode = ~dlg.runmode;
84 			runexternal = dlg.external;
85 			consolemode = dlg.consolemode;
86 			console_utf8 = ~dlg.utf8;
87 			stdout_file = ~dlg.stdout_file;
88 			disable_uhd = ~dlg.disable_uhd;
89 			darkmode = ~dlg.darkmode;
90 			dlg.arg.AddHistory();
91 			{
92 				StringStream ss;
93 				dlg.arg.SerializeList(ss);
94 				recent_runarg = ss;
95 			}
96 			{
97 				StringStream ss;
98 				dlg.stdout_file.SerializeList(ss);
99 				recent_stdout_file = ss;
100 			}
101 			return;
102 
103 		case IDCANCEL:
104 			return;
105 		}
106 	}
107 }
108 
CreateHostRunDir()109 One<Host> Ide::CreateHostRunDir()
110 {
111 	One<Host> h = CreateHost(darkmode, disable_uhd);
112 	if(IsNull(rundir))
113 		h->ChDir(GetFileFolder(target));
114 	else
115 		h->ChDir(rundir);
116 	return h;
117 }
118 
ShouldHaveConsole()119 bool Ide::ShouldHaveConsole()
120 {
121 	return decode(consolemode, 0, FindIndex(SplitFlags(mainconfigparam, true), "GUI") < 0,
122 	                           1, true, false);
123 }
124 
BuildAndExecute()125 void Ide::BuildAndExecute()
126 {
127 	if(Build()) {
128 		String targetExt = GetFileExt(target);
129 		if(targetExt == ".apk")
130 			ExecuteApk();
131 		else
132 			ExecuteBinary();
133 	}
134 }
135 
ExecuteBinary()136 void Ide::ExecuteBinary()
137 {
138 	int time = msecs();
139 	One<Host> h = CreateHostRunDir();
140 	h->ChDir(Nvl(rundir, GetFileFolder(target)));
141 	String cmdline;
142 	if(!runexternal)
143 		cmdline << '\"' << h->GetHostPath(target) << "\" ";
144 	cmdline << ToSystemCharset(runarg);
145 
146 	int exitcode;
147 	switch(runmode) {
148 	case RUN_WINDOW:
149 		HideBottom();
150 		h->Launch(cmdline, ShouldHaveConsole());
151 		break;
152 	case RUN_CONSOLE:
153 		ShowConsole();
154 		PutConsole(String().Cat() << "Executing: " << cmdline);
155 		console.Sync();
156 		exitcode = h->ExecuteWithInput(cmdline, console_utf8);
157 		PutConsole("Finished in " + GetPrintTime(time) + ", exit code: " + AsString(exitcode));
158 		break;
159 	case RUN_FILE: {
160 			HideBottom();
161 			String fn;
162 			if(IsNull(stdout_file))
163 				fn = ForceExt(target, ".ol");
164 			else
165 				fn = stdout_file;
166 			FileOut out(fn);
167 			if(!out) {
168 				PromptOK("Unable to open output file [* " + DeQtf(stdout_file) + "] !");
169 				return;
170 			}
171 			if(h->Execute(cmdline, out, console_utf8) >= 0) {
172 				out.Close();
173 				EditFile(fn);
174 			}
175 		}
176 	}
177 }
178 
179 class SelectAndroidDeviceDlg : public WithSelectAndroidDeviceLayout<TopWindow> {
180 	typedef SelectAndroidDeviceDlg CLASSNAME;
181 
182 public:
183 	SelectAndroidDeviceDlg(AndroidSDK* sdk);
184 
GetDeviceCount() const185 	int    GetDeviceCount() const    { return devicesArray.GetCount(); }
186 	String GetSelectedSerial() const;
187 
188 private:
189 	void LoadDevices();
190 
191 	void OnRefresh();
192 
193 private:
194 	AndroidSDK* sdk;
195 };
196 
SelectAndroidDeviceDlg(AndroidSDK * sdk)197 SelectAndroidDeviceDlg::SelectAndroidDeviceDlg(AndroidSDK* sdk) :
198 	sdk(sdk)
199 {
200 	CtrlLayoutOKCancel(*this, "Android device selection");
201 
202 	devicesArray.AddColumn("Serial Number");
203 	devicesArray.AddColumn("State");
204 
205 	refresh <<= THISBACK(OnRefresh);
206 
207 	LoadDevices();
208 }
209 
GetSelectedSerial() const210 String SelectAndroidDeviceDlg::GetSelectedSerial() const
211 {
212 	int row = devicesArray.IsCursor() ? devicesArray.GetCursor() : 0;
213 	return devicesArray.GetCount() ? devicesArray.Get(row, 0) : "";
214 }
215 
LoadDevices()216 void SelectAndroidDeviceDlg::LoadDevices()
217 {
218 	Vector<AndroidDevice> devices = sdk->FindDevices();
219 	for(int i = 0; i < devices.GetCount(); i++) {
220 		devicesArray.Add(devices[i].GetSerial(), devices[i].GetState());
221 	}
222 
223 	if(devicesArray.GetCount()) {
224 		devicesArray.GoBegin();
225 		ok.Enable();
226 	}
227 	else
228 		ok.Disable();
229 }
230 
OnRefresh()231 void SelectAndroidDeviceDlg::OnRefresh()
232 {
233 	devicesArray.Clear();
234 	LoadDevices();
235 }
236 
ExecuteApk()237 void Ide::ExecuteApk()
238 {
239 	AndroidSDK sdk(GetAndroidSdkPath(), true);
240 	if(!sdk.Validate())
241 		return;
242 
243 	SelectAndroidDeviceDlg select(&sdk);
244 	if(select.GetDeviceCount() != 1 && select.Run() != IDOK)
245 		return;
246 	if(!select.GetDeviceCount())
247 		return;
248 
249 	One<Host> host = CreateHost(darkmode, disable_uhd);
250 	Apk apk(target, sdk);
251 	String packageName = apk.FindPackageName();
252 	String activityName = apk.FindLaunchableActivity();
253 
254 	Adb adb = sdk.MakeAdb();
255 	adb.SetSerial(select.GetSelectedSerial());
256 	host->Execute(adb.MakeInstallCmd(target));
257 
258 	if(!packageName.IsEmpty() && !activityName.IsEmpty())
259 		host->Execute(adb.MakeLaunchOnDeviceCmd(packageName, activityName));
260 }
261 
BuildAndDebug0(const String & srcfile)262 void Ide::BuildAndDebug0(const String& srcfile)
263 {
264 	if(Build()) {
265 		One<Host> h = CreateHostRunDir();
266 		h->ChDir(GetFileFolder(target));
267 		VectorMap<String, String> bm = GetMethodVars(method);
268 		String dbg = Nvl(bm.Get("DEBUGGER", Null), "gdb");
269 		h->Launch('\"' + dbg + "\" \"" + h->GetHostPath(target) + "\"", true);
270 	}
271 }
272 
BuildAndExtDebug()273 void Ide::BuildAndExtDebug()
274 {
275 	BuildAndDebug0(Null);
276 }
277 
BuildAndExtDebugFile()278 void Ide::BuildAndExtDebugFile()
279 {
280 	BuildAndDebug0(editfile);
281 }
282 
283 One<Debugger> GdbCreate(One<Host>&& host, const String& exefile, const String& cmdline, bool console);
284 
285 #ifdef PLATFORM_WIN32
286 One<Debugger> PdbCreate(One<Host>&& host, const String& exefile, const String& cmdline, bool clang);
287 #endif
288 
BuildAndDebug(bool runto)289 void Ide::BuildAndDebug(bool runto)
290 {
291 	VectorMap<String, String> bm = GetMethodVars(method);
292 	String builder = bm.Get("BUILDER", "");
293 
294 	// TODO: implement debugging on android
295 	if(builder == "ANDROID") {
296 		BuildAndExecute();
297 		return;
298 	}
299 
300 	if(!Build())
301 		return;
302 	if(!FileExists(target))
303 		return;
304 	if(designer && !editfile_isfolder)
305 		EditAsText();
306 	One<Host> host = CreateHostRunDir();
307 	host->ChDir(Nvl(rundir, GetFileFolder(target)));
308 	HideBottom();
309 	editor.Disable();
310 
311 	bool console = ShouldHaveConsole();
312 
313 #ifdef PLATFORM_WIN32
314 	if(findarg(builder, "GCC") < 0) // llvm-mingw can generate pdb symbolic info
315 		debugger = PdbCreate(pick(host), target, runarg, builder == "CLANG");
316 	else
317 #endif
318 		debugger = GdbCreate(pick(host), target, runarg, console);
319 
320 	if(!debugger) {
321 		IdeEndDebug();
322 		SetBar();
323 		editor.Enable();
324 		return;
325 	}
326 	debuglock = 0;
327 	const Workspace& wspc = IdeWorkspace();
328 	for(int i = 0; i < wspc.GetCount(); i++) {
329 		const Package& pk = wspc.GetPackage(i);
330 		String n = wspc[i];
331 		for(int i = 0; i < pk.file.GetCount(); i++) {
332 			String file = SourcePath(n, pk.file[i]);
333 			LineInfo& ln = Filedata(file).lineinfo;
334 			for(int i = 0; i < ln.GetCount(); i++) {
335 				LineInfoRecord& lr = ln[i];
336 				if(!lr.breakpoint.IsEmpty())
337 					if(!debugger->SetBreakpoint(file, lr.lineno, lr.breakpoint)) {
338 						lr.breakpoint = "\xe";
339 						if(PathIsEqual(file, editfile))
340 							editor.SetBreakpoint(lr.lineno, "\xe");
341 					}
342 			}
343 		}
344 	}
345 	SetBar();
346 	editor.Enable();
347 	if(runto) {
348 		if(!debugger->RunTo())
349 			IdeEndDebug();
350 	}
351 	else
352 		debugger->Run();
353 }
354 
DebugClearBreakpoints()355 void Ide::DebugClearBreakpoints()
356 {
357 	const Workspace& wspc = IdeWorkspace();
358 	for(int i = 0; i < wspc.GetCount(); i++) {
359 		const Package& pk = wspc.GetPackage(i);
360 		String n = wspc[i];
361 		for(int i = 0; i < pk.file.GetCount(); i++) {
362 			String file = SourcePath(n, pk.file[i]);
363 			LineInfo& ln = Filedata(file).lineinfo;
364 			if(debugger)
365 				for(int i = 0; i < ln.GetCount(); i++) {
366 					const LineInfoRecord& lr = ln[i];
367 					if(!lr.breakpoint.IsEmpty())
368 						debugger->SetBreakpoint(file, lr.lineno, "");
369 				}
370 			ClearBreakpoints(ln);
371 		}
372 	}
373 	editor.ClearBreakpoints();
374 }
375 
OnBreakpoint(int i)376 void Ide::OnBreakpoint(int i)
377 {
378 	if(!editfile.IsEmpty() && !designer && debugger) {
379 		String q = editor.GetBreakpoint(i);
380 		if(q[0] != 0xe && !debugger->SetBreakpoint(editfile, i, q)) {
381 			auto event = editor.WhenBreakpoint;
382 			editor.WhenBreakpoint = {};
383 
384 			if(!q.IsEmpty())
385 				editor.SetBreakpoint(i, Null);
386 			else
387 				editor.SetBreakpoint(i, "1");
388 
389 			editor.WhenBreakpoint = event;
390 		}
391 	}
392 }
393 
DebugToggleBreak()394 void Ide::DebugToggleBreak()
395 {
396 	if(editfile.IsEmpty() || designer)
397 		return;
398 	int ln = editor.GetCursorLine();
399 	String brk = editor.GetBreakpoint(ln);
400 	if(!brk.IsEmpty())
401 		editor.SetBreakpoint(ln, Null);
402 	else
403 		editor.SetBreakpoint(ln, "1");
404 	editor.RefreshFrame();
405 }
406 
ConditionalBreak()407 void Ide::ConditionalBreak()
408 {
409 	if(editfile.IsEmpty() || designer)
410 		return;
411 	int ln = editor.GetCursorLine();
412 	String brk = editor.GetBreakpoint(ln);
413 	if(brk == "\xe")
414 		brk = "1";
415 
416 	Index<String> cfg = PackageConfig(IdeWorkspace(), 0, GetMethodVars(method), mainconfigparam,
417 	                                  *CreateHost(darkmode, disable_uhd), *CreateBuilder(~CreateHostRunDir()));
418 #ifdef PLATFORM_WIN32
419 	if(cfg.Find("MSC") >= 0) {
420 		if(EditPDBExpression("Conditional breakpoint", brk, NULL))
421 			editor.SetBreakpoint(ln, brk);
422 	}
423 	else
424 #endif
425 	if(EditText(brk, "Conditional breakpoint", "Condition"))
426 		editor.SetBreakpoint(ln, brk);
427 	editor.RefreshFrame();
428 }
429 
StopDebug()430 void Ide::StopDebug()
431 {
432 	if(debugger)
433 		debugger->Stop();
434 	console.Kill();
435 	PosSync();
436 }
437 
EditorTip(CodeEditor::MouseTip & mt)438 bool Ide::EditorTip(CodeEditor::MouseTip& mt)
439 {
440 	if(!debugger)
441 		return false;
442 	DR_LOG("EditorTip");
443 	int pos = mt.pos;
444 	String e;
445 	String sep;
446 	while(pos >= 0) {
447 		String b = editor.ReadIdBackPos(pos, false);
448 		if(b.GetCount() == 0)
449 			break;
450 		e = b + sep + e;
451 		sep = ".";
452 		while(pos > 0 && editor.GetChar(pos - 1) == ' ')
453 			pos--;
454 		if(pos > 0 && editor.GetChar(pos - 1) == '.')
455 			--pos;
456 		else
457 		if(pos >= 2 && editor.GetChar(pos - 1) == ':' && editor.GetChar(pos - 2) == ':') {
458 			pos -= 2;
459 			sep = "::";
460 		}
461 		else
462 		if(pos >= 2 && editor.GetChar(pos - 1) == '>' && editor.GetChar(pos - 2) == '-')
463 			pos -= 2;
464 		else
465 			break;
466 		while(pos > 0 && editor.GetChar(pos - 1) == ' ')
467 			pos--;
468 	}
469 	DR_LOG("debugger->Tip");
470 	return debugger->Tip(e, mt);
471 }
472