diff --git a/debug/org.eclipse.debug.tests/META-INF/MANIFEST.MF b/debug/org.eclipse.debug.tests/META-INF/MANIFEST.MF index 9423ff5b0a1..7016e231f40 100644 --- a/debug/org.eclipse.debug.tests/META-INF/MANIFEST.MF +++ b/debug/org.eclipse.debug.tests/META-INF/MANIFEST.MF @@ -2,17 +2,17 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.debug.tests;singleton:=true -Bundle-Version: 3.15.300.qualifier +Bundle-Version: 3.15.400.qualifier Bundle-Localization: plugin -Require-Bundle: org.eclipse.ui;bundle-version="[3.6.0,4.0.0)", - org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", - org.eclipse.debug.ui;bundle-version="[3.10.0,4.0.0)", - org.eclipse.core.filesystem;bundle-version="[1.3.0,2.0.0)", - org.eclipse.test.performance;bundle-version="3.6.0", - org.eclipse.ui.externaltools;bundle-version="[3.3.0,4.0.0)", - org.eclipse.ui.console;bundle-version="[3.7.0,4.0.0)", - org.eclipse.jface.text;bundle-version="[3.5.0,4.0.0)", - org.eclipse.ui.workbench.texteditor;bundle-version="[3.15.100,4.0.0)" +Require-Bundle: org.eclipse.ui;bundle-version="[3.208.0,4.0.0)", + org.eclipse.core.runtime;bundle-version="[3.34.0,4.0.0)", + org.eclipse.debug.ui;bundle-version="[3.21.0,4.0.0)", + org.eclipse.core.filesystem;bundle-version="[1.11.0,2.0.0)", + org.eclipse.test.performance;bundle-version="[3.21.0,4.0.0)", + org.eclipse.ui.externaltools;bundle-version="[3.7.0,4.0.0)", + org.eclipse.ui.console;bundle-version="[3.17.0,4.0.0)", + org.eclipse.jface.text;bundle-version="[3.30.0,4.0.0)", + org.eclipse.ui.workbench.texteditor;bundle-version="[3.20.0,4.0.0)" Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-21 Bundle-Vendor: %providerName diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java index df91f40b224..52be8447f02 100644 --- a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java @@ -19,6 +19,7 @@ import org.eclipse.debug.tests.breakpoint.BreakpointTests; import org.eclipse.debug.tests.breakpoint.SerialExecutorTest; import org.eclipse.debug.tests.console.ConsoleDocumentAdapterTests; +import org.eclipse.debug.tests.console.ConsoleShowHideTests; import org.eclipse.debug.tests.console.ConsoleManagerTests; import org.eclipse.debug.tests.console.ConsoleTests; import org.eclipse.debug.tests.console.FileLinkTests; @@ -117,6 +118,7 @@ // Console view ConsoleDocumentAdapterTests.class, // + ConsoleShowHideTests.class, // ConsoleManagerTests.class, // ConsoleTests.class, // IOConsoleTests.class, // diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleManagerTests.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleManagerTests.java index 61e7dd4c76f..b241684aa61 100644 --- a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleManagerTests.java +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleManagerTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2017 Andrey Loskutov and others. + * Copyright (c) 2016, 2026 Andrey Loskutov and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -21,14 +21,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.debug.tests.DebugTestExtension; import org.eclipse.debug.tests.TestUtil; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.util.IPropertyChangeListener; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchPage; @@ -36,10 +31,7 @@ import org.eclipse.ui.console.ConsolePlugin; import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleManager; -import org.eclipse.ui.console.IConsoleView; import org.eclipse.ui.internal.console.ConsoleManager; -import org.eclipse.ui.part.IPageBookViewPage; -import org.eclipse.ui.part.MessagePage; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -47,7 +39,8 @@ import org.junit.jupiter.api.extension.ExtendWith; /** - * Tests console manager + * Tests console manager behavior when multiple consoles are shown at the same + * time. */ @ExtendWith(DebugTestExtension.class) public class ConsoleManagerTests { @@ -58,6 +51,8 @@ public class ConsoleManagerTests { private CountDownLatch latch; private ConsoleMock[] consoles; ConsoleMock firstConsole; + private IViewPart consoleView; + private IWorkbenchPage activePage; @BeforeEach public void setUp(TestInfo testInfo) throws Exception { @@ -66,7 +61,7 @@ public void setUp(TestInfo testInfo) throws Exception { latch = new CountDownLatch(count); executorService = Executors.newFixedThreadPool(count); manager = ConsolePlugin.getDefault().getConsoleManager(); - IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); TestUtil.processUIEvents(100); consoles = new ConsoleMock[count]; for (int i = 0; i < count; i++) { @@ -76,7 +71,7 @@ public void setUp(TestInfo testInfo) throws Exception { // register consoles (this does *not* show anything) manager.addConsoles(consoles); - IViewPart consoleView = activePage.showView("org.eclipse.ui.console.ConsoleView"); //$NON-NLS-1$ + consoleView = activePage.showView("org.eclipse.ui.console.ConsoleView"); activePage.activate(consoleView); TestUtil.processUIEvents(100); @@ -96,6 +91,7 @@ public void tearDown() throws Exception { executorService.shutdownNow(); manager.removeConsoles(consoles); manager.removeConsoles(new ConsoleMock[] { firstConsole }); + activePage.hideView(consoleView); TestUtil.processUIEvents(100); } @@ -168,73 +164,4 @@ private void showConsole(final ConsoleMock console, String testName) { }); } - /** - * Dummy console page showing mock number and counting the numbers its - * control was shown in the console view. - */ - static final class ConsoleMock implements IConsole { - MessagePage page; - final AtomicInteger showCalled; - final int number; - final static AtomicInteger allShownConsoles = new AtomicInteger(); - - public ConsoleMock(int number) { - this.number = number; - showCalled = new AtomicInteger(); - } - - @Override - public void removePropertyChangeListener(IPropertyChangeListener listener) { - } - - @Override - public String getType() { - return null; - } - - @Override - public String getName() { - return toString(); - } - - @Override - public ImageDescriptor getImageDescriptor() { - return null; - } - - @Override - public void addPropertyChangeListener(IPropertyChangeListener listener) { - } - - /** - * Just a page showing the mock console name - */ - @Override - public IPageBookViewPage createPage(IConsoleView view) { - page = new MessagePage() { - @Override - public void createControl(Composite parent) { - super.createControl(parent); - // This listener is get called if the page is really shown - // in the console view - getControl().addListener(SWT.Show, event -> { - int count = showCalled.incrementAndGet(); - if (count == 1) { - count = allShownConsoles.incrementAndGet(); - System.out.println("Shown: " + ConsoleMock.this + ", overall: " + count); //$NON-NLS-1$ //$NON-NLS-2$ - } - }); - } - - }; - page.setMessage(toString()); - return page; - } - - @Override - public String toString() { - return "mock #" + number; //$NON-NLS-1$ - } - } - } diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleMock.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleMock.java new file mode 100644 index 00000000000..b5b866504a1 --- /dev/null +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleMock.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2026 Andrey Loskutov 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 + * + * Contributors: + * Andrey Loskutov - initial API and implementation + *******************************************************************************/ +package org.eclipse.debug.tests.console; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.console.IConsole; +import org.eclipse.ui.console.IConsoleView; +import org.eclipse.ui.part.IPageBookViewPage; +import org.eclipse.ui.part.MessagePage; + +/** + * Dummy console page showing mock number and counting the numbers its + * control was shown in the console view. + */ +final class ConsoleMock implements IConsole { + MessagePage page; + final AtomicInteger showCalled; + final AtomicInteger pageShownCalled; + final AtomicInteger pageHiddenCalled; + final int number; + final static AtomicInteger allShownConsoles = new AtomicInteger(); + + public ConsoleMock(int number) { + this.number = number; + showCalled = new AtomicInteger(); + pageShownCalled = new AtomicInteger(); + pageHiddenCalled = new AtomicInteger(); + } + + @Override + public void pageShown() { + pageShownCalled.incrementAndGet(); + } + + @Override + public void pageHidden() { + pageHiddenCalled.incrementAndGet(); + } + + @Override + public void removePropertyChangeListener(IPropertyChangeListener listener) { + } + + @Override + public String getType() { + return null; + } + + @Override + public String getName() { + return toString(); + } + + @Override + public ImageDescriptor getImageDescriptor() { + return null; + } + + @Override + public void addPropertyChangeListener(IPropertyChangeListener listener) { + } + + /** + * Just a page showing the mock console name + */ + @Override + public IPageBookViewPage createPage(IConsoleView view) { + page = new MessagePage() { + @Override + public void createControl(Composite parent) { + super.createControl(parent); + // This listener is get called if the page is really shown + // in the console view + getControl().addListener(SWT.Show, event -> { + int count = showCalled.incrementAndGet(); + if (count == 1) { + count = allShownConsoles.incrementAndGet(); + System.out.println("Shown: " + ConsoleMock.this + ", overall: " + count); //$NON-NLS-1$ //$NON-NLS-2$ + } + }); + } + + }; + page.setMessage(toString()); + return page; + } + + @Override + public String toString() { + return "mock #" + number; //$NON-NLS-1$ + } +} \ No newline at end of file diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleShowHideTests.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleShowHideTests.java new file mode 100644 index 00000000000..e60bd9a95b6 --- /dev/null +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ConsoleShowHideTests.java @@ -0,0 +1,283 @@ +/******************************************************************************* + * Copyright (c) 2026 Andrey Loskutov 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 + * + * Contributors: + * Andrey Loskutov - initial API and implementation + *******************************************************************************/ +package org.eclipse.debug.tests.console; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.eclipse.debug.tests.DebugTestExtension; +import org.eclipse.debug.tests.TestUtil; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.console.ConsolePlugin; +import org.eclipse.ui.console.IConsole; +import org.eclipse.ui.console.IConsoleManager; +import org.eclipse.ui.internal.console.ConsoleManager; +import org.eclipse.ui.internal.console.ConsoleView; +import org.eclipse.ui.internal.console.ConsoleViewConsoleFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Tests console manager's show/hide behavior when multiple consoles are shown + * in the console view. + */ +@ExtendWith(DebugTestExtension.class) +public class ConsoleShowHideTests { + + private IConsoleManager manager; + private int count; + private ConsoleMock[] consoles; + private ConsoleView consoleView; + private ConsoleView consoleView2; + private IWorkbenchPage activePage; + + @BeforeEach + public void setUp() throws Exception { + assertNotNull(Display.getCurrent(), "Must run in UI thread, but was in: " + Thread.currentThread().getName()); + count = 3; + manager = ConsolePlugin.getDefault().getConsoleManager(); + activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + TestUtil.processUIEvents(100); + consoles = new ConsoleMock[count]; + for (int i = 0; i < count; i++) { + consoles[i] = new ConsoleMock(i + 1); + } + consoleView = (ConsoleView) activePage.showView("org.eclipse.ui.console.ConsoleView"); + activePage.activate(consoleView); + TestUtil.processUIEvents(100); + } + + @AfterEach + public void tearDown() throws Exception { + manager.removeConsoles(consoles); + activePage.hideView(consoleView); + if (consoleView2 != null) { + activePage.hideView(consoleView2); + } + TestUtil.processUIEvents(100); + } + + /** + * The test triggers {@link #count} sequential calls to the + * {@link IConsoleManager#showConsoleView(IConsole)} and checks if + * {@link IConsole#pageShown()} and {@link IConsole#pageHidden()} were + * properly called for each console. + */ + @Test + public void testShowHideConsoles(TestInfo testInfo) throws Exception { + // First time adding & showing consoles should trigger pageShown for + // each console + for (ConsoleMock console : consoles) { + addConsole(console, testInfo.getDisplayName()); + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + } + + // Page hidden should be called for all but the last console + for (int i = 0; i < consoles.length; i++) { + ConsoleMock console = consoles[i]; + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + if (i == consoles.length - 1) { + // last console should not be hidden + assertEquals(0, console.pageHiddenCalled.get(), console + " was hidden unexpected number of times: " + console.pageHiddenCalled.get()); + } else { + assertEquals(1, console.pageHiddenCalled.get(), console + " was hidden unexpected number of times: " + console.pageHiddenCalled.get()); + } + } + + clearConsoleCounts(); + + // Second time showing consoles should trigger pageShown for each + // console + for (ConsoleMock console : consoles) { + showConsole(console, testInfo.getDisplayName()); + assertEquals(1, console.showCalled.get()); + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + } + + // Page hidden should be called for all consoles + for (ConsoleMock console : consoles) { + assertEquals(1, console.showCalled.get()); + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + assertEquals(1, console.pageHiddenCalled.get(), console + " was hidden unexpected number of times: " + console.pageHiddenCalled.get()); + } + + clearConsoleCounts(); + + // Last console should be shown now. + // Close consoles one by one in reverse order. + for (int i = consoles.length - 1; i >= 0; i--) { + ConsoleMock console = consoles[i]; + removeConsole(console, testInfo.getDisplayName()); + if (i == consoles.length - 1) { + // last console should not be shown again + assertEquals(0, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + } else { + // Other consoles should be shown again when the previous one is + // closed + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + } + assertEquals(1, console.pageHiddenCalled.get(), console + " was hidden unexpected number of times: " + console.pageHiddenCalled.get()); + } + } + + /** + * The test triggers {@link #count} sequential calls to the + * {@link IConsoleManager#showConsoleView(IConsole)} and checks if + * {@link IConsole#pageShown()} and {@link IConsole#pageHidden()} were + * properly called for each console, if there are two console views showing + * the same consoles. + */ + @Test + public void testShowHideConsolesWithTwoViews(TestInfo testInfo) throws Exception { + assertSame(consoleView, activePage.getActivePart()); + + ConsoleViewConsoleFactoryForTest factory = new ConsoleViewConsoleFactoryForTest(); + factory.setConsoleView(consoleView); + factory.openConsole(); + TestUtil.processUIEvents(100); + + // Second console view should be opened and active + consoleView2 = (ConsoleView) activePage.getActivePart(); + assertNotSame(consoleView, consoleView2); + + // First time adding & showing consoles should trigger pageShown for + // each console (on creation) + for (ConsoleMock console : consoles) { + addConsole(console, testInfo.getDisplayName()); + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + } + + // Page hidden should be called for all but the last console + for (int i = 0; i < consoles.length; i++) { + ConsoleMock console = consoles[i]; + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + if (i == consoles.length - 1) { + // last console should not be hidden + assertEquals(0, console.pageHiddenCalled.get(), console + " was hidden unexpected number of times: " + console.pageHiddenCalled.get()); + } else { + assertEquals(1, console.pageHiddenCalled.get(), console + " was hidden unexpected number of times: " + console.pageHiddenCalled.get()); + } + } + + // Activate first console view again + activePage.activate(consoleView); + TestUtil.processUIEvents(100); + assertSame(consoleView, activePage.getActivePart()); + + clearConsoleCounts(); + + // Second time showing consoles should trigger pageShown for each + // console + for (ConsoleMock console : consoles) { + showConsole(console, testInfo.getDisplayName()); + assertEquals(1, console.showCalled.get()); + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + } + + // Page hidden should be called for all consoles (last one should be + // hidden now, as it was shown on top) + for (ConsoleMock console : consoles) { + assertEquals(1, console.showCalled.get()); + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + assertEquals(1, console.pageHiddenCalled.get(), console + " was hidden unexpected number of times: " + console.pageHiddenCalled.get()); + } + + // Activate second console view again + activePage.activate(consoleView2); + TestUtil.processUIEvents(100); + assertSame(consoleView2, activePage.getActivePart()); + + clearConsoleCounts(); + + // Second time showing consoles should trigger pageShown for each + // console (last one should be shown on top again) + for (ConsoleMock console : consoles) { + showConsole(console, testInfo.getDisplayName()); + assertEquals(1, console.showCalled.get()); + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + } + + // Page hidden should be called for all consoles (last one should be + // hidden now, as it was shown on top) + for (ConsoleMock console : consoles) { + assertEquals(1, console.showCalled.get()); + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + assertEquals(1, console.pageHiddenCalled.get(), console + " was hidden unexpected number of times: " + console.pageHiddenCalled.get()); + } + + clearConsoleCounts(); + + // Last console should be shown now. + // Close consoles one by one in reverse order. + for (int i = consoles.length - 1; i >= 0; i--) { + ConsoleMock console = consoles[i]; + removeConsole(console, testInfo.getDisplayName()); + if (i == consoles.length - 1) { + // last console should not be shown again + assertEquals(0, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + } else { + // Other consoles should be shown again when the previous one is + // closed + assertEquals(1, console.pageShownCalled.get(), console + " was shown unexpected number of times: " + console.pageShownCalled.get()); + } + assertEquals(1, console.pageHiddenCalled.get(), console + " was hidden unexpected number of times: " + console.pageHiddenCalled.get()); + } + } + + private void clearConsoleCounts() { + for (ConsoleMock console : consoles) { + console.showCalled.set(0); + console.pageShownCalled.set(0); + console.pageHiddenCalled.set(0); + } + } + + private void addConsole(final ConsoleMock console, String testName) { + System.out.println("Requesting to add: " + console); //$NON-NLS-1$ + manager.addConsoles(new IConsole[] { console }); + TestUtil.waitForJobs(testName, ConsoleManager.CONSOLE_JOB_FAMILY, 200, 5000); + } + + private void removeConsole(final ConsoleMock console, String testName) { + System.out.println("Requesting to remove: " + console); //$NON-NLS-1$ + manager.removeConsoles(new IConsole[] { console }); + TestUtil.waitForJobs(testName, ConsoleManager.CONSOLE_JOB_FAMILY, 200, 5000); + } + + private void showConsole(final ConsoleMock console, String testName) { + System.out.println("Requesting to show: " + console); //$NON-NLS-1$ + manager.showConsoleView(console); + TestUtil.waitForJobs(testName, ConsoleManager.CONSOLE_JOB_FAMILY, 200, 5000); + } + + class ConsoleViewConsoleFactoryForTest extends ConsoleViewConsoleFactory { + + @Override + protected boolean handleAutoPin() { + // Override super to avoid dialogs about pinning consoles in the + // view, which would require user interaction and thus fail the + // test. + // No pinning required + return false; + } + } + +} diff --git a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java index 5a707283a91..69e3a9e542d 100644 --- a/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java +++ b/debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.debug.tests.TestUtil.waitWhile; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; @@ -54,6 +55,7 @@ import org.eclipse.debug.core.Launch; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.internal.ui.DebugUIPlugin; +import org.eclipse.debug.internal.ui.views.console.ConsoleMessages; import org.eclipse.debug.internal.ui.views.console.ProcessConsole; import org.eclipse.debug.tests.DebugTestExtension; import org.eclipse.debug.tests.TestUtil; @@ -63,6 +65,9 @@ import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.IPerspectiveDescriptor; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.console.ConsolePlugin; import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleConstants; @@ -70,6 +75,7 @@ import org.eclipse.ui.console.IOConsole; import org.eclipse.ui.console.IOConsoleInputStream; import org.eclipse.ui.internal.console.ConsoleManager; +import org.eclipse.ui.internal.console.ConsoleView; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -230,10 +236,10 @@ public void testInputReadJobCancel() throws Exception { final MockProcess mockProcess = new MockProcess(MockProcess.RUN_FOREVER); try { final IProcess process = mockProcess.toRuntimeProcess("testInputReadJobCancel"); - final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider()); + final ProcessConsole console = new ProcessConsole(process, new ConsoleColorProvider()); try { console.initialize(); - final Class jobFamily = org.eclipse.debug.internal.ui.views.console.ProcessConsole.class; + final Class jobFamily = ProcessConsole.class; assertThat(Job.getJobManager().find(jobFamily)).as("check input read job started").hasSizeGreaterThan(0); Job.getJobManager().cancel(jobFamily); TestUtil.waitForJobs(testInfo.getDisplayName(), ProcessConsole.class, 0, 1000); @@ -292,7 +298,7 @@ public void processTerminationTest(ILaunchConfiguration launchConfig, boolean te final AtomicBoolean terminationSignaled = new AtomicBoolean(false); final Process mockProcess = new MockProcess(null, null, terminateBeforeConsoleInitialization ? 0 : -1); final IProcess process = DebugPlugin.newProcess(new Launch(launchConfig, ILaunchManager.RUN_MODE, null), mockProcess, testInfo.getDisplayName()); - final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider()); + final ProcessConsole console = new ProcessConsole(process, new ConsoleColorProvider()); console.addPropertyChangeListener(event -> { if (event.getSource() == console && IConsoleConstants.P_CONSOLE_OUTPUT_COMPLETE.equals(event.getProperty())) { terminationSignaled.set(true); @@ -393,7 +399,7 @@ private IOConsole doConsoleOutputTest(byte[] testContent, Map la final IProcess process = mockProcess.toRuntimeProcess("Output Redirect", launchConfigAttributes); final String encoding = launchConfigAttributes != null ? (String) launchConfigAttributes.get(DebugPlugin.ATTR_CONSOLE_ENCODING) : null; final AtomicBoolean consoleFinished = new AtomicBoolean(false); - final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider(), encoding); + final ProcessConsole console = new ProcessConsole(process, new ConsoleColorProvider(), encoding); console.addPropertyChangeListener((PropertyChangeEvent event) -> { if (event.getSource() == console && IConsoleConstants.P_CONSOLE_OUTPUT_COMPLETE.equals(event.getProperty())) { consoleFinished.set(true); @@ -412,7 +418,7 @@ private IOConsole doConsoleOutputTest(byte[] testContent, Map la final IDocument doc = console.getDocument(); if (outFile != null) { - String expectedPathMsg = MessageFormat.format(org.eclipse.debug.internal.ui.views.console.ConsoleMessages.ProcessConsole_1, outFile.getAbsolutePath()); + String expectedPathMsg = MessageFormat.format(ConsoleMessages.ProcessConsole_1, outFile.getAbsolutePath()); assertEquals(expectedPathMsg, doc.get(doc.getLineOffset(0), doc.getLineLength(0)), "No or wrong output of redirect file path in console."); assertThat(console.getHyperlinks()).as("check redirect file path is linked").hasSize(1); } @@ -451,7 +457,7 @@ public void testOutput() throws Exception { launchConfigAttributes.put(DebugPlugin.ATTR_CONSOLE_ENCODING, consoleEncoding); final IProcess process = mockProcess.toRuntimeProcess("simpleOutput", launchConfigAttributes); sysout.println(lines[1]); - final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider(), consoleEncoding); + final ProcessConsole console = new ProcessConsole(process, new ConsoleColorProvider(), consoleEncoding); sysout.println(lines[2]); try { console.initialize(); @@ -507,7 +513,7 @@ public void testBinaryOutputToFile() throws Exception { launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_FILE, outFile.getCanonicalPath()); launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, false); final IProcess process = mockProcess.toRuntimeProcess("redirectBinaryOutput", launchConfigAttributes); - final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider(), consoleEncoding); + final ProcessConsole console = new ProcessConsole(process, new ConsoleColorProvider(), consoleEncoding); try { console.initialize(); @@ -561,7 +567,7 @@ public void testBinaryInputFromFile() throws Exception { launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_STDIN_FILE, inFile.getCanonicalPath()); launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, false); final IProcess process = mockProcess.toRuntimeProcess("redirectBinaryInput", launchConfigAttributes); - final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider(), consoleEncoding); + final ProcessConsole console = new ProcessConsole(process, new ConsoleColorProvider(), consoleEncoding); try { console.initialize(); mockProcess.waitFor(TestUtil.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); @@ -575,4 +581,138 @@ public void testBinaryInputFromFile() throws Exception { byte[] receivedInput = mockProcess.getReceivedInput(); assertThat(receivedInput).as("received input").isEqualTo(input); } + + /** + * Test that console name updates (elapsed time) only happen for visible + * consoles. Hidden consoles should not update their name. When a hidden + * console is brought to front, it should start updating. When the console + * view is hidden/minimized, no console should update its name. When the + * view is shown again, the visible console should resume updates. + */ + @Test + public void testConsoleNameUpdateForVisibleAndHiddenConsoles() throws Exception { + final IConsoleManager consoleManager = ConsolePlugin.getDefault().getConsoleManager(); + final IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + + // Make sure we only see exact one console view during the test, + // otherwise it may interfere with visibility and name update checks. + IPerspectiveDescriptor currentPerspective = activePage.getPerspective(); + activePage.closeAllPerspectives(false, false); + TestUtil.processUIEvents(); + activePage.setPerspective(currentPerspective); + TestUtil.processUIEvents(); + + // Create two silent mock processes (no output, run forever) + final MockProcess mockProcess1 = new MockProcess(MockProcess.RUN_FOREVER); + final MockProcess mockProcess2 = new MockProcess(MockProcess.RUN_FOREVER); + try { + final IProcess process1 = mockProcess1.toRuntimeProcess("Process1"); + final IProcess process2 = mockProcess2.toRuntimeProcess("Process2"); + process1.setAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP, Long.toString(System.currentTimeMillis())); + process2.setAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP, Long.toString(System.currentTimeMillis())); + final ProcessConsole console1 = new ProcessConsole(process1, new ConsoleColorProvider()); + final ProcessConsole console2 = new ProcessConsole(process2, new ConsoleColorProvider()); + ConsoleView consoleView = (ConsoleView) activePage.showView(IConsoleConstants.ID_CONSOLE_VIEW); + try { + // Open console view and add both consoles + activePage.activate(consoleView); + TestUtil.processUIEvents(); + + consoleManager.addConsoles(new IConsole[] { console1, console2 }); + + // Display console2 (visible) - console1 is hidden + consoleView.display(console2); + TestUtil.waitForJobs(testInfo.getDisplayName(), ConsoleManager.CONSOLE_JOB_FAMILY, 200, 10000); + + // Record initial names + String console2NameBefore = console2.getName(); + String console1NameBefore = console1.getName(); + + // Wait >1 second for the elapsed time update to trigger + TestUtil.processUIEvents(1500); + + // Visible console (console2) should have updated name + String console2NameAfter = console2.getName(); + assertNotEquals(console2NameBefore, console2NameAfter, "Visible console name should have been updated (elapsed time changed)"); + + // Hidden console (console1) should NOT have updated name + String console1NameAfter = console1.getName(); + assertEquals(console1NameBefore, console1NameAfter, "Hidden console name should not be updated"); + + // Bring hidden console1 to front (visible) - console2 becomes + // hidden + consoleView.display(console1); + TestUtil.processUIEvents(200); + + // Record names after switch + String console1NameAfterSwitch = console1.getName(); + String console2NameAfterSwitch = console2.getName(); + + // Wait >1 second for the elapsed time update + TestUtil.processUIEvents(2000); + + // Now console1 (visible) should update + String console1NameAfterWait = console1.getName(); + assertNotEquals(console1NameAfterSwitch, console1NameAfterWait, "Console brought to front should start updating its name"); + + // console2 (now hidden) should stop updating + String console2NameAfterWait = console2.getName(); + assertEquals(console2NameAfterSwitch, console2NameAfterWait, "Console moved to background should stop updating its name"); + + // Minimize the console view - neither console should update + activePage.setPartState(activePage.getReference(consoleView), IWorkbenchPage.STATE_MINIMIZED); + TestUtil.processUIEvents(200); + + // Record names after minimizing + String console1NameBeforeHide = console1.getName(); + String console2NameBeforeHide = console2.getName(); + + // Wait >1 second + TestUtil.processUIEvents(2000); + + // Neither console should update when view is minimized + assertEquals(console1NameBeforeHide, console1.getName(), "Console name should not update when console view is minimized"); + assertEquals(console2NameBeforeHide, console2.getName(), "Console name should not update when console view is minimized"); + + // Restore the console view - console1 should resume + // updating, console2 should still not update because it's + // hidden in the view + activePage.setPartState(activePage.getReference(consoleView), IWorkbenchPage.STATE_RESTORED); + activePage.activate(consoleView); + TestUtil.processUIEvents(200); + + String console1NameAfterReshow = console1.getName(); + String console2NameAfterReshow = console2.getName(); + + // Wait >1 second for update + TestUtil.processUIEvents(2000); + + // Visible console should resume updating + assertNotEquals(console1NameAfterReshow, console1.getName(), "Visible console should resume name updates after view is shown again"); + assertEquals(console2NameAfterReshow, console2.getName(), "Hidden console name should not update after view is restored"); + + console1NameAfterReshow = console1.getName(); + console2NameAfterReshow = console2.getName(); + + activePage.hideView(consoleView); + + // Wait >1 second for update + TestUtil.processUIEvents(2000); + assertEquals(console1NameAfterReshow, console1.getName(), "Console name should not update when console view is minimized"); + assertEquals(console2NameAfterReshow, console2.getName(), "Console name should not update when console view is minimized"); + + mockProcess1.destroy(); + mockProcess2.destroy(); + } finally { + activePage.hideView(consoleView); + consoleManager.removeConsoles(List.of(console1, console2).toArray(new IConsole[0])); + waitForConsoleRelatedJobs(); + console1.destroy(); + console2.destroy(); + } + } finally { + mockProcess1.destroy(); + mockProcess2.destroy(); + } + } } diff --git a/debug/org.eclipse.debug.ui/META-INF/MANIFEST.MF b/debug/org.eclipse.debug.ui/META-INF/MANIFEST.MF index b4cbecb0922..8c8a0b29b7f 100644 --- a/debug/org.eclipse.debug.ui/META-INF/MANIFEST.MF +++ b/debug/org.eclipse.debug.ui/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.debug.ui; singleton:=true -Bundle-Version: 3.20.0.qualifier +Bundle-Version: 3.21.0.qualifier Bundle-Activator: org.eclipse.debug.internal.ui.DebugUIPlugin Bundle-Vendor: %providerName Bundle-Localization: plugin @@ -79,18 +79,18 @@ Export-Package: org.eclipse.debug.internal.ui; org.eclipse.debug.ui.memory, org.eclipse.debug.ui.sourcelookup, org.eclipse.debug.ui.stringsubstitution -Require-Bundle: org.eclipse.core.variables;bundle-version="[3.2.800,4.0.0)", +Require-Bundle: org.eclipse.core.variables;bundle-version="[3.6.0,4.0.0)", org.eclipse.ui;bundle-version="[3.208.0,4.0.0)", - org.eclipse.ui.console;bundle-version="[3.13.0,4.0.0)", - org.eclipse.help;bundle-version="[3.4.0,4.0.0)", - org.eclipse.debug.core;bundle-version="[3.9.0,4.0.0)";visibility:=reexport, - org.eclipse.jface.text;bundle-version="[3.5.0,4.0.0)", - org.eclipse.ui.workbench.texteditor;bundle-version="[3.5.0,4.0.0)", - org.eclipse.ui.ide;bundle-version="[3.5.0,4.0.0)", - org.eclipse.ui.editors;bundle-version="[3.5.0,4.0.0)", - org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", - org.eclipse.core.filesystem;bundle-version="[1.2.0,2.0.0)", - org.eclipse.e4.ui.services;bundle-version="[1.3.700,2.0.0)" + org.eclipse.ui.console;bundle-version="[3.17.0,4.0.0)", + org.eclipse.help;bundle-version="[3.11.0,4.0.0)", + org.eclipse.debug.core;bundle-version="[3.23.0,4.0.0)";visibility:=reexport, + org.eclipse.jface.text;bundle-version="[3.30.0,4.0.0)", + org.eclipse.ui.workbench.texteditor;bundle-version="[3.20.0,4.0.0)", + org.eclipse.ui.ide;bundle-version="[3.23.0,4.0.0)", + org.eclipse.ui.editors;bundle-version="[3.21.0,4.0.0)", + org.eclipse.core.runtime;bundle-version="[3.34.0,4.0.0)", + org.eclipse.core.filesystem;bundle-version="[1.11.0,2.0.0)", + org.eclipse.e4.ui.services;bundle-version="[1.6.0,2.0.0)" Bundle-ActivationPolicy: lazy Import-Package: org.eclipse.ui.forms.widgets Bundle-RequiredExecutionEnvironment: JavaSE-21 diff --git a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java index e2fd398e51d..7b11e31a96b 100644 --- a/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java +++ b/debug/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java @@ -37,6 +37,9 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; @@ -116,7 +119,7 @@ public class ProcessConsole extends IOConsole implements IConsole, IDebugEventSetListener, IPropertyChangeListener { private final IProcess fProcess; - private final List fStreamListeners = new ArrayList<>(); + private final List fStreamListeners; private final IConsoleColorProvider fColorProvider; @@ -134,10 +137,16 @@ public class ProcessConsole extends IOConsole implements IConsole, IDebugEventSe private FileOutputStream fFileOutputStream; - private boolean fAllocateConsole = true; + private boolean fAllocateConsole; private String fStdInFile; private volatile boolean fStreamsClosed; + private volatile boolean disposed; + private volatile boolean isVisible; + private volatile boolean isPeriodicNameUpdateRequired; + + private final ExecutorService consoleNameUpdateExecutor; + private final AtomicBoolean updateRunning; /** * Create process console with default encoding. @@ -158,8 +167,16 @@ public ProcessConsole(IProcess process, IConsoleColorProvider colorProvider) { */ public ProcessConsole(IProcess process, IConsoleColorProvider colorProvider, String encoding) { super(IInternalDebugCoreConstants.EMPTY_STRING, IDebugUIConstants.ID_PROCESS_CONSOLE_TYPE, null, encoding, true); + updateRunning = new AtomicBoolean(); + fStreamListeners = new ArrayList<>(); + fAllocateConsole = true; fProcess = process; fUserInput = getInputStream(); + consoleNameUpdateExecutor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "Console name updater"); //$NON-NLS-1$ + t.setDaemon(true); + return t; + }); ILaunchConfiguration configuration = process.getLaunch().getLaunchConfiguration(); String file = null; @@ -250,12 +267,11 @@ public ProcessConsole(IProcess process, IConsoleColorProvider colorProvider, Str fInput = getInputStream(); } - colorProvider.connect(fProcess, this); Color color = fColorProvider.getColor(IDebugUIConstants.ID_STANDARD_INPUT_STREAM); - if (fInput instanceof IOConsoleInputStream) { - ((IOConsoleInputStream)fInput).setColor(color); + if (fInput instanceof IOConsoleInputStream ioStream) { + ioStream.setColor(color); } IConsoleLineTracker[] lineTrackers = DebugUIPlugin.getDefault().getProcessConsoleManager().getLineTrackers(process); @@ -295,122 +311,180 @@ public IPageBookViewPage createPage(IConsoleView view) { /** * Computes and returns the current name of this console. * + * @param triggerAsyncUpdate if true and process is still running, + * triggers asynchronous update of console name every + * second to update elapsed time display in console + * name + * * @return a name for this console */ - protected String computeName() { - String label = null; + protected String computeName(boolean triggerAsyncUpdate) { + if (disposed) { + return getName(); + } IProcess process = getProcess(); - ILaunchConfiguration config = process.getLaunch().getLaunchConfiguration(); - - label = process.getAttribute(IProcess.ATTR_PROCESS_LABEL); + String label = process.getAttribute(IProcess.ATTR_PROCESS_LABEL); if (label == null) { - if (config == null) { + ILaunchConfiguration config = process.getLaunch().getLaunchConfiguration(); + if (config != null && DebugUITools.isPrivate(config)) { label = process.getLabel(); } else { - // check if PRIVATE config - if (DebugUITools.isPrivate(config)) { - label = process.getLabel(); - } else { - String type = null; - try { - type = config.getType().getName(); - } catch (CoreException e) { - } - StringBuilder buffer = new StringBuilder(); - buffer.append(config.getName()); - if (type != null) { - buffer.append(" ["); //$NON-NLS-1$ - buffer.append(type); - buffer.append("] "); //$NON-NLS-1$ - } + label = computeLabel(process, config); + } + } - Date launchTime = parseTimestamp(process.getAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP)); - Date terminateTime = parseTimestamp(process.getAttribute(DebugPlugin.ATTR_TERMINATE_TIMESTAMP)); - - String procLabel = process.getLabel(); - if (launchTime != null) { - // FIXME workaround to remove start time from process label added from jdt for - // java launches - int idx = procLabel.lastIndexOf('('); - if (idx >= 0) { - int end = procLabel.lastIndexOf(')'); - if (end > idx) { - String jdtTime = procLabel.substring(idx + 1, end); - try { - DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).parse(jdtTime); - procLabel = procLabel.substring(0, idx); - } catch (ParseException pe) { - // not a date. Label just contains parentheses - } - } - } - } + if (process.isTerminated()) { + return MessageFormat.format(ConsoleMessages.ProcessConsole_0, label); + } - DateFormat dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); - String elapsedFormat = "%d:%02d:%02d.%03d"; //$NON-NLS-1$ - if (terminateTime == null) { - // refresh every second: - DebugUIPlugin.getStandardDisplay().asyncExec( - () -> DebugUIPlugin.getStandardDisplay().timerExec(1000, () -> resetName(false))); - // pointless to update milliseconds: - elapsedFormat = "%d:%02d:%02d"; //$NON-NLS-1$ - } + // Process is still running, so trigger async update of console name every + // second to keep elapsed time updated. + if (triggerAsyncUpdate) { + triggerAsyncConsoleNameUpdate(label); + } + return label; + } - IPreferenceStore store = DebugUIPlugin.getDefault().getPreferenceStore(); - - String elapsedTimeFormat = store.getString(IDebugPreferenceConstants.CONSOLE_ELAPSED_FORMAT); - String elapsedString = "";//$NON-NLS-1$ - if (!elapsedTimeFormat.equals(DebugPreferencesMessages.ConsoleDisableElapsedTime)) { - Duration elapsedTime = Duration.between( - launchTime != null ? launchTime.toInstant() : Instant.now(), - terminateTime != null ? terminateTime.toInstant() : Instant.now()); - elapsedFormat = "elapsed " + convertElapsedFormat(elapsedTimeFormat); //$NON-NLS-1$ - elapsedString = String.format(elapsedFormat, elapsedTime.toHours(), elapsedTime.toMinutesPart(), - elapsedTime.toSecondsPart(), elapsedTime.toMillisPart()); - } + private static String computeLabel(IProcess process, ILaunchConfiguration config) { + StringBuilder buffer = new StringBuilder(); + if (config != null) { + buffer.append(config.getName()); + String type = getConfigTypeName(config); + if (type != null) { + buffer.append(" ["); //$NON-NLS-1$ + buffer.append(type); + buffer.append("] "); //$NON-NLS-1$ + } + } - if (launchTime != null && terminateTime != null) { - String launchTimeStr = dateTimeFormat.format(launchTime); - // Check if process started and terminated at same day. If so only print the - // time part of termination time and omit the date part. - LocalDateTime launchDate = LocalDateTime.ofInstant(launchTime.toInstant(), - ZoneId.systemDefault()); - LocalDateTime terminateDate = LocalDateTime.ofInstant(terminateTime.toInstant(), - ZoneId.systemDefault()); - LocalDateTime launchDay = launchDate.truncatedTo(ChronoUnit.DAYS); - LocalDateTime terminateDay = terminateDate.truncatedTo(ChronoUnit.DAYS); - String terminateTimeStr; - if (launchDay.equals(terminateDay)) { - terminateTimeStr = DateFormat.getTimeInstance(DateFormat.MEDIUM).format(terminateTime); - } else { - terminateTimeStr = dateTimeFormat.format(terminateTime); - } + Date launchTime = parseTimestamp(process.getAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP)); + Date terminateTime = parseTimestamp(process.getAttribute(DebugPlugin.ATTR_TERMINATE_TIMESTAMP)); + DateFormat dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); + String elapsedString = computeElapsedTimeLabel(launchTime, terminateTime); + String procLabel = computeProcessLabel(process, launchTime); + if (launchTime != null && terminateTime != null) { + String launchTimeStr = dateTimeFormat.format(launchTime); + String terminateTimeStr = computeTerminatedTimeLabel(launchTime, terminateTime, dateTimeFormat); + buffer.append(MessageFormat.format(ConsoleMessages.ProcessConsole_commandLabel_withStartEnd, procLabel, + launchTimeStr, terminateTimeStr, elapsedString)); + } else if (launchTime != null) { + buffer.append(MessageFormat.format(ConsoleMessages.ProcessConsole_commandLabel_withStart, procLabel, + dateTimeFormat.format(launchTime), elapsedString)); + } else if (terminateTime != null) { + buffer.append(MessageFormat.format(ConsoleMessages.ProcessConsole_commandLabel_withEnd, procLabel, + dateTimeFormat.format(terminateTime))); + } else { + buffer.append(procLabel); + } + + String pid = process.getAttribute(IProcess.ATTR_PROCESS_ID); + if (pid != null && !pid.isBlank()) { + buffer.append(" [pid: "); //$NON-NLS-1$ + buffer.append(pid); + buffer.append("]"); //$NON-NLS-1$ + } + return buffer.toString(); + } - buffer.append(MessageFormat.format(ConsoleMessages.ProcessConsole_commandLabel_withStartEnd, - procLabel, launchTimeStr, terminateTimeStr, elapsedString)); - } else if (launchTime != null) { - buffer.append(MessageFormat.format(ConsoleMessages.ProcessConsole_commandLabel_withStart, - procLabel, dateTimeFormat.format(launchTime), elapsedString)); - } else if (terminateTime != null) { - buffer.append(MessageFormat.format(ConsoleMessages.ProcessConsole_commandLabel_withEnd, - procLabel, dateTimeFormat.format(terminateTime))); - } + private static String getConfigTypeName(ILaunchConfiguration config) { + String type = null; + try { + type = config.getType().getName(); + } catch (CoreException e) { + } + return type; + } - String pid = process.getAttribute(IProcess.ATTR_PROCESS_ID); - if (pid != null && !pid.isBlank()) { - buffer.append(" [pid: "); //$NON-NLS-1$ - buffer.append(pid); - buffer.append("]"); //$NON-NLS-1$ + private static String computeProcessLabel(IProcess process, Date launchTime) { + String procLabel = process.getLabel(); + if (launchTime != null) { + // FIXME workaround to remove start time from process label added from jdt for + // java launches + int idx = procLabel.lastIndexOf('('); + if (idx >= 0) { + int end = procLabel.lastIndexOf(')'); + if (end > idx) { + String jdtTime = procLabel.substring(idx + 1, end); + try { + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).parse(jdtTime); + procLabel = procLabel.substring(0, idx); + } catch (ParseException pe) { + // not a date. Label just contains parentheses } - label = buffer.toString(); } } } + return procLabel; + } - if (process.isTerminated()) { - return MessageFormat.format(ConsoleMessages.ProcessConsole_0, label); + private void triggerAsyncConsoleNameUpdate(String newName) { + if (!canUpdateConsoleName()) { + return; } - return label; + // update console name immediately to show elapsed time right after launch and + // then start async update every second + showName(false, newName); + + if (!updateRunning.compareAndSet(false, true)) { + return; + } + consoleNameUpdateExecutor.submit(() -> { + try { + while (!disposed) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // ignore and continue to update name + } + if (!canUpdateConsoleName()) { + break; + } + showName(false, computeName(false)); + } + } finally { + updateRunning.set(false); + } + }); + } + + private boolean canUpdateConsoleName() { + return !disposed && isVisible && isPeriodicNameUpdateRequired && !fProcess.isTerminated(); + } + + private static String computeElapsedTimeLabel(Date launchTime, Date terminateTime) { + String elapsedString; + IPreferenceStore store = DebugUIPlugin.getDefault().getPreferenceStore(); + String elapsedTimeFormat = store.getString(IDebugPreferenceConstants.CONSOLE_ELAPSED_FORMAT); + if (!isElapsedTimeDisabled(elapsedTimeFormat)) { + Duration elapsedTime = Duration.between(launchTime != null ? launchTime.toInstant() : Instant.now(), + terminateTime != null ? terminateTime.toInstant() : Instant.now()); + String elapsedFormat = "elapsed " + convertElapsedFormat(elapsedTimeFormat); //$NON-NLS-1$ + elapsedString = String.format(elapsedFormat, elapsedTime.toHours(), elapsedTime.toMinutesPart(), + elapsedTime.toSecondsPart(), elapsedTime.toMillisPart()); + } else { + elapsedString = ""; //$NON-NLS-1$ + } + return elapsedString; + } + + private static boolean isElapsedTimeDisabled(String elapsedTimeFormatValue) { + return DebugPreferencesMessages.ConsoleDisableElapsedTime.equals(elapsedTimeFormatValue); + } + + private static String computeTerminatedTimeLabel(Date launchTime, Date terminateTime, DateFormat dateTimeFormat) { + // Check if process started and terminated at same day. If so only print the + // time part of termination time and omit the date part. + LocalDateTime launchDate = LocalDateTime.ofInstant(launchTime.toInstant(), ZoneId.systemDefault()); + LocalDateTime terminateDate = LocalDateTime.ofInstant(terminateTime.toInstant(), ZoneId.systemDefault()); + LocalDateTime launchDay = launchDate.truncatedTo(ChronoUnit.DAYS); + LocalDateTime terminateDay = terminateDate.truncatedTo(ChronoUnit.DAYS); + String terminateTimeStr; + if (launchDay.equals(terminateDay)) { + terminateTimeStr = DateFormat.getTimeInstance(DateFormat.MEDIUM).format(terminateTime); + } else { + terminateTimeStr = dateTimeFormat.format(terminateTime); + } + return terminateTimeStr; } /** @@ -433,9 +507,6 @@ private static Date parseTimestamp(String timestamp) { } } - /** - * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) - */ @Override public void propertyChange(PropertyChangeEvent evt) { String property = evt.getProperty(); @@ -500,6 +571,10 @@ public void propertyChange(PropertyChangeEvent evt) { setHandleControlCharacters(store.getBoolean(IDebugPreferenceConstants.CONSOLE_INTERPRET_CONTROL_CHARACTERS)); } else if (property.equals(IDebugPreferenceConstants.CONSOLE_INTERPRET_CR_AS_CONTROL_CHARACTER)) { setCarriageReturnAsControlCharacter(store.getBoolean(IDebugPreferenceConstants.CONSOLE_INTERPRET_CR_AS_CONTROL_CHARACTER)); + } else if (property.equals(IDebugPreferenceConstants.CONSOLE_ELAPSED_FORMAT)) { + String elapsedTimeFormat = store.getString(IDebugPreferenceConstants.CONSOLE_ELAPSED_FORMAT); + isPeriodicNameUpdateRequired = !isElapsedTimeDisabled(elapsedTimeFormat); + showName(false, computeName(isPeriodicNameUpdateRequired)); } } @@ -516,20 +591,16 @@ public IOConsoleOutputStream getStream(String streamIdentifier) { return null; } - /** - * @see org.eclipse.debug.ui.console.IConsole#getProcess() - */ @Override public IProcess getProcess() { return fProcess; } - /** - * @see org.eclipse.ui.console.IOConsole#dispose() - */ @Override protected void dispose() { super.dispose(); + disposed = true; + consoleNameUpdateExecutor.shutdownNow(); fColorProvider.disconnect(); DebugPlugin.getDefault().removeDebugEventListener(this); DebugUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this); @@ -583,22 +654,22 @@ private synchronized void disposeStreams() { fUserInput = null; } - /** - * @see org.eclipse.ui.console.AbstractConsole#init() - */ @Override protected void init() { super.init(); + IPreferenceStore store = DebugUIPlugin.getDefault().getPreferenceStore(); + String elapsedTimeFormat = store.getString(IDebugPreferenceConstants.CONSOLE_ELAPSED_FORMAT); + isPeriodicNameUpdateRequired = !isElapsedTimeDisabled(elapsedTimeFormat); + DebugPlugin.getDefault().addDebugEventListener(this); // computeName() after addDebugEventListener() // see https://github.com/eclipse-jdt/eclipse.jdt.debug/issues/390 - setName(computeName()); + setName(computeName(false)); if (fProcess.isTerminated()) { closeStreams(); resetName(true); DebugPlugin.getDefault().removeDebugEventListener(this); } - IPreferenceStore store = DebugUIPlugin.getDefault().getPreferenceStore(); store.addPropertyChangeListener(this); JFaceResources.getFontRegistry().addListener(this); if (store.getBoolean(IDebugPreferenceConstants.CONSOLE_WRAP)) { @@ -616,6 +687,9 @@ protected void init() { setCarriageReturnAsControlCharacter(store.getBoolean(IDebugPreferenceConstants.CONSOLE_INTERPRET_CR_AS_CONTROL_CHARACTER)); DebugUIPlugin.getStandardDisplay().asyncExec(() -> { + if (disposed) { + return; + } setFont(JFaceResources.getFont(IDebugUIConstants.PREF_CONSOLE_FONT)); setBackground(DebugUIPlugin.getPreferenceColor(IDebugPreferenceConstants.CONSOLE_BAKGROUND_COLOR)); }); @@ -642,15 +716,25 @@ public void handleDebugEvents(DebugEvent[] events) { } /** - * resets the name of this console to the original computed name + * Compute & update console name, notify listeners if content has changed. + * + * @param contentChanged whether the content of console has changed since last + * name update. */ - private synchronized void resetName(boolean changed) { - final String newName = computeName(); + private synchronized void resetName(boolean contentChanged) { + final String newName = computeName(false); + showName(contentChanged, newName); + } + + private void showName(boolean contentChanged, final String newName) { String name = getName(); if (!name.equals(newName)) { DebugUIPlugin.getStandardDisplay().execute(() -> { + if (disposed) { + return; + } setName(newName); - if (changed) { + if (contentChanged) { warnOfContentChange(); } }); @@ -664,9 +748,6 @@ private void warnOfContentChange() { ConsolePlugin.getDefault().getConsoleManager().warnOfContentChange(DebugUITools.getConsole(fProcess)); } - /** - * @see org.eclipse.debug.ui.console.IConsole#connect(org.eclipse.debug.core.model.IStreamsProxy) - */ @Override public void connect(IStreamsProxy streamsProxy) { IPreferenceStore store = DebugUIPlugin.getDefault().getPreferenceStore(); @@ -685,9 +766,6 @@ public void connect(IStreamsProxy streamsProxy) { readJob.schedule(); } - /** - * @see org.eclipse.debug.ui.console.IConsole#connect(org.eclipse.debug.core.model.IStreamMonitor, java.lang.String) - */ @Override public void connect(IStreamMonitor streamMonitor, String streamIdentifier) { connect(streamMonitor, streamIdentifier, false); @@ -716,9 +794,6 @@ private void connect(IStreamMonitor streamMonitor, String streamIdentifier, bool } } - /** - * @see org.eclipse.debug.ui.console.IConsole#addLink(org.eclipse.debug.ui.console.IConsoleHyperlink, int, int) - */ @Override public void addLink(IConsoleHyperlink link, int offset, int length) { try { @@ -728,9 +803,6 @@ public void addLink(IConsoleHyperlink link, int offset, int length) { } } - /** - * @see org.eclipse.debug.ui.console.IConsole#addLink(org.eclipse.ui.console.IHyperlink, int, int) - */ @Override public void addLink(IHyperlink link, int offset, int length) { try { @@ -740,9 +812,6 @@ public void addLink(IHyperlink link, int offset, int length) { } } - /** - * @see org.eclipse.debug.ui.console.IConsole#getRegion(org.eclipse.debug.ui.console.IConsoleHyperlink) - */ @Override public IRegion getRegion(IConsoleHyperlink link) { return super.getRegion(link); @@ -1186,4 +1255,15 @@ public static String convertElapsedFormat(String humanReadable) { return format.toString(); } -} + + @Override + public void pageShown() { + isVisible = true; + computeName(true); + } + + @Override + public void pageHidden() { + isVisible = false; + } +} \ No newline at end of file diff --git a/debug/org.eclipse.ui.console/.settings/.api_filters b/debug/org.eclipse.ui.console/.settings/.api_filters new file mode 100644 index 00000000000..d5cc8304011 --- /dev/null +++ b/debug/org.eclipse.ui.console/.settings/.api_filters @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/debug/org.eclipse.ui.console/META-INF/MANIFEST.MF b/debug/org.eclipse.ui.console/META-INF/MANIFEST.MF index d66f083c3a4..e187457d024 100644 --- a/debug/org.eclipse.ui.console/META-INF/MANIFEST.MF +++ b/debug/org.eclipse.ui.console/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.console; singleton:=true -Bundle-Version: 3.16.0.qualifier +Bundle-Version: 3.17.0.qualifier Bundle-Activator: org.eclipse.ui.console.ConsolePlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/AbstractConsole.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/AbstractConsole.java index 3539baa6a07..49f7ffcae50 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/AbstractConsole.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/AbstractConsole.java @@ -39,17 +39,17 @@ public abstract class AbstractConsole implements IConsole { /** * Console name */ - private String fName = null; + private String fName; /** * Console image descriptor */ - private ImageDescriptor fImageDescriptor = null; + private ImageDescriptor fImageDescriptor; /** * Console type identifier */ - private String fType = null; + private String fType; /** * Used to notify this console of lifecycle methods init() @@ -316,4 +316,9 @@ public String getHelpContextId() { return null; } + @Override + public String toString() { + return "Console [" + fName + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + } + } diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IConsole.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IConsole.java index 0a56b91da49..2b66f2e2235 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IConsole.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IConsole.java @@ -97,4 +97,40 @@ public interface IConsole { */ String getType(); + /** + * Notifies this console that its page has been shown in the UI. This method is + * called when this console page is shown on top of other console pages in at + * least one visible console view. + *

