Skip to content

Commit f777347

Browse files
committed
Show relative termination time in Launch History tooltip
Display the relative termination time (for example, "a moment ago", "5 mins ago", or "1 hour ago") in launch history tooltips using the stored termination timestamp. This provides a quick indication of when a launch was last terminated
1 parent e3ca8be commit f777347

5 files changed

Lines changed: 219 additions & 5 deletions

File tree

debug/org.eclipse.debug.core/core/org/eclipse/debug/core/Launch.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2000, 2018 IBM Corporation and others.
2+
* Copyright (c) 2000, 2026 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -25,15 +25,19 @@
2525
import java.util.concurrent.locks.ReadWriteLock;
2626
import java.util.concurrent.locks.ReentrantReadWriteLock;
2727

28+
import org.eclipse.core.runtime.CoreException;
2829
import org.eclipse.core.runtime.IStatus;
2930
import org.eclipse.core.runtime.MultiStatus;
3031
import org.eclipse.core.runtime.PlatformObject;
32+
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
33+
import org.eclipse.core.runtime.preferences.InstanceScope;
3134
import org.eclipse.debug.core.model.IDebugTarget;
3235
import org.eclipse.debug.core.model.IDisconnect;
3336
import org.eclipse.debug.core.model.IProcess;
3437
import org.eclipse.debug.core.model.ISourceLocator;
3538
import org.eclipse.debug.internal.core.DebugCoreMessages;
3639
import org.eclipse.debug.internal.core.LaunchManager;
40+
import org.osgi.service.prefs.BackingStoreException;
3741

