mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-25 22:03:45 -06:00
This might bring some incompatibilities in the code that expects wchar to be 16 bit, which escpecially involves dealing with Win32 (and to lesser extend MacOS) APIs, so if your application is doing that, please check all instances of WCHAR (UniChar on MacOS) or even wchar especially type casts. To support host APIs, char16 is introduced (but there is no 16-bit String varian). Use ToSystemCharsetW, FromSystemCharsetW to convert texts to Win32 API. - Support of drawing non-BMP characters in GUI - Vastly improved character font replacement code (when drawing characters missing with requested font, replacement font is used) - Last instances of Win32 ANSI calls (those ending with A) are removed - UTF handling routines are refactored and their's naming is unified - RTF is now being able to handle non-BMP characters (RTF is used as clipboard format for RichText) Other minor changes: - fixed TryRealloc issue - improved MemoryCheck - Removed MemoryAlloc48/MemoryFree48 - In theide Background parsing should less often cause delays in the main thread
504 lines
11 KiB
C++
504 lines
11 KiB
C++
#include "ide.h"
|
|
|
|
namespace Upp {
|
|
bool IsSystemThemeDark();
|
|
};
|
|
|
|
void Ide::RunArgs() {
|
|
WithRunLayout<TopWindow> dlg;
|
|
CtrlLayoutOKCancel(dlg, "Run options");
|
|
dlg.Sizeable().Zoomable();
|
|
|
|
#ifndef PLATFORM_POSIX
|
|
dlg.consolemode.Hide();
|
|
dlg.console_label.Hide();
|
|
#endif
|
|
|
|
#ifndef PLATFORM_WIN32
|
|
dlg.advanced_label.Hide();
|
|
dlg.disable_uhd.Hide();
|
|
dlg.darkmode.Hide();
|
|
#endif
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
dlg.darkmode.SetLabel(IsSystemThemeDark() ? "Run in light mode" : "Run in dark mode");
|
|
#endif
|
|
|
|
DirSelect(dlg.dir, dlg.dirb);
|
|
dlg.dir = rundir.ToWString();
|
|
|
|
dlg.arg <<= runarg;
|
|
|
|
{
|
|
StringStream ss(recent_runarg);
|
|
dlg.arg.SerializeList(ss);
|
|
}
|
|
|
|
FileSelectSaveAs(dlg.stdout_file, dlg.stdout_fileb,
|
|
"Text files (*.txt)\t*.txt\nLog files (*.log)\t*.log\nAll files (*.*)\t*.*");
|
|
|
|
{
|
|
StringStream ss(recent_stdout_file);
|
|
dlg.stdout_file.SerializeList(ss);
|
|
dlg.stdout_file <<= stdout_file;
|
|
}
|
|
|
|
dlg.runmode <<= runmode;
|
|
dlg.external = runexternal;
|
|
dlg.consolemode = consolemode;
|
|
dlg.utf8 <<= console_utf8;
|
|
dlg.runmode <<= dlg.Breaker(222);
|
|
dlg.disable_uhd <<= disable_uhd;
|
|
dlg.darkmode <<= darkmode;
|
|
dlg.minimize <<= minimize;
|
|
|
|
auto Ins = [&](bool file) {
|
|
int l, h;
|
|
dlg.arg.GetSelection(l, h);
|
|
String s = file ? SelectFileOpen("All files\t*.*") : SelectDirectory();
|
|
dlg.arg.SetSelection(l, h);
|
|
if(s.GetCount()) {
|
|
if(s.Find(' ') >= 0)
|
|
s = '\"' + s + '\"';
|
|
dlg.arg.Insert(s);
|
|
}
|
|
};
|
|
dlg.ifile.SetImage(CtrlImg::File());
|
|
dlg.ifile << [&] { Ins(true); };
|
|
dlg.idir.SetImage(CtrlImg::Dir());
|
|
dlg.idir << [&] { Ins(false); };
|
|
|
|
for(;;) {
|
|
bool b = ~dlg.runmode == RUN_FILE;
|
|
dlg.stdout_file_lbl.Enable(b);
|
|
dlg.stdout_file.Enable(b);
|
|
dlg.stdout_fileb.Enable(b);
|
|
int rm = ~dlg.runmode;
|
|
dlg.stdout_file.Enable(rm == RUN_FILE || rm == RUN_FILE_CONSOLE);
|
|
dlg.utf8.Enable(rm != RUN_WINDOW);
|
|
switch(dlg.Run()) {
|
|
case IDOK:
|
|
rundir = ~dlg.dir;
|
|
runarg = ~dlg.arg;
|
|
runmode = ~dlg.runmode;
|
|
runexternal = dlg.external;
|
|
consolemode = dlg.consolemode;
|
|
console_utf8 = ~dlg.utf8;
|
|
stdout_file = ~dlg.stdout_file;
|
|
disable_uhd = ~dlg.disable_uhd;
|
|
darkmode = ~dlg.darkmode;
|
|
minimize = ~dlg.minimize;
|
|
dlg.arg.AddHistory();
|
|
{
|
|
StringStream ss;
|
|
dlg.arg.SerializeList(ss);
|
|
recent_runarg = ss;
|
|
}
|
|
{
|
|
StringStream ss;
|
|
dlg.stdout_file.SerializeList(ss);
|
|
recent_stdout_file = ss;
|
|
}
|
|
return;
|
|
|
|
case IDCANCEL:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ide::CreateHostRunDir(Host& h)
|
|
{
|
|
CreateHost(h, darkmode, disable_uhd);
|
|
if(IsNull(rundir))
|
|
h.ChDir(GetFileFolder(target));
|
|
else
|
|
h.ChDir(rundir);
|
|
}
|
|
|
|
bool Ide::ShouldHaveConsole()
|
|
{
|
|
return decode(consolemode, 0, FindIndex(SplitFlags(mainconfigparam, true), "GUI") < 0,
|
|
1, true, false);
|
|
}
|
|
|
|
void Ide::BuildAndExecute()
|
|
{
|
|
if(Build()) {
|
|
String targetExt = GetFileExt(target);
|
|
if(targetExt == ".apk")
|
|
ExecuteApk();
|
|
else
|
|
ExecuteBinary();
|
|
}
|
|
}
|
|
|
|
void Ide::ExecuteBinary()
|
|
{
|
|
int time = msecs();
|
|
Host h;
|
|
CreateHostRunDir(h);
|
|
h.ChDir(Nvl(rundir, GetFileFolder(target)));
|
|
String cmdline;
|
|
if(minimize)
|
|
Minimize();
|
|
if(!runexternal)
|
|
cmdline << '\"' << target << "\" ";
|
|
cmdline << runarg;
|
|
|
|
int exitcode;
|
|
switch(runmode) {
|
|
case RUN_WINDOW:
|
|
HideBottom();
|
|
h.Launch(cmdline, ShouldHaveConsole());
|
|
break;
|
|
case RUN_CONSOLE:
|
|
ShowConsole();
|
|
PutConsole(String().Cat() << "Executing: " << cmdline);
|
|
console.Sync();
|
|
exitcode = h.ExecuteWithInput(cmdline, console_utf8);
|
|
PutConsole("Finished in " + GetPrintTime(time) + ", exit code: " + AsString(exitcode));
|
|
break;
|
|
case RUN_FILE: {
|
|
HideBottom();
|
|
String fn;
|
|
if(IsNull(stdout_file))
|
|
fn = ForceExt(target, ".ol");
|
|
else
|
|
fn = stdout_file;
|
|
FileOut out(fn);
|
|
if(!out) {
|
|
PromptOK("Unable to open output file [* " + DeQtf(stdout_file) + "] !");
|
|
return;
|
|
}
|
|
if(h.Execute(cmdline, out, console_utf8) >= 0) {
|
|
out.Close();
|
|
EditFile(fn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ide::LaunchTerminal(const char *dir)
|
|
{
|
|
Host h;
|
|
CreateHost(h, false, false);
|
|
h.ChDir(dir);
|
|
#ifdef PLATFORM_WIN32
|
|
h.Launch(Nvl(HostConsole, "powershell.exe"), false);
|
|
#elif defined(PLATFORM_COCOA)
|
|
String script = ConfigFile("console-script-" + AsString(getpid()));
|
|
FileStream out(script, FileStream::CREATE, 0777);
|
|
out << "#!/bin/sh\n"
|
|
<< "cd " << dir << '\n'
|
|
<< "export PS1=\"\\w > \"\n"
|
|
<< "/bin/bash\n"
|
|
;
|
|
h.Launch("/usr/bin/open " + script);
|
|
#else
|
|
String c = HostConsole;
|
|
int q = c.Find(' ');
|
|
if(q >= 0)
|
|
c.Trim(q);
|
|
h.Launch(Nvl(c, "/usr/bin/xterm"), false);
|
|
#endif
|
|
}
|
|
|
|
class SelectAndroidDeviceDlg : public WithSelectAndroidDeviceLayout<TopWindow> {
|
|
typedef SelectAndroidDeviceDlg CLASSNAME;
|
|
|
|
public:
|
|
SelectAndroidDeviceDlg(AndroidSDK* sdk);
|
|
|
|
int GetDeviceCount() const { return devicesArray.GetCount(); }
|
|
String GetSelectedSerial() const;
|
|
|
|
private:
|
|
void LoadDevices();
|
|
|
|
void OnRefresh();
|
|
|
|
private:
|
|
AndroidSDK* sdk;
|
|
};
|
|
|
|
SelectAndroidDeviceDlg::SelectAndroidDeviceDlg(AndroidSDK* sdk) :
|
|
sdk(sdk)
|
|
{
|
|
CtrlLayoutOKCancel(*this, "Android device selection");
|
|
|
|
devicesArray.AddColumn("Serial Number");
|
|
devicesArray.AddColumn("State");
|
|
|
|
refresh <<= THISBACK(OnRefresh);
|
|
|
|
LoadDevices();
|
|
}
|
|
|
|
String SelectAndroidDeviceDlg::GetSelectedSerial() const
|
|
{
|
|
int row = devicesArray.IsCursor() ? devicesArray.GetCursor() : 0;
|
|
return devicesArray.GetCount() ? devicesArray.Get(row, 0) : "";
|
|
}
|
|
|
|
void SelectAndroidDeviceDlg::LoadDevices()
|
|
{
|
|
Vector<AndroidDevice> devices = sdk->FindDevices();
|
|
for(int i = 0; i < devices.GetCount(); i++) {
|
|
devicesArray.Add(devices[i].GetSerial(), devices[i].GetState());
|
|
}
|
|
|
|
if(devicesArray.GetCount()) {
|
|
devicesArray.GoBegin();
|
|
ok.Enable();
|
|
}
|
|
else
|
|
ok.Disable();
|
|
}
|
|
|
|
void SelectAndroidDeviceDlg::OnRefresh()
|
|
{
|
|
devicesArray.Clear();
|
|
LoadDevices();
|
|
}
|
|
|
|
void Ide::ExecuteApk()
|
|
{
|
|
AndroidSDK sdk(GetAndroidSdkPath(), true);
|
|
if(!sdk.Validate())
|
|
return;
|
|
|
|
SelectAndroidDeviceDlg select(&sdk);
|
|
if(select.GetDeviceCount() != 1 && select.Run() != IDOK)
|
|
return;
|
|
if(!select.GetDeviceCount())
|
|
return;
|
|
|
|
Host host;
|
|
CreateHost(host, darkmode, disable_uhd);
|
|
Apk apk(target, sdk);
|
|
String packageName = apk.FindPackageName();
|
|
String activityName = apk.FindLaunchableActivity();
|
|
|
|
Adb adb = sdk.MakeAdb();
|
|
adb.SetSerial(select.GetSelectedSerial());
|
|
host.Execute(adb.MakeInstallCmd(target));
|
|
|
|
if(!packageName.IsEmpty() && !activityName.IsEmpty())
|
|
host.Execute(adb.MakeLaunchOnDeviceCmd(packageName, activityName));
|
|
}
|
|
|
|
void Ide::BuildAndDebug0(const String& srcfile)
|
|
{
|
|
if(Build()) {
|
|
Host h;
|
|
CreateHostRunDir(h);
|
|
h.ChDir(GetFileFolder(target));
|
|
VectorMap<String, String> bm = GetMethodVars(method);
|
|
String dbg = Nvl(bm.Get("DEBUGGER", Null), "gdb");
|
|
h.Launch('\"' + dbg + "\" \"" + target + "\"", true);
|
|
}
|
|
}
|
|
|
|
void Ide::BuildAndExtDebug()
|
|
{
|
|
BuildAndDebug0(Null);
|
|
}
|
|
|
|
void Ide::BuildAndExtDebugFile()
|
|
{
|
|
BuildAndDebug0(editfile);
|
|
}
|
|
|
|
One<Debugger> GdbCreate(Host& host, const String& exefile, const String& cmdline, bool console);
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
One<Debugger> PdbCreate(Host& host, const String& exefile, const String& cmdline, bool clang);
|
|
#endif
|
|
|
|
void Ide::BuildAndDebug(bool runto)
|
|
{
|
|
VectorMap<String, String> bm = GetMethodVars(method);
|
|
String builder = bm.Get("BUILDER", "");
|
|
|
|
// TODO: implement debugging on android
|
|
if(builder == "ANDROID") {
|
|
BuildAndExecute();
|
|
return;
|
|
}
|
|
|
|
if(!Build())
|
|
return;
|
|
if(!FileExists(target))
|
|
return;
|
|
if(designer && !editfile_isfolder)
|
|
EditAsText();
|
|
Host host;
|
|
CreateHostRunDir(host);
|
|
host.ChDir(Nvl(rundir, GetFileFolder(target)));
|
|
HideBottom();
|
|
editor.Disable();
|
|
|
|
bool console = ShouldHaveConsole();
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
if(findarg(builder, "GCC") < 0) // llvm-mingw can generate pdb symbolic info
|
|
debugger = PdbCreate(host, target, runarg, builder == "CLANG");
|
|
else
|
|
#endif
|
|
debugger = GdbCreate(host, target, runarg, console);
|
|
|
|
if(!debugger) {
|
|
IdeEndDebug();
|
|
SetBar();
|
|
editor.Enable();
|
|
return;
|
|
}
|
|
debuglock = 0;
|
|
const Workspace& wspc = IdeWorkspace();
|
|
for(int i = 0; i < wspc.GetCount(); i++) {
|
|
const Package& pk = wspc.GetPackage(i);
|
|
String n = wspc[i];
|
|
for(int i = 0; i < pk.file.GetCount(); i++) {
|
|
String file = SourcePath(n, pk.file[i]);
|
|
LineInfo& ln = Filedata(file).lineinfo;
|
|
for(int i = 0; i < ln.GetCount(); i++) {
|
|
LineInfoRecord& lr = ln[i];
|
|
if(!lr.breakpoint.IsEmpty())
|
|
if(!debugger->SetBreakpoint(file, lr.lineno, lr.breakpoint)) {
|
|
lr.breakpoint = "\xe";
|
|
if(PathIsEqual(file, editfile))
|
|
editor.SetBreakpoint(lr.lineno, "\xe");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SetBar();
|
|
editor.Enable();
|
|
if(runto) {
|
|
if(!debugger->RunTo())
|
|
IdeEndDebug();
|
|
}
|
|
else
|
|
debugger->Run();
|
|
}
|
|
|
|
void Ide::DebugClearBreakpoints()
|
|
{
|
|
const Workspace& wspc = IdeWorkspace();
|
|
for(int i = 0; i < wspc.GetCount(); i++) {
|
|
const Package& pk = wspc.GetPackage(i);
|
|
String n = wspc[i];
|
|
for(int i = 0; i < pk.file.GetCount(); i++) {
|
|
String file = SourcePath(n, pk.file[i]);
|
|
LineInfo& ln = Filedata(file).lineinfo;
|
|
if(debugger)
|
|
for(int i = 0; i < ln.GetCount(); i++) {
|
|
const LineInfoRecord& lr = ln[i];
|
|
if(!lr.breakpoint.IsEmpty())
|
|
debugger->SetBreakpoint(file, lr.lineno, "");
|
|
}
|
|
ClearBreakpoints(ln);
|
|
}
|
|
}
|
|
editor.ClearBreakpoints();
|
|
}
|
|
|
|
void Ide::OnBreakpoint(int i)
|
|
{
|
|
if(!editfile.IsEmpty() && !designer && debugger) {
|
|
String q = editor.GetBreakpoint(i);
|
|
if(q[0] != 0xe && !debugger->SetBreakpoint(editfile, i, q)) {
|
|
auto event = editor.WhenBreakpoint;
|
|
editor.WhenBreakpoint = {};
|
|
|
|
if(!q.IsEmpty())
|
|
editor.SetBreakpoint(i, Null);
|
|
else
|
|
editor.SetBreakpoint(i, "1");
|
|
|
|
editor.WhenBreakpoint = event;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ide::DebugToggleBreak()
|
|
{
|
|
if(editfile.IsEmpty() || designer)
|
|
return;
|
|
int ln = editor.GetCursorLine();
|
|
String brk = editor.GetBreakpoint(ln);
|
|
if(!brk.IsEmpty())
|
|
editor.SetBreakpoint(ln, Null);
|
|
else
|
|
editor.SetBreakpoint(ln, "1");
|
|
editor.RefreshFrame();
|
|
}
|
|
|
|
void Ide::ConditionalBreak()
|
|
{
|
|
if(editfile.IsEmpty() || designer)
|
|
return;
|
|
int ln = editor.GetCursorLine();
|
|
String brk = editor.GetBreakpoint(ln);
|
|
if(brk == "\xe")
|
|
brk = "1";
|
|
|
|
Host h;
|
|
CreateHost(h, darkmode, disable_uhd);
|
|
Index<String> cfg = PackageConfig(IdeWorkspace(), 0, GetMethodVars(method), mainconfigparam, h,
|
|
*CreateBuilder(&h));
|
|
#ifdef PLATFORM_WIN32
|
|
if(cfg.Find("MSC") >= 0) {
|
|
if(EditPDBExpression("Conditional breakpoint", brk, NULL))
|
|
editor.SetBreakpoint(ln, brk);
|
|
}
|
|
else
|
|
#endif
|
|
if(EditText(brk, "Conditional breakpoint", "Condition"))
|
|
editor.SetBreakpoint(ln, brk);
|
|
editor.RefreshFrame();
|
|
}
|
|
|
|
void Ide::StopDebug()
|
|
{
|
|
if(debugger)
|
|
debugger->Stop();
|
|
console.Kill();
|
|
PosSync();
|
|
}
|
|
|
|
bool Ide::EditorTip(CodeEditor::MouseTip& mt)
|
|
{
|
|
if(!debugger)
|
|
return false;
|
|
DR_LOG("EditorTip");
|
|
int pos = mt.pos;
|
|
String e;
|
|
String sep;
|
|
while(pos >= 0) {
|
|
String b = editor.ReadIdBackPos(pos, false);
|
|
if(b.GetCount() == 0)
|
|
break;
|
|
e = b + sep + e;
|
|
sep = ".";
|
|
while(pos > 0 && editor.GetChar(pos - 1) == ' ')
|
|
pos--;
|
|
if(pos > 0 && editor.GetChar(pos - 1) == '.')
|
|
--pos;
|
|
else
|
|
if(pos >= 2 && editor.GetChar(pos - 1) == ':' && editor.GetChar(pos - 2) == ':') {
|
|
pos -= 2;
|
|
sep = "::";
|
|
}
|
|
else
|
|
if(pos >= 2 && editor.GetChar(pos - 1) == '>' && editor.GetChar(pos - 2) == '-')
|
|
pos -= 2;
|
|
else
|
|
break;
|
|
while(pos > 0 && editor.GetChar(pos - 1) == ' ')
|
|
pos--;
|
|
}
|
|
DR_LOG("debugger->Tip");
|
|
return debugger->Tip(e, mt);
|
|
}
|