+ * Default implementation does nothing. + *

+ *

+ * Subclasses may override this method to be notified when the console page for + * this console is visible to user. + *

+ * + * @since 3.17 + */ + default void pageShown() { + // Subclasses may override + } + + /** + * Notifies this console that its page has been hidden in the UI. This method is + * called when this console page is not shown on top of other console pages in + * any of visible console views. + *

+ * Default implementation does nothing. + *

+ *

+ * Subclasses may override this method to be notified when the console page for + * this console is not longer visible to user. + *

+ * + * @since 3.17 + */ + default void pageHidden() { + // Subclasses may override + } + } diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IConsoleManager.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IConsoleManager.java index 2f539c36935..dce4ae1161b 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IConsoleManager.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IConsoleManager.java @@ -107,4 +107,37 @@ public interface IConsoleManager { */ void refresh(IConsole console); + /** + * Notifies that given console has been shown in the UI (either the view with + * given console on top has been shown or the console has been switched to the + * top console in at least one view). + * + *

+ * Note, there could be multiple views showing the same console, so the page + * with given console can be still hidden in some views. + *

+ * + * @param console the top console from view that has been shown + * + * @since 3.17 + */ + default void consoleShown(IConsole console) { + } + + /** + * Notifies that given console that was on top of at least one view has been + * hidden in the UI (either the console is not the top page in the view or the + * view with given console on top has been hidden). + *

+ * Note, there could be multiple views showing the same console, so the page + * with given console can be still shown in some views. + *

+ * + * @param console the console that has been hidden + * + * @since 3.17 + */ + default void consoleHidden(IConsole console) { + } + } diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsole.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsole.java index c09f2111a4c..7a03913cea2 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsole.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsole.java @@ -54,7 +54,7 @@ public class IOConsole extends TextConsole { /** * A collection of open streams connected to this console. */ - private final List openStreams = Collections.synchronizedList(new ArrayList<>()); + private final List openStreams; /** * The encoding used to for displaying console output. @@ -113,10 +113,9 @@ public IOConsole(String name, String consoleType, ImageDescriptor imageDescripto public IOConsole(String name, String consoleType, ImageDescriptor imageDescriptor, Charset charset, boolean autoLifecycle) { super(name, consoleType, imageDescriptor, autoLifecycle); this.charset = charset; - synchronized (openStreams) { - inputStream = new IOConsoleInputStream(this); - openStreams.add(inputStream); - } + openStreams = Collections.synchronizedList(new ArrayList<>()); + inputStream = new IOConsoleInputStream(this); + openStreams.add(inputStream); partitioner = new IOConsolePartitioner(this); final IDocument document = getDocument(); if (document != null) { @@ -313,7 +312,7 @@ protected void dispose() { try { closable.close(); } catch (IOException e) { - // e.printStackTrace(); + // ignore } } inputStream = null; @@ -321,8 +320,9 @@ protected void dispose() { final IDocument document = partitioner.getDocument(); partitioner.disconnect(); - document.setDocumentPartitioner(null); - + if (document != null) { + document.setDocumentPartitioner(null); + } super.dispose(); } diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsole.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsole.java index 6e3158efcfa..9c61e61dc9b 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsole.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsole.java @@ -83,28 +83,27 @@ public abstract class TextConsole extends AbstractConsole { /** * indication that the console's partitioner is not expecting more input */ - private volatile boolean fPartitionerFinished = false; + private volatile boolean fPartitionerFinished; /** * Indication that the console's pattern matcher has finished. * (all matches have been found and all listeners notified) */ - private volatile boolean fMatcherFinished = false; + private volatile boolean fMatcherFinished; /** * indication that the console output complete property has been fired */ - private boolean fCompleteFired = false; - - private volatile boolean fConsoleAutoScrollLock = true; + private boolean fCompleteFired; + private volatile boolean fConsoleAutoScrollLock; /** * Map of client defined attributes */ - private final HashMap fAttributes = new HashMap<>(); + private final HashMap fAttributes; - private final IConsoleManager fConsoleManager = ConsolePlugin.getDefault().getConsoleManager(); + private final IConsoleManager fConsoleManager; private ScopedPreferenceStore store; private IPropertyChangeListener propListener; @@ -119,6 +118,7 @@ protected void dispose() { store.removePropertyChangeListener(propListener); } } + /** * Constructs a console with the given name, image descriptor, and lifecycle * @@ -130,6 +130,9 @@ protected void dispose() { */ public TextConsole(String name, String consoleType, ImageDescriptor imageDescriptor, boolean autoLifecycle) { super(name, consoleType, imageDescriptor, autoLifecycle); + fConsoleAutoScrollLock = true; + fAttributes = new HashMap<>(); + fConsoleManager = ConsolePlugin.getDefault().getConsoleManager(); fDocument = new ConsoleDocument(); fDocument.addPositionCategory(ConsoleHyperlinkPosition.HYPER_LINK_CATEGORY); fPatternMatcher = new ConsolePatternMatcher(this); diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleManager.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleManager.java index 69ead9b2015..3e8de2d787b 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleManager.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleManager.java @@ -15,7 +15,6 @@ package org.eclipse.ui.internal.console; import java.util.ArrayList; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -36,6 +35,7 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; @@ -48,7 +48,7 @@ import org.eclipse.ui.console.IConsoleView; import org.eclipse.ui.console.IPatternMatchListener; import org.eclipse.ui.console.TextConsole; -import org.eclipse.ui.progress.UIJob; +import org.eclipse.ui.part.IPage; import org.eclipse.ui.progress.WorkbenchJob; /** @@ -62,12 +62,12 @@ public class ConsoleManager implements IConsoleManager { /** * Console listeners */ - private final ListenerList fListeners = new ListenerList<>(); + private final ListenerList fListeners; /** * List of registered consoles */ - private final List fConsoles = new ArrayList<>(10); + private final List fConsoles; // change notification constants @@ -80,60 +80,52 @@ public class ConsoleManager implements IConsoleManager { private List fConsoleFactoryExtensions; - private final List fConsoleViews = new ArrayList<>(); + private final List fConsoleViews; - private boolean fWarnQueued = false; + /** Used to trigger redrawing of console pages when links changed */ + private final RedrawJob redrawConsoleJob; - private final RepaintJob fRepaintJob = new RepaintJob(); + /** Used to show console view in active window */ + private final ShowConsoleViewJob showConsoleJob; - private class RepaintJob extends WorkbenchJob { - private final Set list = new HashSet<>(); + /** Show console change indication in all views if console is not visible */ + private final WarnAboutContentChangedJob warnAboutContentChangeJob; - public RepaintJob() { - super("schedule redraw() of viewers"); //$NON-NLS-1$ - setSystem(true); - } + public ConsoleManager() { + fListeners = new ListenerList<>(); + fConsoles = new ArrayList<>(10); + fConsoleViews = new ArrayList<>(); + redrawConsoleJob = new RedrawJob(); + showConsoleJob = new ShowConsoleViewJob(); + warnAboutContentChangeJob = new WarnAboutContentChangedJob(); + } - @Override - public boolean belongsTo(Object family) { - return family == ConsoleManager.CONSOLE_JOB_FAMILY; - } + private class RedrawJob extends AbstractConsoleJob { - void addConsole(IConsole console) { - synchronized (list) { - list.add(console); - } + public RedrawJob() { + super("Schedule console redraw"); //$NON-NLS-1$ + setSystem(true); } @Override - public IStatus runInUIThread(IProgressMonitor monitor) { - synchronized (list) { - if (list.isEmpty()) { - return Status.OK_STATUS; - } - - IWorkbenchWindow[] workbenchWindows = PlatformUI.getWorkbench().getWorkbenchWindows(); - for (IWorkbenchWindow window : workbenchWindows) { - if (window != null) { - IWorkbenchPage page = window.getActivePage(); - if (page != null) { - IViewPart part = page.findView(IConsoleConstants.ID_CONSOLE_VIEW); - if (part != null && part instanceof IConsoleView) { - ConsoleView view = (ConsoleView) part; - if (list.contains(view.getConsole())) { - Control control = view.getCurrentPage().getControl(); - if (!control.isDisposed()) { - control.redraw(); - } - } - } - + protected void workWith(IConsole console, IProgressMonitor monitor) { + synchronized (fConsoleViews) { + for (ConsoleView view : fConsoleViews) { + if (console.equals(view.getConsole())) { + IPage currentPage = view.getCurrentPage(); + if (currentPage == null) { + continue; } + Control control = currentPage.getControl(); + if (control != null && !control.isDisposed()) { + control.redraw(); + } + } + if (monitor.isCanceled()) { + return; } } - list.clear(); } - return Status.OK_STATUS; } } @@ -256,12 +248,11 @@ private void fireUpdate(IConsole[] consoles, int type) { new ConsoleNotifier().notify(consoles, type); } - - private class ShowConsoleViewJob extends WorkbenchJob { + private abstract static class AbstractConsoleJob extends WorkbenchJob { private final Set queue = new LinkedHashSet<>(); - ShowConsoleViewJob() { - super("Show Console View"); //$NON-NLS-1$ + AbstractConsoleJob(String name) { + super(name); setSystem(true); setPriority(Job.SHORT); } @@ -271,7 +262,7 @@ public boolean belongsTo(Object family) { return family == ConsoleManager.CONSOLE_JOB_FAMILY; } - void addConsole(IConsole console) { + protected void addConsole(IConsole console) { synchronized (queue) { queue.add(console); } @@ -279,13 +270,16 @@ void addConsole(IConsole console) { @Override public IStatus runInUIThread(IProgressMonitor monitor) { - Set consolesToShow; + Set consolesToWorkWith; synchronized (queue) { - consolesToShow = new LinkedHashSet<>(queue); + consolesToWorkWith = new LinkedHashSet<>(queue); queue.clear(); } - for (IConsole c : consolesToShow) { - showConsole(c); + for (IConsole c : consolesToWorkWith) { + workWith(c, monitor); + if (monitor.isCanceled()) { + return Status.CANCEL_STATUS; + } } synchronized (queue) { if (!queue.isEmpty()) { @@ -295,7 +289,17 @@ public IStatus runInUIThread(IProgressMonitor monitor) { return Status.OK_STATUS; } - private void showConsole(IConsole c) { + abstract protected void workWith(IConsole console, IProgressMonitor monitor); + } + + private class ShowConsoleViewJob extends AbstractConsoleJob { + + ShowConsoleViewJob() { + super("Show Console View"); //$NON-NLS-1$ + } + + @Override + protected void workWith(IConsole c, IProgressMonitor monitor) { boolean consoleFound = false; IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (window != null && c != null) { @@ -303,7 +307,11 @@ private void showConsole(IConsole c) { if (page != null) { synchronized (fConsoleViews) { for (IConsoleView consoleView : fConsoleViews) { - if (consoleView.getSite().getPage().equals(page)) { + IWorkbenchPartSite site = consoleView.getSite(); + if (site == null) { + continue; + } + if (site.getPage().equals(page)) { boolean consoleVisible = page.isPartVisible(consoleView); if (consoleVisible) { consoleFound = true; @@ -314,6 +322,9 @@ private void showConsole(IConsole c) { consoleView.display(c); } } + if (monitor.isCanceled()) { + return; + } } } @@ -334,14 +345,10 @@ private void showConsole(IConsole c) { } } - private final ShowConsoleViewJob showJob = new ShowConsoleViewJob(); - /** - * @see IConsoleManager#showConsoleView(IConsole) - */ @Override public void showConsoleView(final IConsole console) { - showJob.addConsole(console); - showJob.schedule(100); + showConsoleJob.addConsole(console); + showConsoleJob.schedule(100); } /** @@ -366,32 +373,39 @@ private boolean shouldBringToTop(IConsole console, IViewPart consoleView) { @Override public void warnOfContentChange(final IConsole console) { - if (!fWarnQueued) { - fWarnQueued = true; - Job job = new UIJob(ConsolePlugin.getStandardDisplay(), ConsoleMessages.ConsoleManager_consoleContentChangeJob) { - @Override - public boolean belongsTo(Object family) { - return family == ConsoleManager.CONSOLE_JOB_FAMILY; - } + warnAboutContentChangeJob.addConsole(console); + warnAboutContentChangeJob.schedule(50); + } - @Override - public IStatus runInUIThread(IProgressMonitor monitor) { - IWorkbenchWindow window= PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (window != null) { - IWorkbenchPage page= window.getActivePage(); - if (page != null) { - IConsoleView consoleView= (IConsoleView)page.findView(IConsoleConstants.ID_CONSOLE_VIEW); - if (consoleView != null) { - consoleView.warnOfContentChange(console); - } - } + private final class WarnAboutContentChangedJob extends AbstractConsoleJob { + + private WarnAboutContentChangedJob() { + super(ConsoleMessages.ConsoleManager_consoleContentChangeJob); + } + + @Override + protected void workWith(IConsole console, IProgressMonitor monitor) { + List viewsToUpdate = new ArrayList<>(); + synchronized (fConsoleViews) { + for (ConsoleView view : fConsoleViews) { + IWorkbenchPartSite site = view.getSite(); + if (site == null) { + continue; } - fWarnQueued = false; - return Status.OK_STATUS; + boolean viewVisible = site.getPage().isPartVisible(view); + if (viewVisible && view.getConsole() == console) { + // No need to update the UI if the console is already on top, since + // user will already see content changes. This also prevents unnecessary + // redraws which can cause flickering. + viewsToUpdate.clear(); + break; + } + viewsToUpdate.add(view); } - }; - job.setSystem(true); - job.schedule(); + } + for (ConsoleView view : viewsToUpdate) { + view.warnOfContentChange(console); + } } } @@ -432,11 +446,6 @@ public IPatternMatchListener[] createPatternMatchListeners(IConsole console) { return list.toArray(new PatternMatchListener[0]); } - /* - * @see - * org.eclipse.ui.console.IConsoleManager#getPageParticipants(org.eclipse.ui. - * console.IConsole) - */ public IConsolePageParticipant[] getPageParticipants(IConsole console) { if(fPageParticipants == null) { fPageParticipants = new ArrayList<>(); @@ -460,9 +469,6 @@ public IConsolePageParticipant[] getPageParticipants(IConsole console) { return list.toArray(new IConsolePageParticipant[0]); } - /* - * @see org.eclipse.ui.console.IConsoleManager#getConsoleFactories() - */ public ConsoleFactoryExtension[] getConsoleFactoryExtensions() { if (fConsoleFactoryExtensions == null) { fConsoleFactoryExtensions = new ArrayList<>(); @@ -478,8 +484,58 @@ public ConsoleFactoryExtension[] getConsoleFactoryExtensions() { @Override public void refresh(final IConsole console) { - fRepaintJob.addConsole(console); - fRepaintJob.schedule(50); + redrawConsoleJob.addConsole(console); + redrawConsoleJob.schedule(50); } + @Override + public void consoleShown(IConsole console) { + if (isConsoleViewNotVisibleAnywhere(console)) { + // if no view with console is visible, ignore + return; + } + console.pageShown(); + } + + @Override + public void consoleHidden(IConsole console) { + if (isConsoleVisibleSomewhere(console)) { + // if console is still shown in some other view, then there is no need to call + // pageHidden() on the console, since user can still see the console + return; + } + console.pageHidden(); + } + + private boolean isConsoleVisibleSomewhere(IConsole console) { + synchronized (fConsoleViews) { + for (ConsoleView view : fConsoleViews) { + IWorkbenchPartSite site = view.getSite(); + if (site == null) { + continue; + } + boolean viewVisible = site.getPage().isPartVisible(view); + if (viewVisible && view.getConsole() == console) { + return true; + } + } + } + return false; + } + + private boolean isConsoleViewNotVisibleAnywhere(IConsole console) { + synchronized (fConsoleViews) { + for (ConsoleView view : fConsoleViews) { + IWorkbenchPartSite site = view.getSite(); + if (site == null) { + continue; + } + boolean viewVisible = site.getPage().isPartVisible(view); + if (viewVisible && view.getConsole() == console) { + return false; + } + } + } + return true; + } } diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleView.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleView.java index 5850ed01813..e063e9e68f4 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleView.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleView.java @@ -80,17 +80,17 @@ public class ConsoleView extends PageBookView implements IConsoleView, IConsoleL /** * Whether this console is pinned. */ - private boolean fPinned = false; + private boolean fPinned; /** * Stack of consoles in MRU order */ - private final List fStack = new ArrayList<>(); + private final List fStack; /** * The console being displayed, or null if none */ - private IConsole fActiveConsole = null; + private IConsole fActiveConsole; /** * Map of consoles to dummy console parts (used to close pages) @@ -110,7 +110,7 @@ public class ConsoleView extends PageBookView implements IConsoleView, IConsoleL /** * Whether this view is active */ - private boolean fActive = false; + private boolean fActive; /** * 'In Console View' context @@ -118,10 +118,10 @@ public class ConsoleView extends PageBookView implements IConsoleView, IConsoleL private IContextActivation fActivatedContext; // actions - private PinConsoleAction fPinAction = null; - private ConsoleDropDownAction fDisplayConsoleAction = null; + private PinConsoleAction fPinAction; + private ConsoleDropDownAction fDisplayConsoleAction; - private OpenConsoleAction fOpenConsoleAction = null; + private OpenConsoleAction fOpenConsoleAction; private boolean fScrollLock; private boolean fWordWrap; @@ -132,7 +132,8 @@ public class ConsoleView extends PageBookView implements IConsoleView, IConsoleL private boolean updateConsoleIcon; private boolean isAvailable() { - return getPageBook() != null && !getPageBook().isDisposed(); + PageBook pageBook = getPageBook(); + return pageBook != null && !pageBook.isDisposed(); } @Override @@ -165,11 +166,16 @@ public IConsole getConsole() { return fActiveConsole; } + private void setConsole(IConsole recConsole) { + fActiveConsole = recConsole; + } + @Override protected void showPageRec(PageRec pageRec) { + IConsole oldActiveConsole = getConsole(); // don't show the page when pinned, unless this is the first console to be added // or its the default page - if (fActiveConsole != null && pageRec.page != getDefaultPage() && fPinned && fConsoleToPart.size() > 1) { + if (oldActiveConsole != null && pageRec.page != getDefaultPage() && fPinned && fConsoleToPart.size() > 1) { IConsole console = fPartToConsole.get(pageRec.part); if (!fStack.contains(console)) { fStack.add(console); @@ -178,25 +184,31 @@ protected void showPageRec(PageRec pageRec) { } IConsole recConsole = fPartToConsole.get(pageRec.part); - if (recConsole!=null && recConsole.equals(fActiveConsole)) { + if (recConsole != null && recConsole.equals(oldActiveConsole)) { return; } super.showPageRec(pageRec); - if (fActiveConsole != recConsole) { - if (fActive && fActiveConsole != null) { - deactivateParticipants(fActiveConsole); + if (oldActiveConsole != recConsole) { + if (fActive && oldActiveConsole != null) { + deactivateParticipants(oldActiveConsole); + } + + setConsole(recConsole); + + if (oldActiveConsole != null) { + getConsoleManager().consoleHidden(oldActiveConsole); } if (recConsole != null) { activateParticipants(recConsole); + getConsoleManager().consoleShown(recConsole); } } - fActiveConsole = recConsole; // bring active console on top of stack - if (fActiveConsole != null && !fStack.isEmpty() && !fActiveConsole.equals(fStack.get(0))) { - fStack.remove(fActiveConsole); - fStack.add(0, fActiveConsole); + if (recConsole != null && !fStack.isEmpty() && !recConsole.equals(fStack.get(0))) { + fStack.remove(recConsole); + fStack.add(0, recConsole); } updateTitle(); updateHelp(); @@ -206,8 +218,8 @@ protected void showPageRec(PageRec pageRec) { fPinAction.update(); } IPage page = getCurrentPage(); - if (page instanceof IOConsolePage) { - ((IOConsolePage) page).setWordWrap(fWordWrap); + if (page instanceof IOConsolePage consolePage) { + consolePage.setWordWrap(fWordWrap); } /* * Bug 268608: cannot invoke find/replace after opening console @@ -215,8 +227,8 @@ protected void showPageRec(PageRec pageRec) { * Global actions of TextConsolePage must be updated here, * but they are only updated on a selection change. */ - if (page instanceof TextConsolePage) { - TextConsoleViewer viewer = ((TextConsolePage) page).getViewer(); + if (page instanceof TextConsolePage consolePage) { + TextConsoleViewer viewer = consolePage.getViewer(); viewer.setSelection(viewer.getSelection()); } } @@ -268,7 +280,7 @@ protected void updateTitle() { } else { String newName = console.getName(); String oldName = getContentDescription(); - if (newName!=null && !(newName.equals(oldName))) { + if (newName != null && !(newName.equals(oldName))) { setContentDescription(console.getName()); } } @@ -349,7 +361,7 @@ public void handleException(Throwable exception) { fPartToConsole.remove(part); fConsoleToPart.remove(console); if (fPartToConsole.isEmpty()) { - fActiveConsole = null; + setConsole(null); } // update console actions @@ -377,7 +389,7 @@ protected PageRec doCreatePage(IWorkbenchPart dummyPart) { console.addPropertyChangeListener(this); // initialize page participants - IConsolePageParticipant[] consoleParticipants = ((ConsoleManager)getConsoleManager()).getPageParticipants(console); + IConsolePageParticipant[] consoleParticipants = getConsoleManager().getPageParticipants(console); final ListenerList participants = new ListenerList<>(); for (IConsolePageParticipant consoleParticipant : consoleParticipants) { participants.add(consoleParticipant); @@ -414,7 +426,7 @@ public void dispose() { site.getPage().removePartListener((IPartListener2)this); } super.dispose(); - ConsoleManager consoleManager = (ConsoleManager) ConsolePlugin.getDefault().getConsoleManager(); + ConsoleManager consoleManager = getConsoleManager(); consoleManager.removeConsoleListener(this); consoleManager.unregisterConsoleView(this); if (fDisplayConsoleAction != null) { @@ -429,16 +441,18 @@ public void dispose() { localResManager.dispose(); localResManager = null; } + fConsoleToPageParticipants.clear(); + fStack.clear(); + fConsoleToPart.clear(); + fPartToConsole.clear(); ConsolePlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this); } /** - * Returns the console manager. - * * @return the console manager */ - private IConsoleManager getConsoleManager() { - return ConsolePlugin.getDefault().getConsoleManager(); + private ConsoleManager getConsoleManager() { + return (ConsoleManager) ConsolePlugin.getDefault().getConsoleManager(); } @Override @@ -502,18 +516,17 @@ public void consolesRemoved(final IConsole[] consoles) { */ public ConsoleView() { super(); + fStack = new ArrayList<>(); fConsoleToPart = new HashMap<>(); fPartToConsole = new HashMap<>(); fConsoleToPageParticipants = new HashMap<>(); - - ConsoleManager consoleManager = (ConsoleManager) ConsolePlugin.getDefault().getConsoleManager(); - consoleManager.registerConsoleView(this); + getConsoleManager().registerConsoleView(this); } protected void createActions() { fPinAction = new PinConsoleAction(this); fDisplayConsoleAction = new ConsoleDropDownAction(this); - ConsoleFactoryExtension[] extensions = ((ConsoleManager)ConsolePlugin.getDefault().getConsoleManager()).getConsoleFactoryExtensions(); + ConsoleFactoryExtension[] extensions = getConsoleManager().getConsoleFactoryExtensions(); if (extensions.length > 0) { fOpenConsoleAction = new OpenConsoleAction(this); } @@ -534,8 +547,7 @@ protected void configureToolBar(IToolBarManager mgr) { public void mouseDown(MouseEvent e) { ToolItem ti= tb.getItem(new Point(e.x, e.y)); if (ti != null) { - if (ti.getData() instanceof ActionContributionItem) { - ActionContributionItem actionContributionItem= (ActionContributionItem) ti.getData(); + if (ti.getData() instanceof ActionContributionItem actionContributionItem) { IAction action= actionContributionItem.getAction(); if (action == fOpenConsoleAction) { Event event= new Event(); @@ -554,10 +566,11 @@ public void mouseDown(MouseEvent e) { @Override public void display(IConsole console) { - if (fPinned && fActiveConsole != null) { + IConsole activeConsole = getConsole(); + if (fPinned && activeConsole != null) { return; } - if (console.equals(fActiveConsole)) { + if (console.equals(activeConsole)) { return; } ConsoleWorkbenchPart part = fConsoleToPart.get(console); @@ -590,8 +603,8 @@ protected IWorkbenchPart getBootstrapPart() { } /** - * Registers the given runnable with the display associated with this view's - * control, if any. + * Runs the given runnable with the display associated with this view's control, + * if any. * * @param r the runnable * @see org.eclipse.swt.widgets.Display#asyncExec(java.lang.Runnable) @@ -622,12 +635,12 @@ public void asyncExec(Runnable r) { public void createPartControl(Composite parent) { super.createPartControl(parent); createActions(); - IToolBarManager tbm= getViewSite().getActionBars().getToolBarManager(); - configureToolBar(tbm); + IViewSite viewSite = getViewSite(); + configureToolBar(viewSite.getActionBars().getToolBarManager()); updateForExistingConsoles(); - getViewSite().getActionBars().updateActionBars(); + viewSite.getActionBars().updateActionBars(); PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, IConsoleHelpContextIds.CONSOLE_VIEW); - getViewSite().getPage().addPartListener((IPartListener2)this); + viewSite.getPage().addPartListener((IPartListener2) this); initPageSwitcher(); localResManager = new LocalResourceManager(JFaceResources.getResources(), parent); updateConsoleIcon = shouldUpdateConsoleIcon(); @@ -701,8 +714,8 @@ public void warnOfContentChange(IConsole console) { @SuppressWarnings("unchecked") @Override public T getAdapter(Class key) { - Object adpater = super.getAdapter(key); - if (adpater == null) { + Object adapter = super.getAdapter(key); + if (adapter == null) { IConsole console = getConsole(); if (console != null) { ListenerList listeners = getParticipants(console); @@ -710,15 +723,15 @@ public T getAdapter(Class key) { if (listeners != null) { for (IConsolePageParticipant iConsolePageParticipant : listeners) { IConsolePageParticipant participant = iConsolePageParticipant; - adpater = participant.getAdapter(key); - if (adpater != null) { - return (T) adpater; + adapter = participant.getAdapter(key); + if (adapter != null) { + return (T) adapter; } } } } } - return (T) adpater; + return (T) adapter; } @Override @@ -728,7 +741,7 @@ public void partActivated(IWorkbenchPartReference partRef) { IContextService contextService = getSite().getService(IContextService.class); if(contextService != null) { fActivatedContext = contextService.activateContext(IConsoleConstants.ID_CONSOLE_VIEW); - activateParticipants(fActiveConsole); + activateParticipants(getConsole()); } } } @@ -748,7 +761,7 @@ public void partDeactivated(IWorkbenchPartReference partRef) { IContextService contextService = getSite().getService(IContextService.class); if(contextService != null) { contextService.deactivateContext(fActivatedContext); - deactivateParticipants(fActiveConsole); + deactivateParticipants(getConsole()); } } } @@ -765,8 +778,8 @@ protected boolean isThisPart(IWorkbenchPartReference partRef) { if (getViewSite() != null && viewRef.getId().equals(getViewSite().getId())) { String secId = viewRef.getSecondaryId(); String mySec = null; - if (getSite() instanceof IViewSite) { - mySec = ((IViewSite)getSite()).getSecondaryId(); + if (getSite() instanceof IViewSite site) { + mySec = site.getSecondaryId(); } if (mySec == null) { return secId == null; @@ -811,10 +824,22 @@ public void partOpened(IWorkbenchPartReference partRef) { @Override public void partHidden(IWorkbenchPartReference partRef) { + if (isThisPart(partRef)) { + IConsole console = getConsole(); + if (console != null) { + getConsoleManager().consoleHidden(console); + } + } } @Override public void partVisible(IWorkbenchPartReference partRef) { + if (isThisPart(partRef)) { + IConsole console = getConsole(); + if (console != null) { + getConsoleManager().consoleShown(console); + } + } } @Override @@ -826,8 +851,8 @@ public void setScrollLock(boolean scrollLock) { fScrollLock = scrollLock; IPage page = getCurrentPage(); - if (page instanceof IOConsolePage) { - ((IOConsolePage)page).setAutoScroll(!scrollLock); + if (page instanceof IOConsolePage consolePage) { + consolePage.setAutoScroll(!scrollLock); } } @@ -841,10 +866,10 @@ public void setWordWrap(boolean wordWrap) { fWordWrap = wordWrap; IWorkbenchPart part = getSite().getPart(); - if (part instanceof PageBookView) { - Control control = ((PageBookView) part).getCurrentPage().getControl(); - if (control instanceof StyledText) { - ((StyledText) control).setWordWrap(wordWrap); + if (part instanceof PageBookView pagebookView) { + Control control = pagebookView.getCurrentPage().getControl(); + if (control instanceof StyledText styledText) { + styledText.setWordWrap(wordWrap); } } } @@ -870,8 +895,8 @@ public void pin(IConsole console) { @Override public void setAutoScrollLock(boolean scrollLock) { IPage page = getCurrentPage(); - if (page instanceof IOConsolePage) { - ((IOConsolePage) page).setAutoScroll(!scrollLock); + if (page instanceof IOConsolePage consolePage) { + consolePage.setAutoScroll(!scrollLock); } } @@ -879,9 +904,9 @@ public void setAutoScrollLock(boolean scrollLock) { @Override public boolean getAutoScrollLock() { IPage page = getCurrentPage(); - if (page instanceof IOConsolePage) { - return !((IOConsolePage) page).isAutoScroll(); + if (page instanceof IOConsolePage consolePage) { + return !consolePage.isAutoScroll(); } return fScrollLock; } -} +} \ No newline at end of file diff --git a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleViewConsoleFactory.java b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleViewConsoleFactory.java index 7ebab677345..f186c1f8613 100644 --- a/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleViewConsoleFactory.java +++ b/debug/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/ConsoleViewConsoleFactory.java @@ -64,7 +64,7 @@ public void openConsole() { * If the remember auto-pin decision state is true it gathers the auto * pin preference value and sets this to the current view. */ - private boolean handleAutoPin() { + protected boolean handleAutoPin() { if (currentConsoleView == null) { return false; } @@ -92,7 +92,7 @@ private boolean handleAutoPin() { /** * Sets the console view, on which the open new console action was called. */ - void setConsoleView(ConsoleView consoleView) { + public void setConsoleView(ConsoleView consoleView) { this.currentConsoleView = consoleView; }