Bug 507546 - Show variable values inline

Uses code mining to render extra information at end of line.
Lookup for information that's worth being shown relies on a
TextPostion->IVariable adapter being available and returning a value.

Change-Id: I6cbadd48ff6a2888cef047fac5128cb58e7ade3a
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.debug/+/190735
Tested-by: Mickael Istria <mistria@redhat.com>
Reviewed-by: Mickael Istria <mistria@redhat.com>
diff --git a/org.eclipse.debug.ui/META-INF/MANIFEST.MF b/org.eclipse.debug.ui/META-INF/MANIFEST.MF
index ddea511..e7aad2c 100644
--- a/org.eclipse.debug.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.debug.ui/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.debug.ui; singleton:=true
-Bundle-Version: 3.15.300.qualifier
+Bundle-Version: 3.16.0.qualifier
 Bundle-Activator: org.eclipse.debug.internal.ui.DebugUIPlugin
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
diff --git a/org.eclipse.debug.ui/plugin.xml b/org.eclipse.debug.ui/plugin.xml
index 2d052d8..fc82676 100644
--- a/org.eclipse.debug.ui/plugin.xml
+++ b/org.eclipse.debug.ui/plugin.xml
@@ -3392,4 +3392,11 @@
             icon="icons/full/obj16/launchConfiguration.png">
       </icon>
    </extension>
+   <extension
+         point="org.eclipse.ui.workbench.texteditor.codeMiningProviders">
+      <codeMiningProvider
+            class="org.eclipse.debug.internal.ui.codemining.DebugValueCodeMiningProvider"
+            id="org.eclipse.debug.ui.codeMiningProvider1">
+      </codeMiningProvider>
+   </extension>
 </plugin>
diff --git a/org.eclipse.debug.ui/pom.xml b/org.eclipse.debug.ui/pom.xml
index 082dee7..6fe008b 100644
--- a/org.eclipse.debug.ui/pom.xml
+++ b/org.eclipse.debug.ui/pom.xml
@@ -19,7 +19,7 @@
   </parent>
   <groupId>org.eclipse.debug</groupId>
   <artifactId>org.eclipse.debug.ui</artifactId>
