diff --git a/dub.json b/dub.json index e63d01a..1e2eb06 100644 --- a/dub.json +++ b/dub.json @@ -14,7 +14,7 @@ "copyFiles-windows": ["lib/win32/dcd-server.exe", "lib/win32/dcd-client.exe"], "dependencies": { - "dlangui": "~>0.7.17", + "dlangui": "~>0.7.18", "libdparse": "==0.2.0" }, diff --git a/src/ddebug/common/debugger.d b/src/ddebug/common/debugger.d index 682b5a2..b2e211d 100644 --- a/src/ddebug/common/debugger.d +++ b/src/ddebug/common/debugger.d @@ -18,7 +18,7 @@ class Breakpoint { string fullFilePath; string projectFilePath; int line; - bool enabled; + bool enabled = true; string projectName; this() { id = nextBreakpointId++; diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d index 001e79c..7ae9429 100644 --- a/src/dlangide/ui/dsourceedit.d +++ b/src/dlangide/ui/dsourceedit.d @@ -1,6 +1,7 @@ module dlangide.ui.dsourceedit; import dlangui.core.logger; +import dlangui.core.signals; import dlangui.widgets.editors; import dlangui.widgets.srcedit; import dlangui.widgets.menu; @@ -16,12 +17,17 @@ import dlangide.ui.commands; import dlangide.ui.settings; import dlangide.tools.d.dsyntax; import dlangide.tools.editorTool; +import ddebug.common.debugger; import std.algorithm; +import std.utf : toUTF8, toUTF32; +interface BreakpointListChangeListener { + void onBreakpointListChanged(ProjectSourceFile sourceFile, Breakpoint[] breakpoints); +} /// DIDE source file editor -class DSourceEdit : SourceEdit { +class DSourceEdit : SourceEdit, EditableContentMarksChangeListener { this(string ID) { super(ID); styleId = null; @@ -36,12 +42,15 @@ class DSourceEdit : SourceEdit { popupMenu = editPopupItem; showIcons = true; showFolding = true; + content.marksChanged = this; } this() { this("SRCEDIT"); } + Signal!BreakpointListChangeListener breakpointListChanged; + /// handle theme change: e.g. reload some themed resources override void onThemeChanged() { backgroundColor = style.customColor("edit_background"); @@ -184,15 +193,87 @@ class DSourceEdit : SourceEdit { return super.handleAction(a); } + protected void addBreakpoint(int line) { + import std.path; + Breakpoint bp = new Breakpoint(); + bp.file = baseName(filename); + bp.line = line + 1; + bp.fullFilePath = filename; + if (projectSourceFile) { + bp.projectName = toUTF8(projectSourceFile.project.name); + bp.projectFilePath = projectSourceFile.project.absoluteToRelativePath(filename); + } + LineIcon icon = new LineIcon(LineIconType.breakpoint, line, bp); + content.lineIcons.add(icon); + notifyBreakpointListChanged(); + } + + protected void removeBreakpoint(int line, LineIcon icon) { + content.lineIcons.remove(icon); + notifyBreakpointListChanged(); + } + + void setBreakpointList(Breakpoint[] breakpoints) { + // remove all existing breakpoints + content.lineIcons.removeByType(LineIconType.breakpoint); + // add new breakpoints + foreach(bp; breakpoints) { + LineIcon icon = new LineIcon(LineIconType.breakpoint, bp.line - 1, bp); + content.lineIcons.add(icon); + } + } + + Breakpoint[] getBreakpointList() { + LineIcon[] icons = content.lineIcons.findByType(LineIconType.breakpoint); + Breakpoint[] breakpoints; + foreach(icon; icons) { + Breakpoint bp = cast(Breakpoint)icon.objectParam; + if (bp) + breakpoints ~= bp; + } + return breakpoints; + } + + protected void onMarksChange(EditableContent content, LineIcon[] movedMarks, LineIcon[] removedMarks) { + bool changed = false; + foreach(moved; movedMarks) { + if (moved.type == LineIconType.breakpoint) { + Breakpoint bp = cast(Breakpoint)moved.objectParam; + if (bp) { + // update Breakpoint line + bp.line = moved.line + 1; + changed = true; + } + } + } + foreach(removed; removedMarks) { + if (removed.type == LineIconType.breakpoint) { + Breakpoint bp = cast(Breakpoint)removed.objectParam; + if (bp) { + changed = true; + } + } + } + if (changed) + notifyBreakpointListChanged(); + } + + protected void notifyBreakpointListChanged() { + if (projectSourceFile) { + if (breakpointListChanged.assigned) + breakpointListChanged(projectSourceFile, getBreakpointList()); + } + } + protected void handleBreakpointAction(const Action a) { int line = a.longParam >= 0 ? cast(int)a.longParam : caretPos.line; LineIcon icon = content.lineIcons.findByLineAndType(line, LineIconType.breakpoint); switch(a.id) { case IDEActions.DebugToggleBreakpoint: if (icon) - content.lineIcons.remove(icon); + removeBreakpoint(line, icon); else - content.lineIcons.add(new LineIcon(LineIconType.breakpoint, line)); + addBreakpoint(line); break; case IDEActions.DebugEnableBreakpoint: break; diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d index 3d45b45..c4bb0d4 100644 --- a/src/dlangide/ui/frame.d +++ b/src/dlangide/ui/frame.d @@ -67,7 +67,7 @@ class BackgroundOperationWatcherTest : BackgroundOperationWatcher { } /// DIDE app frame -class IDEFrame : AppFrame, ProgramExecutionStatusListener { +class IDEFrame : AppFrame, ProgramExecutionStatusListener, BreakpointListChangeListener { private ToolBarComboBox projectConfigurationCombo; @@ -332,6 +332,8 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener { TabItem tab = _tabs.tab(filename); tab.objectParam = file; editor.modifiedStateChange = &onModifiedStateChange; + editor.breakpointListChanged = this; //onBreakpointListChanged + editor.setBreakpointList(currentWorkspace.getSourceFileBreakpoints(file)); applySettings(editor, settings); _tabs.selectTab(index, true); if( filename.endsWith(".d") ) @@ -878,6 +880,14 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener { }); } + void onBreakpointListChanged(ProjectSourceFile sourcefile, Breakpoint[] breakpoints) { + if (!currentWorkspace) + return; + if (sourcefile) { + currentWorkspace.setSourceFileBreakpoints(sourcefile, breakpoints); + } + } + void refreshProjectItem(const Object obj) { if (currentWorkspace is null) return; diff --git a/src/dlangide/workspace/project.d b/src/dlangide/workspace/project.d index a061135..12634aa 100644 --- a/src/dlangide/workspace/project.d +++ b/src/dlangide/workspace/project.d @@ -192,6 +192,10 @@ class ProjectSourceFile : ProjectItem { this(string filename) { super(filename); } + /// file path relative to project directory + @property string projectFilePath() { + return project.absoluteToRelativePath(filename); + } } class WorkspaceItem { @@ -436,6 +440,12 @@ class Project : WorkspaceItem { return buildNormalizedPath(_dir, path); } + string absoluteToRelativePath(string path) { + if (!isAbsolute(path)) + return path; + return relativePath(path, _dir); + } + @property ProjectSourceFile mainSourceFile() { return _mainSourceFile; } @property ProjectFolder items() { return _items; diff --git a/src/dlangide/workspace/workspace.d b/src/dlangide/workspace/workspace.d index 3978e1c..852f899 100644 --- a/src/dlangide/workspace/workspace.d +++ b/src/dlangide/workspace/workspace.d @@ -91,6 +91,24 @@ class Workspace : WorkspaceItem { _projectConfiguration = _startupProject.configurations[conf]; } } + + void updateBreakpointFiles(Breakpoint[] breakpoints) { + foreach(bp; breakpoints) { + Project project = findProjectByName(bp.projectName); + if (project) + bp.fullFilePath = project.relativeToAbsolutePath(bp.projectFilePath); + } + } + + Breakpoint[] getSourceFileBreakpoints(ProjectSourceFile file) { + Breakpoint[] res = _settings.getProjectBreakpoints(toUTF8(file.project.name), file.projectFilePath); + updateBreakpointFiles(res); + return res; + } + + void setSourceFileBreakpoints(ProjectSourceFile file, Breakpoint[] breakpoints) { + _settings.setProjectBreakpoints(toUTF8(file.project.name), file.projectFilePath, breakpoints); + } protected void fillStartupProject() { string s = _settings.startupProjectName; @@ -127,6 +145,15 @@ class Workspace : WorkspaceItem { return null; } + /// find project in workspace by filename + Project findProjectByName(string name) { + foreach (Project p; _projects) { + if (p.name.toUTF8.equal(name)) + return p; + } + return null; + } + void addProject(Project p) { _projects ~= p; p.workspace = this; diff --git a/src/dlangide/workspace/workspacesettings.d b/src/dlangide/workspace/workspacesettings.d index 25cab58..135a066 100644 --- a/src/dlangide/workspace/workspacesettings.d +++ b/src/dlangide/workspace/workspacesettings.d @@ -4,6 +4,8 @@ import dlangui.core.settings; import dlangui.core.i18n; import ddebug.common.debugger; +import std.array; + /// local settings for workspace (not supposed to put under source control) class WorkspaceSettings : SettingsFile { @@ -24,6 +26,44 @@ class WorkspaceSettings : SettingsFile { } } + /// get all breakpoints for project (for specified source file only, if specified) + Breakpoint[] getProjectBreakpoints(string projectName, string projectFilePath) { + Breakpoint[] res; + for (int i = cast(int)_breakpoints.length - 1; i >= 0; i--) { + Breakpoint bp = _breakpoints[i]; + if (!bp.projectName.equal(projectName)) + continue; + if (!projectFilePath.empty && !bp.projectFilePath.equal(projectFilePath)) + continue; + res ~= bp; + } + return res; + } + + /// get all breakpoints for project (for specified source file only, if specified) + void setProjectBreakpoints(string projectName, string projectFilePath, Breakpoint[] bps) { + bool changed = false; + for (int i = cast(int)_breakpoints.length - 1; i >= 0; i--) { + Breakpoint bp = _breakpoints[i]; + if (!bp.projectName.equal(projectName)) + continue; + if (!projectFilePath.empty && !bp.projectFilePath.equal(projectFilePath)) + continue; + for (auto j = i; j < _breakpoints.length - 1; j++) + _breakpoints[j] = _breakpoints[j + 1]; + _breakpoints.length--; + changed = true; + } + if (bps.length) { + changed = true; + foreach(bp; bps) + _breakpoints ~= bp; + } + if (changed) { + setBreakpoints(_breakpoints); + } + } + void setBreakpoints(Breakpoint[] bps) { Setting obj = _setting.settingByPath("breakpoints", SettingType.ARRAY); obj.clear(SettingType.ARRAY); @@ -53,10 +93,13 @@ class WorkspaceSettings : SettingsFile { _breakpoints = null; for (int i = 0; i < obj.length; i++) { Breakpoint bp = new Breakpoint(); - bp.id = cast(int)obj.getInteger("id"); - bp.file = obj.getString("file"); - bp.line = cast(int)obj.getInteger("line"); - bp.enabled = obj.getBoolean("enabled"); + Setting item = obj[i]; + bp.id = cast(int)item.getInteger("id"); + bp.file = item.getString("file"); + bp.projectName = item.getString("projectName"); + bp.projectFilePath = item.getString("projectFilePath"); + bp.line = cast(int)item.getInteger("line"); + bp.enabled = item.getBoolean("enabled"); _breakpoints ~= bp; } }