3842
/**
3943
* A launch is the result of launching a debug session
@@ -475,7 +479,24 @@ protected void fireChanged() {
475479
* properly created/initialized.
476480
*/
477481
protected void fireTerminate() {
478-
setAttribute(DebugPlugin.ATTR_TERMINATE_TIMESTAMP, Long.toString(System.currentTimeMillis()));
482+
String timeStamp = Long.toString(System.currentTimeMillis());
483+
setAttribute(DebugPlugin.ATTR_TERMINATE_TIMESTAMP, timeStamp);
484+
ILaunchConfiguration launchConfig = getLaunchConfiguration();
485+
if (launchConfig != null) {
486+
try {
487+
if (launchConfig.isLocal()) {
488+
ILaunchConfigurationWorkingCopy launchCopy = launchConfig.getWorkingCopy();
489+
launchCopy.setAttribute(DebugPlugin.ATTR_TERMINATE_TIMESTAMP, timeStamp);
490+
launchCopy.doSave();
491+
} else {
492+
IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(DebugPlugin.getUniqueIdentifier());
493+
prefs.put(launchConfig.getName(), timeStamp);
494+
prefs.flush();
495+
}
496+
} catch (CoreException | BackingStoreException e) {
497+
DebugPlugin.log(e);
498+
}
499+
}
479500
if (!fSuppressChange) {
480501
((LaunchManager)getLaunchManager()).fireUpdate(this, LaunchManager.TERMINATE);
481502
((LaunchManager)getLaunchManager()).fireUpdate(new ILaunch[] {this}, LaunchManager.TERMINATE);

debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchConfigurationTests.java

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2000, 2025 IBM Corporation and others.
2+
* Copyright (c) 2000, 2026 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -2039,4 +2039,105 @@ public void launchConfigurationAdded(ILaunchConfiguration configuration) {
20392039

20402040
}
20412041

2042+
/**
2043+
* Tests that terminate timestamps for shared launch configurations are
2044+
* persisted in the instance preferences rather than in the launch
2045+
* configuration itself.
2046+
*/
2047+
@Test
2048+
public void testSharedTerminateTimeStampPersistence() throws Exception {
2049+
IProject project = getProject();
2050+
ILaunchConfigurationWorkingCopy workingCopy = newConfiguration(project, "testSharedTerminateTimestamp");
2051+
Set<ILaunch> terminatedLaunches = Collections.synchronizedSet(new HashSet<>());
2052+
2053+
ILaunchesListener2 listener = new ILaunchesListener2() {
2054+
@Override
2055+
public void launchesRemoved(ILaunch[] launches) {
2056+
}
2057+
2058+
@Override
2059+
public void launchesChanged(ILaunch[] launches) {
2060+
}
2061+
2062+
@Override
2063+
public void launchesAdded(ILaunch[] launches) {
2064+
}
2065+
2066+
@Override
2067+
public void launchesTerminated(ILaunch[] launches) {
2068+
terminatedLaunches.addAll(Arrays.asList(launches));
2069+
}
2070+
};
2071+
2072+
DebugPlugin.getDefault().getLaunchManager().addLaunchListener(listener);
2073+
ILaunch launch = workingCopy.launch(ILaunchManager.DEBUG_MODE, null);
2074+
IProcess process = null;
2075+
2076+
try {
2077+
process = DebugPlugin.newProcess(launch, new MockProcess(0), "test");
2078+
waitWhile(() -> !terminatedLaunches.contains(launch), () -> "Launch termination event did not occur");
2079+
IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(DebugPlugin.getUniqueIdentifier());
2080+
String stamp = prefs.get(workingCopy.getName(), null);
2081+
assertNotNull(stamp, "Missing persisted terminate timestamp");
2082+
long timestamp = Long.parseLong(stamp);
2083+
assertTrue(timestamp <= System.currentTimeMillis(), "Terminate timestamp should be before current time");
2084+
} finally {
2085+
DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(listener);
2086+
if (launch != null) {
2087+
getLaunchManager().removeLaunch(launch);
2088+
}
2089+
if (process != null) {
2090+
process.terminate();
2091+
}
2092+
}
2093+
}
2094+
2095+
@Test
2096+
public void testLocalTerminateTimeStampPersistence() throws Exception {
2097+
ILaunchConfigurationWorkingCopy workingCopy = newConfiguration(null, "testLocalTerminateTimestamp");
2098+
Set<ILaunch> terminatedLaunches = Collections.synchronizedSet(new HashSet<>());
2099+
2100+
ILaunchesListener2 listener = new ILaunchesListener2() {
2101+
@Override
2102+
public void launchesRemoved(ILaunch[] launches) {
2103+
}
2104+
2105+
@Override
2106+
public void launchesChanged(ILaunch[] launches) {
2107+
}
2108+
2109+
@Override
2110+
public void launchesAdded(ILaunch[] launches) {
2111+
}
2112+
2113+
@Override
2114+
public void launchesTerminated(ILaunch[] launches) {
2115+
terminatedLaunches.addAll(Arrays.asList(launches));
2116+
}
2117+
};
2118+
DebugPlugin.getDefault().getLaunchManager().addLaunchListener(listener);
2119+
ILaunch launch = workingCopy.launch(ILaunchManager.DEBUG_MODE, null);
2120+
IProcess process = null;
2121+
2122+
try {
2123+
process = DebugPlugin.newProcess(launch, new MockProcess(0), "test");
2124+
waitWhile(() -> !terminatedLaunches.contains(launch), () -> "Launch termination event did not occur");
2125+
ILaunchConfiguration config = launch.getLaunchConfiguration();
2126+
assertNotNull(config);
2127+
assertTrue(config.isLocal());
2128+
String timeStamp = config.getAttribute(DebugPlugin.ATTR_TERMINATE_TIMESTAMP, (String) null);
2129+
assertNotNull(timeStamp, "Terminate timestamp was not persisted");
2130+
long timestamp = Long.parseLong(timeStamp);
2131+
assertTrue(timestamp <= System.currentTimeMillis(), "Terminate timestamp should be before current time");
2132+
} finally {
2133+
DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(listener);
2134+
if (launch != null) {
2135+
getLaunchManager().removeLaunch(launch);
2136+
}
2137+
if (process != null) {
2138+
process.terminate();
2139+
}
2140+
}
2141+
}
2142+
20422143
}

debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/actions/ActionMessages.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,5 +261,12 @@ public class ActionMessages extends NLS {
261261
public static String ExpressionPasteDialog;
262262
public static String ExpressionPasteRemember;
263263
public static String ExpressionPastePromptButton;
264+
public static String LaunchActionToolTip_Seconds;
265+
public static String LaunchActionToolTip_Minutes;
266+
public static String LaunchActionToolTip_OneMinute;
267+
public static String LaunchActionToolTip_OneHour;
268+
public static String LaunchActionToolTip_Hours;
269+
public static String LaunchActionToolTip_OneDay;
270+
public static String LaunchActionToolTip_Days;
264271

265272
}

debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/actions/ActionMessages.properties

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,15 @@ ExpressionPasteSingleButton=Single Expression
245245
ExpressionPastePromptButton=Prompt
246246
ExpressionPasteTitle=Paste Multiline Expression
247247
ExpressionPasteDialog=You are pasting a multiline expression. Choose whether to paste it as a single combined expression or as separate multiple expressions.
248-
ExpressionPasteRemember=Remember my preference next time
248+
ExpressionPasteRemember=Remember my preference next time
249+
250+
LaunchActionToolTip_Seconds = a moment ago
251+
LaunchActionToolTip_OneMinute = 1 min ago
252+
LaunchActionToolTip_Minutes = {0} mins ago
253+
LaunchActionToolTip_OneHour = 1 hour ago
254+
LaunchActionToolTip_Hours = {0} hours ago
255+
LaunchActionToolTip_OneDay = 1 day ago
256+
LaunchActionToolTip_Days = {0} days ago
257+
258+
259+

debug/org.eclipse.debug.ui/ui/org/eclipse/debug/ui/actions/AbstractLaunchHistoryAction.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2000, 2016 IBM Corporation and others.
2+
* Copyright (c) 2000, 2026 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -22,6 +22,8 @@
2222

2323
import org.eclipse.core.resources.IResource;
2424
import org.eclipse.core.runtime.CoreException;
25+
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
26+
import org.eclipse.core.runtime.preferences.InstanceScope;
2527
import org.eclipse.debug.core.DebugPlugin;
2628
import org.eclipse.debug.core.ILaunch;
2729
import org.eclipse.debug.core.ILaunchConfiguration;
@@ -50,6 +52,7 @@
5052
import org.eclipse.jface.viewers.ISelection;
5153
import org.eclipse.jface.viewers.IStructuredSelection;
5254
import org.eclipse.jface.viewers.StructuredSelection;
55+
import org.eclipse.osgi.util.NLS;
5356
import org.eclipse.swt.SWT;
5457
import org.eclipse.swt.events.MenuAdapter;
5558
import org.eclipse.swt.events.MenuEvent;
@@ -353,6 +356,8 @@ protected void fillMenu(Menu menu) {
353356
LaunchAction action= new LaunchAction(launch, getMode());
354357
if (checkIfLaunchActive(launch, launches)) {
355358
action.setText(action.getText() + " \u2699"); //$NON-NLS-1$
359+
} else {
360+
addRecentLaunchTimeTooltip(launch, action);
356361
}
357362
addToMenu(menu, action, accelerator);
358363
accelerator++;
@@ -368,6 +373,8 @@ protected void fillMenu(Menu menu) {
368373
LaunchAction action= new LaunchAction(launch, getMode());
369374
if (checkIfLaunchActive(launch, launches)) {
370375
action.setText(action.getText() + " \u2699"); //$NON-NLS-1$
376+
} else {
377+
addRecentLaunchTimeTooltip(launch, action);
371378
}
372379
addToMenu(menu, action, accelerator);
373380
accelerator++;
@@ -627,4 +634,71 @@ private LaunchConfigurationManager getLaunchConfigurationManager() {
627634
protected String getLaunchGroupIdentifier() {
628635
return fLaunchGroup.getIdentifier();
629636
}
637+
638+
/**
639+
* Adds a tooltip showing how long ago the given launch was terminated if
640+
* there's a valid terminate timestamp attribute.
641+
*
642+
* @param launch launch configuration
643+
* @param launchAction launch action to update
644+
*/
645+
private void addRecentLaunchTimeTooltip(ILaunchConfiguration launch, LaunchAction launchAction) {
646+
try {
647+
String timeStamp;
648+
if (launch.isLocal()) {
649+
timeStamp = launch.getAttribute(DebugPlugin.ATTR_TERMINATE_TIMESTAMP, ""); //$NON-NLS-1$
650+
} else {
651+
IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(DebugPlugin.getUniqueIdentifier());
652+
timeStamp = prefs.get(launch.getName(), ""); //$NON-NLS-1$
653+
}
654+
655+
if (!timeStamp.isEmpty()) {
656+
long timestamp = Long.parseLong(timeStamp);
657+
launchAction.setToolTipText(getRelativeTime(timestamp));
658+
}
659+
} catch (CoreException | NumberFormatException e) {
660+
DebugUIPlugin.log(e);
661+
}
662+
}
663+
664+
/**
665+
* Returns a human-readable relative time string for the given timestamp.
666+
*
667+
* @param timestamp timestamp in milliseconds since the epoch
668+
* @return relative time string
669+
*/
670+
private String getRelativeTime(long timestamp) {
671+
long diffMillis = Math.max(0L, System.currentTimeMillis() - timestamp);
672+
long seconds = diffMillis / 1000;
673+
674+
if (seconds < 60) {
675+
return ActionMessages.LaunchActionToolTip_Seconds;
676+
}
677+
678+
long minutes = seconds / 60;
679+
680+
if (minutes == 1) {
681+
return ActionMessages.LaunchActionToolTip_OneMinute;
682+
}
683+
if (minutes < 60) {
684+
return NLS.bind(ActionMessages.LaunchActionToolTip_Minutes, minutes);
685+
}
686+
687+
long hours = minutes / 60;
688+
689+
if (hours == 1) {
690+
return ActionMessages.LaunchActionToolTip_OneHour;
691+
}
692+
if (hours < 24) {
693+
return NLS.bind(ActionMessages.LaunchActionToolTip_Hours, hours);
694+
}
695+
696+
long days = hours / 24;
697+
698+
if (days == 1) {
699+
return ActionMessages.LaunchActionToolTip_OneDay;
700+
}
701+
return NLS.bind(ActionMessages.LaunchActionToolTip_Days, days);
702+
}
703+
630704
}

0 commit comments

Comments
 (0)