-  <version>3.15.300-SNAPSHOT</version>
+  <version>3.16.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
   <properties>
     <code.ignoredWarnings>-warn:+resource,-deprecation,unavoidableGenericProblems</code.ignoredWarnings>
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/codemining/DebugValueCodeMining.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/codemining/DebugValueCodeMining.java
new file mode 100644
index 0000000..b535535
--- /dev/null
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/codemining/DebugValueCodeMining.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Red Hat, Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.debug.internal.ui.codemining;
+
+import java.util.function.Consumer;
+
+import org.eclipse.debug.core.model.IVariable;
+import org.eclipse.debug.internal.ui.DebugUIPlugin;
+import org.eclipse.debug.internal.ui.views.variables.VariablesView;
+import org.eclipse.debug.ui.IDebugUIConstants;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.codemining.ICodeMiningProvider;
+import org.eclipse.jface.text.codemining.LineEndCodeMining;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+
+public class DebugValueCodeMining extends LineEndCodeMining {
+
+	private IVariable variable;
+
+	protected DebugValueCodeMining(IDocument document, int line, IVariable variable, ICodeMiningProvider provider)
+			throws BadLocationException {
+		super(document, line, provider);
+		this.variable = variable;
+		setLabel(DebugUIPlugin.getModelPresentation().getText(variable));
+	}
+
+	@Override
+	public Consumer<MouseEvent> getAction() {
+		return e -> openVariableInVariablesView(variable);
+	}
+
+	private static void openVariableInVariablesView(IVariable variable) {
+
+		VariablesView view;
+		try {
+			view = (VariablesView) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
+					.showView(IDebugUIConstants.ID_VARIABLE_VIEW);
+			view.getViewer().setSelection(new StructuredSelection(variable));
+		} catch (PartInitException e) {
+			DebugUIPlugin.log(e);
+		}
+	}
+
+	@Override
+	public boolean isResolved() {
+		return true;
+	}
+
+}
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/codemining/DebugValueCodeMiningProvider.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/codemining/DebugValueCodeMiningProvider.java
new file mode 100644
index 0000000..b960305
--- /dev/null
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/codemining/DebugValueCodeMiningProvider.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Red Hat, Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.debug.internal.ui.codemining;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.core.runtime.Adapters;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.IDebugEventSetListener;
+import org.eclipse.debug.core.model.IVariable;
+import org.eclipse.debug.internal.ui.DebugUIPlugin;
+import org.eclipse.debug.ui.IDebugUIConstants;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider;
+import org.eclipse.jface.text.codemining.ICodeMining;
+import org.eclipse.jface.text.source.ISourceViewerExtension5;
+
+public class DebugValueCodeMiningProvider extends AbstractCodeMiningProvider {
+
+	private boolean alreadyListening;
+	private IDebugEventSetListener listener;
+
+	@Override
+	public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer,
+			IProgressMonitor monitor) {
+		if (!isEnabled()) {
+			return CompletableFuture.completedFuture(List.of());
+		}
+		final IDocument document = viewer.getDocument();
+		if (viewer instanceof ISourceViewerExtension5 && !alreadyListening) {
+			alreadyListening = true;
+			listener = e -> ((ISourceViewerExtension5) viewer).updateCodeMinings();
+			DebugPlugin.getDefault().addDebugEventListener(listener);
+		}
+		return CompletableFuture.supplyAsync(() -> {
+			List<DebugValueCodeMining> res = new ArrayList<>();
+			for (int line = 0; line < document.getNumberOfLines(); line++) {
+				LinkedHashMap<String, IVariable> variablesOnLine = new LinkedHashMap<>();
+				try {
+					IRegion lineInfo = document.getLineInformation(line);
+					for (int offsetInLine = 0; offsetInLine < lineInfo.getLength(); offsetInLine++) {
+						IVariable variableAtOffset = Adapters.adapt(
+								new TextSelection(document, lineInfo.getOffset() + offsetInLine, 0), IVariable.class);
+						if (variableAtOffset != null) {
+							try {
+								variablesOnLine.putIfAbsent(variableAtOffset.getName(), variableAtOffset);
+								offsetInLine += variableAtOffset.getName().length();
+							} catch (DebugException ex) {
+								DebugUIPlugin.log(ex);
+							}
+						}
+					}
+					for (IVariable variableOnLine : variablesOnLine.values()) {
+						res.add(new DebugValueCodeMining(document, line, variableOnLine, this));
+					}
+				} catch (BadLocationException e) {
+					DebugUIPlugin.log(e);
+				}
+			}
+			return res;
+		});
+	}
+
+	private boolean isEnabled() {
+		return DebugUIPlugin.getDefault().getPreferenceStore().getBoolean(IDebugUIConstants.PREF_SHOW_VARIABLES_INLINE);
+	}
+
+	@Override
+	public void dispose() {
+		DebugPlugin.getDefault().removeDebugEventListener(listener);
+	}
+}
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencePage.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencePage.java
index 129c1a3..2f740d0 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencePage.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencePage.java
@@ -50,6 +50,8 @@
 	@Override
 	protected void createFieldEditors() {
 		addField(new BooleanFieldEditor(IDebugUIConstants.PREF_REUSE_EDITOR, DebugPreferencesMessages.DebugPreferencePage_2, SWT.NONE, getFieldEditorParent()));
+		addField(new BooleanFieldEditor(IDebugUIConstants.PREF_SHOW_VARIABLES_INLINE,
+				DebugPreferencesMessages.DebugPreferencePage_showValuesInline, SWT.NONE, getFieldEditorParent()));
 
 		SWTFactory.createHorizontalSpacer(getFieldEditorParent(), 2);
 
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.java
index 72b5771..bc2d6e4 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.java
@@ -59,6 +59,7 @@
 	public static String DebugPreferencePage_25;
 	public static String DebugPreferencePage_26;
 	public static String DebugPreferencePage_27;
+	public static String DebugPreferencePage_showValuesInline;
 
 	public static String DefaultLaunchConfigurationsPropertiesPage_0;
 
@@ -215,4 +216,5 @@
 
 	public static String RunDebugPropertiesPage_0;
 
+
 }
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.properties b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.properties
index a2a56c2..6cb77f4 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.properties
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/preferences/DebugPreferencesMessages.properties
@@ -52,6 +52,7 @@
 DebugPreferencePage_31=&Prompt for confirmation when removing all triggerpoints
 DebugPreferencePage_32=&Prompt for confirmation when disabling all breakpoints
 DebugPreferencePage_5=Prompt for confirmation when deleting all e&xpressions
+DebugPreferencePage_showValuesInline=Show debug values &inline on text editors
 
 LaunchingPreferencePage_1=&Build (if required) before launching
 LaunchingPreferencePage_2=Save required dirty editors before launching
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/IDebugUIConstants.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/IDebugUIConstants.java
index e30a6fc..208e033 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/IDebugUIConstants.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/IDebugUIConstants.java
@@ -1280,6 +1280,13 @@
 	String PREF_DETAIL_PANE_FONT= "org.eclipse.debug.ui.DetailPaneFont"; //$NON-NLS-1$
 
 	/**
+	 * Preference to enable variable view inline on text editors.
+	 *
+	 * @since 3.16
+	 */
+	String PREF_SHOW_VARIABLES_INLINE = "org.eclipse.debug.ui.ShowVariableInline"; //$NON-NLS-1$
+
+	/**
 	 * Instruction pointer image for editor ruler for the currently executing
 	 * instruction in the top stack frame.
 	 *
@@ -1347,4 +1354,5 @@
 	 * @since 3.8
 	 */
 	String COLUMN_ID_VARIABLE_VALUE_TYPE = COLUMN_PRESENTATION_ID_VARIABLE + ".COL_VALUE_TYPE"; //$NON-NLS-1$
+
 }