From 90da989bf3efacb3cd0c020980bbc944533ad1b7 Mon Sep 17 00:00:00 2001
From: Vadim Lopatin <coolreader.org@gmail.com>
Date: Tue, 15 Dec 2015 10:52:03 +0300
Subject: [PATCH] Source editor: breakpoints support

---
 src/dlangide/ui/commands.d    | 10 +++++++
 src/dlangide/ui/dsourceedit.d | 52 ++++++++++++++++++++++++++++++++++-
 src/dlangide/ui/frame.d       |  4 ++-
 views/res/i18n/en.ini         |  5 ++++
 4 files changed, 69 insertions(+), 2 deletions(-)

diff --git a/src/dlangide/ui/commands.d b/src/dlangide/ui/commands.d
index 0231c40..d1630df 100644
--- a/src/dlangide/ui/commands.d
+++ b/src/dlangide/ui/commands.d
@@ -28,11 +28,16 @@ enum IDEActions : int {
     UpdateProjectDependencies,
     SetStartupProject,
     ProjectSettings,
+
     DebugStart,
     DebugStartNoDebug,
     DebugContinue,
     DebugStop,
     DebugPause,
+    DebugToggleBreakpoint,
+    DebugEnableBreakpoint,
+    DebugDisableBreakpoint,
+
     HelpAbout,
     WindowCloseAllDocuments,
     CreateNewWorkspace,
@@ -88,6 +93,11 @@ const Action ACTION_DEBUG_START_NO_DEBUG = new Action(IDEActions.DebugStartNoDeb
 const Action ACTION_DEBUG_CONTINUE = new Action(IDEActions.DebugContinue, "MENU_DEBUG_CONTINUE"c, "debug-run");
 const Action ACTION_DEBUG_STOP = (new Action(IDEActions.DebugStop, "MENU_DEBUG_STOP"c, "debug-stop")).disableByDefault();
 const Action ACTION_DEBUG_PAUSE = (new Action(IDEActions.DebugPause, "MENU_DEBUG_PAUSE"c, "debug-pause")).disableByDefault();
+
+const Action ACTION_DEBUG_TOGGLE_BREAKPOINT = (new Action(IDEActions.DebugToggleBreakpoint, "MENU_DEBUG_BREAKPOINT_TOGGLE"c, null, KeyCode.F9, 0)).disableByDefault();
+const Action ACTION_DEBUG_ENABLE_BREAKPOINT = (new Action(IDEActions.DebugEnableBreakpoint, "MENU_DEBUG_BREAKPOINT_ENABLE"c, null, KeyCode.F9, KeyFlag.Shift)).disableByDefault();
+const Action ACTION_DEBUG_DISABLE_BREAKPOINT = (new Action(IDEActions.DebugDisableBreakpoint, "MENU_DEBUG_BREAKPOINT_DISABLE"c, null, KeyCode.F9, KeyFlag.Control)).disableByDefault();
+
 const Action ACTION_EDIT_COPY = (new Action(EditorActions.Copy, "MENU_EDIT_COPY"c, "edit-copy"c, KeyCode.KEY_C, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Control).disableByDefault();
 const Action ACTION_EDIT_PASTE = (new Action(EditorActions.Paste, "MENU_EDIT_PASTE"c, "edit-paste"c, KeyCode.KEY_V, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Shift).disableByDefault();
 const Action ACTION_EDIT_CUT = (new Action(EditorActions.Cut, "MENU_EDIT_CUT"c, "edit-cut"c, KeyCode.KEY_X, KeyFlag.Control)).addAccelerator(KeyCode.DEL, KeyFlag.Shift).disableByDefault();
diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d
index 78b8b76..001e79c 100644
--- a/src/dlangide/ui/dsourceedit.d
+++ b/src/dlangide/ui/dsourceedit.d
@@ -29,12 +29,15 @@ class DSourceEdit : SourceEdit {
         onThemeChanged();
         //setTokenHightlightColor(TokenCategory.Identifier, 0x206000);  // no colors
 		MenuItem editPopupItem = new MenuItem(null);
-		editPopupItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_GET_COMPLETIONS, ACTION_GO_TO_DEFINITION);
+		editPopupItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_EDIT_UNDO, 
+                          ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_GET_COMPLETIONS, 
+                          ACTION_GO_TO_DEFINITION, ACTION_DEBUG_TOGGLE_BREAKPOINT);
         //ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS
         popupMenu = editPopupItem;
         showIcons = true;
         showFolding = true;
 	}
+
 	this() {
 		this("SRCEDIT");
 	}
@@ -87,6 +90,26 @@ class DSourceEdit : SourceEdit {
         return filename.endsWith(".d") || filename.endsWith(".dd") || filename.endsWith(".dh") || filename.endsWith(".ddoc");
     }
 
+    override protected MenuItem getLeftPaneIconsPopupMenu(int line) {
+        MenuItem menu = super.getLeftPaneIconsPopupMenu(line);
+        if (isDSourceFile) {
+            Action action = ACTION_DEBUG_TOGGLE_BREAKPOINT.clone();
+            action.longParam = line;
+            action.objectParam = this;
+            menu.add(action);
+            action = ACTION_DEBUG_ENABLE_BREAKPOINT.clone();
+            action.longParam = line;
+            action.objectParam = this;
+            menu.add(action);
+            action = ACTION_DEBUG_DISABLE_BREAKPOINT.clone();
+            action.longParam = line;
+            action.objectParam = this;
+            menu.add(action);
+        }
+        return menu;
+    }
+
+
     void setSyntaxSupport() {
         if (isDSourceFile) {
             content.syntaxSupport = new SimpleDSyntaxSupport(filename);
@@ -149,6 +172,11 @@ class DSourceEdit : SourceEdit {
                 case IDEActions.InsertCompletion:
                     insertCompletion(a.label);
                     return true;
+                case IDEActions.DebugToggleBreakpoint:
+                case IDEActions.DebugEnableBreakpoint:
+                case IDEActions.DebugDisableBreakpoint:
+                    handleBreakpointAction(a);
+                    return true;
                 default:
                     break;
             }
@@ -156,11 +184,33 @@ class DSourceEdit : SourceEdit {
         return super.handleAction(a);
     }
 
+    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);
+                else
+                    content.lineIcons.add(new LineIcon(LineIconType.breakpoint, line));
+                break;
+            case IDEActions.DebugEnableBreakpoint:
+                break;
+            case IDEActions.DebugDisableBreakpoint:
+                break;
+            default:
+                break;
+        }
+    }
+
 	/// override to handle specific actions state (e.g. change enabled state for supported actions)
 	override bool handleActionStateRequest(const Action a) {
 		switch (a.id) {
 			case IDEActions.GoToDefinition:
 			case IDEActions.GetCompletionSuggestions:
+            case IDEActions.DebugToggleBreakpoint:
+            case IDEActions.DebugEnableBreakpoint:
+            case IDEActions.DebugDisableBreakpoint:
                 if (isDSourceFile)
                     a.state = ACTION_STATE_ENABLED;
                 else
diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d
index beecde7..3d45b45 100644
--- a/src/dlangide/ui/frame.d
+++ b/src/dlangide/ui/frame.d
@@ -575,7 +575,9 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
 
         MenuItem debugItem = new MenuItem(new Action(23, "MENU_DEBUG"));
         debugItem.add(ACTION_DEBUG_START, ACTION_DEBUG_START_NO_DEBUG, 
-                      ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE);
+                      ACTION_DEBUG_CONTINUE, ACTION_DEBUG_STOP, ACTION_DEBUG_PAUSE,
+                      ACTION_DEBUG_TOGGLE_BREAKPOINT, ACTION_DEBUG_ENABLE_BREAKPOINT, ACTION_DEBUG_DISABLE_BREAKPOINT
+                      );
 
 
 		MenuItem windowItem = new MenuItem(new Action(3, "MENU_WINDOW"c));
diff --git a/views/res/i18n/en.ini b/views/res/i18n/en.ini
index f21f46f..c4d505c 100644
--- a/views/res/i18n/en.ini
+++ b/views/res/i18n/en.ini
@@ -43,6 +43,11 @@ MENU_DEBUG_START_NO_DEBUGGING=Start Without Debugging
 MENU_DEBUG_CONTINUE=Continue Debugging
 MENU_DEBUG_STOP=Stop
 MENU_DEBUG_PAUSE=Pause
+
+MENU_DEBUG_BREAKPOINT_TOGGLE=Toggle breakpoint
+MENU_DEBUG_BREAKPOINT_ENABLE=Enable breakpoint
+MENU_DEBUG_BREAKPOINT_DISABLE=Disable breakpoint
+
 MENU_VIEW=&VIEW
 MENU_VIEW_LANGUAGE=Interface &Language
 MENU_VIEW_LANGUAGE_EN=English