Xterm view for OS terminal in Eclipse

This commit is contained in:
BoykoAlex
2020-10-15 17:30:35 -04:00
parent 49aef18f06
commit e33a921641
44 changed files with 2186 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
/target/
/.settings/
.DS_Store
/**/.DS_Store
.project
.classpath

View File

@@ -0,0 +1,20 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: org.springframework.ide.eclipse.xterm
Bundle-SymbolicName: org.springframework.ide.eclipse.xterm;singleton:=true
Bundle-Version: 4.8.1.qualifier
Automatic-Module-Name: org.springframework.ide.eclipse.xterm
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-Activator: org.springframework.ide.eclipse.xterm.XtermPlugin
Eclipse-BundleShape: dir
Require-Bundle: org.eclipse.ui;bundle-version="3.115.0",
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.apache.httpcomponents.httpclient,
org.apache.httpcomponents.httpcore,
org.eclipse.wildwebdeveloper.embedder.node
Import-Package: javax.inject
Bundle-ClassPath: .
Bundle-ActivationPolicy: lazy
Export-Package: org.springframework.ide.eclipse.xterm,
org.springframework.ide.eclipse.xterm.views

View File

@@ -0,0 +1,7 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
plugin.xml,\
css/,\
icons/

View File

@@ -0,0 +1,9 @@
/* See bug 466075 about the pseudo-selector ":org-springframework-tooling-ls-eclipse-commons" */
IEclipsePreferences#org-eclipse-ui-workbench:org-springframework-ide-eclipse-xterm {
preferences:
'org.springframework.ide.eclipse.xterm.background=41,41,41'
'org.springframework.ide.eclipse.xterm.foreground=255,255,255'
'org.springframework.ide.eclipse.xterm.selection=235,235,235'
'org.springframework.ide.eclipse.xterm.cursor=255,255,255'
'org.springframework.ide.eclipse.xterm.cursorAccent=255,255,255'

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" baseProfile="full" width="100px" height="100px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet" id="svg_document" style="zoom: 1;"><!-- Created with macSVG - https://macsvg.org/ - https://github.com/dsward2/macsvg/ --><title id="svg_document_title">Untitled.svg</title><defs id="svg_document_defs"></defs><g id="main_group"><rect stroke="#000000" id="rect1" stroke-width="nonepx" x="0px" rx="20px" width="100px" y="0px" fill="0" transform="" ry="20px" height="97.116px"></rect></g><polyline points="15,30 40,50 15,70 " stroke="white" id="polyline1" stroke-width="15px" fill="none" stroke-linecap="round" stroke-linejoin="round" transform=""></polyline><line stroke="white" y1="70px" id="line1" stroke-width="15px" x1="55px" y2="70px" stroke-linecap="round" x2="80px" transform=""></line></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

View File

@@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.ui.views">
<view
category="org.eclipse.ui"
class="org.springframework.ide.eclipse.xterm.views.TerminalView"
icon="icons/terminal.png"
id="org.springframework.ide.eclipse.xterm.views.TerminalView"
inject="true"
name="Xterm">
</view>
</extension>
<extension
point="org.eclipse.e4.ui.css.swt.theme">
<stylesheet
uri="css/terminal-dark-theme.css">
<themeid
refid="org.eclipse.e4.ui.css.theme.e4_dark">
</themeid>
</stylesheet>
</extension>
<extension
point="org.eclipse.ui.themes">
<colorDefinition
categoryId="org.springframework.ide.eclipse.xterm"
id="org.springframework.ide.eclipse.xterm.background"
isEditable="true"
label="Background Color"
value="255,255,255">
</colorDefinition>
<colorDefinition
categoryId="org.springframework.ide.eclipse.xterm"
id="org.springframework.ide.eclipse.xterm.foreground"
label="Foreground Color"
value="0,0,0">
</colorDefinition>
<colorDefinition
categoryId="org.springframework.ide.eclipse.xterm"
id="org.springframework.ide.eclipse.xterm.selection"
label="Selection Color"
value="45,45,45">
</colorDefinition>
<colorDefinition
categoryId="org.springframework.ide.eclipse.xterm"
id="org.springframework.ide.eclipse.xterm.cursor"
label="Cursor Color"
value="0,0,0">
</colorDefinition>
<colorDefinition
categoryId="org.springframework.ide.eclipse.xterm"
id="org.springframework.ide.eclipse.xterm.cursorAccent"
label="Cursor Accent Color"
value="0,0,0">
</colorDefinition>
<themeElementCategory
id="org.springframework.ide.eclipse.xterm"
label="Xterm">
<description>
Xterm terminal apperance theme settings
</description>
</themeElementCategory>
<fontDefinition
categoryId="org.springframework.ide.eclipse.xterm"
defaultsTo="org.eclipse.jface.textfont"
id="org.springframework.ide.eclipse.xterm.font"
label="Font">
</fontDefinition>
</extension>
<extension
point="org.eclipse.ui.menus">
<menuContribution
allPopups="false"
locationURI="popup:org.eclipse.ui.popup.any?before=group.edit">
<command
commandId="org.springframework.ide.eclipse.xterm.open"
icon="icons/terminal.png"
id="org.springframework.ide.eclipse.xterm.open"
label="Open Xterm"
style="push"
tooltip="Open terminal for the folder">
<visibleWhen
checkEnabled="false">
<and>
<count
value="1">
</count>
<iterate>
<or>
<adapt
type="org.eclipse.core.resources.IProject">
<test
property="org.eclipse.core.resources.open"
value="true">
</test>
</adapt>
<and>
<instanceof
value="org.eclipse.core.resources.IProject">
</instanceof>
<test
property="org.eclipse.core.resources.open"
value="true">
</test>
</and>
</or>
</iterate>
</and>
</visibleWhen>
</command>
</menuContribution>
</extension>
<extension
point="org.eclipse.ui.handlers">
<handler
class="org.springframework.ide.eclipse.xterm.views.OpenTerminalHandler"
commandId="org.springframework.ide.eclipse.xterm.open">
<enabledWhen>
<and>
<count
value="1">
</count>
<iterate>
<or>
<adapt
type="org.eclipse.core.resources.IProject">
<test
property="org.eclipse.core.resources.open"
value="true">
</test>
</adapt>
<and>
<instanceof
value="org.eclipse.core.resources.IProject">
</instanceof>
<test
property="org.eclipse.core.resources.open"
value="true">
</test>
</and>
</or>
</iterate>
</and>
</enabledWhen>
</handler>
</extension>
<extension
point="org.eclipse.ui.commands">
<command
categoryId="org.eclipse.ui.category.navigate"
defaultHandler="org.springframework.ide.eclipse.xterm.views.OpenTerminalHandler"
description="Open terminal for folder"
id="org.springframework.ide.eclipse.xterm.open"
name="Open Terminal">
</command>
</extension>
<extension
point="org.eclipse.core.runtime.preferences">
<initializer
class="org.springframework.ide.eclipse.xterm.views.XtermPreferencesInitializer">
</initializer>
</extension>
<extension
point="org.eclipse.ui.preferencePages">
<page
class="org.springframework.ide.eclipse.xterm.views.XtermPreferencePage"
id="org.springframework.ide.eclipse.xterm."
name="Xterm">
</page>
</extension>
</plugin>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot.ide</groupId>
<artifactId>org.springframework.boot.ide</artifactId>
<version>4.8.1-SNAPSHOT</version>
<relativePath>../../eclipse-distribution/pom.xml</relativePath>
</parent>
<packaging>eclipse-plugin</packaging>
<artifactId>org.springframework.ide.eclipse.xterm</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>target-platform-configuration</artifactId>
<version>${tycho-version}</version>
<configuration>
<resolver>p2</resolver>
<pomDependencies>ignore</pomDependencies>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-plugin</artifactId>
<version>${tycho-version}</version>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-source-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>plugin-source</id>
<phase>generate-sources</phase>
<goals>
<goal>plugin-source</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-p2-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>second-generate-p2-metadata</id>
<goals>
<goal>p2-metadata</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,169 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.xterm;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
import org.springframework.ide.eclipse.xterm.views.TerminalView;
public class XtermPlugin extends AbstractUIPlugin {
private static XtermPlugin plugin;
private static final String PLUGIN_ID = "org.springframework.ide.eclipse.xterm";
public static final String BG_COLOR = "org.springframework.ide.eclipse.xterm.background";
public static final String FG_COLOR = "org.springframework.ide.eclipse.xterm.foreground";
public static final String SELECTION_COLOR = "org.springframework.ide.eclipse.xterm.selection";
public static final String CURSOR_COLOR = "org.springframework.ide.eclipse.xterm.cursor";
public static final String CURSOR_ACCENT_COLOR = "org.springframework.ide.eclipse.xterm.cursorAccent";
public static final String FONT = "org.springframework.ide.eclipse.xterm.font";
public static final String PREFS_DEFAULT_SHELL_CMD = "org.springframework.ide.eclipse.xterm.defaultShellCmd";
private static final List<String> NO_SHOW_EXCEPTIONS = Arrays.asList(
"ValueParseException",
"CoreException"
);
private XtermServiceProcessManager serviceManager = new XtermServiceProcessManager();
@Override
public void start(BundleContext bundle) throws Exception {
plugin = this;
}
@Override
public void stop(BundleContext bundle) throws Exception {
serviceManager.stopService();
plugin = null;
}
public static XtermPlugin getDefault() {
return plugin;
}
public static void log(String m, Throwable t) {
getDefault().getLog().error(m, t);
}
public void openTerminalView(String cmd, String cwd) {
try {
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
String terminalId = UUID.randomUUID().toString();
TerminalView terminalView = (TerminalView) page.showView(TerminalView.ID, terminalId, IWorkbenchPage.VIEW_ACTIVATE);
terminalView.startTerminal(terminalId, cmd, cwd);
} catch (Exception e) {
XtermPlugin.log(e);
}
}
public CompletableFuture<String> xtermUrl(long timeout) {
return CompletableFuture.supplyAsync(() -> {
try {
return serviceManager.serviceUrl(Duration.ofSeconds(10));
} catch (Throwable t) {
throw new CompletionException(t);
}
});
}
public static void log(Throwable e) {
if (isCancelation(e)) {
//Don't log canceled operations, those aren't real errors.
return;
}
try {
XtermPlugin.getDefault().getLog().log(status(e));
} catch (NullPointerException npe) {
//Can happen if errors are trying to be logged during Eclipse's shutdown
e.printStackTrace();
}
}
private static boolean isCancelation(Throwable e) {
if (e==null) {
return false;
}
Throwable cause = e.getCause();
boolean isCancel = (
e instanceof OperationCanceledException ||
e instanceof InterruptedException ||
e instanceof CancellationException ||
(
e instanceof CoreException &&
((CoreException)e).getStatus().getSeverity()==IStatus.CANCEL
)
);
return isCancel || (
cause!=null && //avoid npe's on recursive check
cause!=e && //avoid infinite recursion on e == cause
isCancelation(cause)
);
}
private static IStatus status(Throwable e) {
if (isCancelation(e)) {
return Status.CANCEL_STATUS;
}
return status(IStatus.ERROR, e);
}
private static IStatus status(int severity, Throwable e) {
if (e instanceof CoreException) {
IStatus status = ((CoreException) e).getStatus();
if (status != null && status.getSeverity() == severity) {
Throwable ee = status.getException();
if (ee != null) {
return status;
}
}
}
return new Status(severity, XtermPlugin.PLUGIN_ID, getMessage(e), e);
}
private static String getMessage(Throwable e) {
// The message of nested exception is usually more interesting than the
// one on top.
Throwable cause = getDeepestCause(e);
String errorType = cause.getClass().getSimpleName();
String msg = cause.getMessage();
if (NO_SHOW_EXCEPTIONS.contains(errorType) && msg!=null) {
return msg;
}
return errorType + ": " + msg;
}
private static Throwable getDeepestCause(Throwable e) {
Throwable cause = e;
Throwable parent = e.getCause();
while (parent != null && parent != e) {
cause = parent;
parent = cause.getCause();
}
return cause;
}
}

View File

@@ -0,0 +1,165 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.xterm;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Duration;
import java.util.concurrent.TimeoutException;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.wildwebdeveloper.embedder.node.NodeJSManager;
import org.osgi.framework.Bundle;
class XtermServiceProcessManager {
private static final int INVALID_PORT = -1;
private static final String NODEJS_SYSTEM_PROPERTY = "org.springframework.xterm.nodejs";
private HttpClient httpClient = HttpClientBuilder.create().build();
private Process process;
private int port = INVALID_PORT;
synchronized void startProcess() throws IOException {
port = findFreePort();
Bundle bundle = XtermPlugin.getDefault().getBundle();
URL url = FileLocator.find(bundle, new Path("/lib/node_modules/node-xterm/terminal-server.js"), null);
// URL url = FileLocator.find(bundle, new Path("/lib/terminal-server.js"), null);
// URL url = FileLocator.find(bundle, new Path("/lib/node-xterm-macos"), null);
// URL url = FileLocator.find(bundle, new Path("/lib/node-xterm-win.exe"), null);
url = FileLocator.toFileURL(url);
try {
File serverJsFile = new File(url.toURI());
if (serverJsFile == null || !serverJsFile.exists()) {
throw new IllegalStateException("Cannot find file " + serverJsFile + ". Cannot start xterm service!");
}
File nodeJs = null;
if (System.getProperty(NODEJS_SYSTEM_PROPERTY) != null) {
nodeJs = new File(System.getProperty(NODEJS_SYSTEM_PROPERTY));
} else {
nodeJs = NodeJSManager.getNodeJsLocation();
}
if (nodeJs == null || !nodeJs.exists()) {
throw new IllegalStateException("Cannot find NodeJS executable at '" + nodeJs + "'. Cannot start xterm service!");
}
ProcessBuilder builder = new ProcessBuilder(
nodeJs.toString(),
serverJsFile.toString(),
"--server.port=" + port,
"--terminal.pty.shutdown=delay", // terminal pty process destroyed right after sockets closed
"--terminal.pty.shutdown-delay=5",
"--terminal.auto-shutdown.on=true", // terminal app can shutdown itself if not used
"--terminal.auto-shutdown.delay=30" // terminal app shuts itself down in not used for 30 sec
);
File logFile = Platform.getStateLocation(XtermPlugin.getDefault().getBundle()).append("xterm-log.log").toFile();
builder.redirectError(logFile);
builder.redirectOutput(logFile);
process = builder.start();
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
}
synchronized private void waitUntilStarted(Duration timeout) throws TimeoutException, InterruptedException {
long start = System.currentTimeMillis();
long timeoutMillis = timeout.toMillis();
do {
if (System.currentTimeMillis() - start > timeoutMillis) {
throw new TimeoutException("Timed out waiting for Xterm service to start");
}
Thread.sleep(150);
} while (!isStarted(port));
}
synchronized private boolean isStarted(int port) {
HttpGet request = new HttpGet("http://localhost:" + port);
try {
HttpResponse response = httpClient.execute(request);
if (response.getStatusLine().getStatusCode() == 200) {
return true;
}
} catch (IOException e) {
// Ignore
}
return false;
}
synchronized void stopService() {
if (process != null && process.isAlive()) {
if (port > 0) {
HttpPost request = new HttpPost("http://localhost:" + port + "/shutdown");
request.setHeader("Content-Type", "application/json");
try {
HttpResponse response = httpClient.execute(request);
int code = response.getStatusLine().getStatusCode();
if (code != 200) {
process.destroy();
}
} catch (ClientProtocolException e) {
throw new RuntimeException(e);
} catch (IOException e) {
process.destroy();
}
} else {
process.destroy();
}
}
process = null;
}
synchronized String serviceUrl(Duration timeout) throws Exception {
if (port == INVALID_PORT && process != null && process.isAlive()) {
process.destroy();
process = null;
}
if (process == null || !process.isAlive()) {
startProcess();
try {
waitUntilStarted(timeout);
} catch (Exception e) {
this.port = INVALID_PORT;
throw e;
}
}
return "http://localhost:" + port;
}
private static int findFreePort() throws IOException {
ServerSocket socket = new ServerSocket(0);
try {
return socket.getLocalPort();
} finally {
try {
socket.close();
} catch (IOException e) {
}
}
}
}

View File

@@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.xterm.views;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.ui.handlers.HandlerUtil;
import org.springframework.ide.eclipse.xterm.XtermPlugin;
public class OpenTerminalHandler extends AbstractHandler implements IHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
try {
Object obj = HandlerUtil.getCurrentStructuredSelection(event).getFirstElement();
IProject project = null;
if (obj instanceof IProject) {
project = (IProject) obj;
} else if (obj instanceof IAdaptable) {
project = ((IAdaptable)obj).getAdapter(IProject.class);
}
if (project == null) {
throw new ExecutionException("Cannot find folder for element " + obj);
}
String cwd = project.getLocation().toOSString();
XtermPlugin.getDefault().openTerminalView(null, cwd);
} catch (Exception e) {
throw new ExecutionException("Failed to open terminal", e);
}
return null;
}
}

View File

@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.xterm.views;
import java.net.URLEncoder;
import org.springframework.ide.eclipse.xterm.XtermPlugin;
class SimpleUriBuilder {
private final StringBuilder builder;
private int numparams = 0;
public SimpleUriBuilder(String baseUrl) {
builder = new StringBuilder(baseUrl);
}
public void addParameter(String name, String paramValue) {
try {
if (numparams==0) {
builder.append("?");
} else {
builder.append("&");
}
numparams++;
builder.append(URLEncoder.encode(name, "UTF-8"));
if (paramValue!=null) {
builder.append("=");
builder.append(URLEncoder.encode(paramValue, "UTF-8"));
}
} catch (Exception e) {
XtermPlugin.log(e);
}
}
@Override
public String toString() {
return builder.toString();
}
}

View File

@@ -0,0 +1,285 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.xterm.views;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.RGBA;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.themes.ITheme;
import org.springframework.ide.eclipse.xterm.XtermPlugin;
public class TerminalView extends ViewPart {
private static final String CWD_PARAM = "cwd";
private static final String CMD_PARAM = "cmd";
private static final String TERMINAL_ID_PARAM = "terminal";
private static final String DEFAULT_TERMINAL_ID = "default";
private static final String ERROR_DIALOG_TITLE = "Error Opening Xterm";
private static final String ERROR_DIALOG_MESSAGE = "Failed to determine if Xterm service is running.\n";
/**
* The ID of the view as specified by the extension.
*/
public static final String ID = "org.springframework.ide.eclipse.xterm.views.TerminalView";
@Inject IWorkbench workbench;
private Action refreshAction;
private Browser browser;
private String terminalId = DEFAULT_TERMINAL_ID;
private String cmd;
private String cwd;
private boolean isNewView;
private final IPropertyChangeListener PROPERTY_LISTENER = new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
switch (event.getProperty()) {
case XtermPlugin.BG_COLOR:
case XtermPlugin.FG_COLOR:
case XtermPlugin.SELECTION_COLOR:
case XtermPlugin.CURSOR_COLOR:
case XtermPlugin.CURSOR_ACCENT_COLOR:
case XtermPlugin.FONT:
refresh();
break;
default:
}
}
};
@Override
public void createPartControl(Composite parent) {
PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(PROPERTY_LISTENER);
browser = new Browser(parent, SWT.CHROMIUM);
makeActions();
contributeToActionBars();
if (isNewView) {
// Non-default Xterm views are initialized with #startTerminal(...) call which will use Xterm service with the right parameters
if (isDefault()) {
navigateToTerminal(terminalId, null, ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString());
}
} else {
navigateToTerminal(terminalId, cmd, cwd == null ? ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString() : cwd);
}
}
private boolean isDefault() {
// Secondary id is present only for non-default Terminal views
return getViewSite().getSecondaryId() == null;
}
@Override
public void saveState(IMemento memento) {
if (terminalId != null) {
memento.putString(TERMINAL_ID_PARAM, terminalId);
if (cmd != null) {
memento.putString(CMD_PARAM, cmd);
}
if (cwd != null) {
memento.putString(CWD_PARAM, cwd);
}
}
super.saveState(memento);
}
@Override
public void init(IViewSite site, IMemento memento) throws PartInitException {
super.init(site, memento);
isNewView = memento == null;
if (memento != null) {
terminalId = memento.getString(TERMINAL_ID_PARAM);
if (terminalId == null) {
terminalId = DEFAULT_TERMINAL_ID;
} else {
cmd = memento.getString(CMD_PARAM);
cwd = memento.getString(CWD_PARAM);
}
}
}
public CompletableFuture<Void> refresh() {
return navigateToTerminal(terminalId, null, ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString());
}
private CompletableFuture<Void> navigateToTerminal(final String terminalId, final String cmd, final String cwd) {
return CompletableFuture.runAsync(() -> {
try {
String serviceUrl = XtermPlugin.getDefault().xtermUrl(10_000).get();
if (Display.getCurrent() != null) {
if (browser != null && !browser.isDisposed() && terminalId.equals(TerminalView.this.terminalId)) {
browser.setUrl(createUrl(serviceUrl, terminalId, cmd, cwd));
}
} else {
Display display = PlatformUI.getWorkbench().getDisplay();
if (display != null && !display.isDisposed()) {
display.asyncExec(() -> {
if (browser != null && !browser.isDisposed() && terminalId.equals(TerminalView.this.terminalId)) {
String url = createUrl(serviceUrl, terminalId, cmd, cwd);
browser.setUrl(url);
}
});
}
}
} catch (ExecutionException e) {
// TODO show error page in the browser
XtermPlugin.log(e);
if (e.getCause() != null) {
StringBuilder errorMessage = new StringBuilder(ERROR_DIALOG_MESSAGE);
errorMessage.append('\n');
errorMessage.append(e.getCause().getMessage());
if (Display.getCurrent() != null) {
MessageDialog.openError(Display.getCurrent().getActiveShell(), ERROR_DIALOG_TITLE, errorMessage.toString());
} else {
Display display = PlatformUI.getWorkbench().getDisplay();
if (display != null && !display.isDisposed()) {
display.asyncExec(() ->
MessageDialog.openError(Display.getCurrent().getActiveShell(), ERROR_DIALOG_TITLE, errorMessage.toString())
);
}
}
}
} catch (InterruptedException e) {
XtermPlugin.log(e);
}
});
}
public CompletableFuture<Void> startTerminal(String terminalId, String cmd, String cwd) {
this.terminalId = terminalId;
this.cmd = cmd;
this.cwd = cwd;
return navigateToTerminal(terminalId, cmd, cwd);
}
private String createUrl(String serviceUrl, String terminalId, String cmd, String cwd) {
SimpleUriBuilder urlBuilder = new SimpleUriBuilder(serviceUrl + "/terminal/" + terminalId);
ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme();
ColorRegistry colorRegistry = theme.getColorRegistry();
Font font = theme.getFontRegistry().get(XtermPlugin.FONT);
urlBuilder.addParameter("bg", rgbToUrlParameter(colorRegistry.get(XtermPlugin.BG_COLOR).getRGB()));
urlBuilder.addParameter("fg", rgbToUrlParameter(colorRegistry.get(XtermPlugin.FG_COLOR).getRGB()));
RGB selectionColor = colorRegistry.get(XtermPlugin.SELECTION_COLOR).getRGB();
// add transparency to selection color
urlBuilder.addParameter("selection", rgbaToUrlParameter(new RGBA(selectionColor.red, selectionColor.green, selectionColor.blue, 51)));
urlBuilder.addParameter("cursor", rgbToUrlParameter(colorRegistry.get(XtermPlugin.CURSOR_COLOR).getRGB()));
urlBuilder.addParameter("cursorAccent", rgbToUrlParameter(colorRegistry.get(XtermPlugin.CURSOR_ACCENT_COLOR).getRGB()));
urlBuilder.addParameter("fontFamily", font.getFontData()[0].getName());
urlBuilder.addParameter("fontSize", String.valueOf(font.getFontData()[0].getHeight()));
if (cmd != null && !cmd.isEmpty()) {
urlBuilder.addParameter(CMD_PARAM, cmd);
} else {
urlBuilder.addParameter(CMD_PARAM,
XtermPlugin.getDefault().getPreferenceStore().getString(XtermPlugin.PREFS_DEFAULT_SHELL_CMD));
}
if (cwd != null && !cwd.isEmpty()) {
urlBuilder.addParameter(CWD_PARAM, cwd);
}
return urlBuilder.toString();
}
private static String rgbToUrlParameter(RGB rgb) {
StringBuilder sb = new StringBuilder("rgb(");
sb.append(rgb.red);
sb.append(",");
sb.append(rgb.green);
sb.append(",");
sb.append(rgb.blue);
sb.append(")");
return sb.toString();
}
private static String rgbaToUrlParameter(RGBA rgba) {
StringBuilder sb = new StringBuilder("rgba(");
sb.append(rgba.rgb.red);
sb.append(",");
sb.append(rgba.rgb.green);
sb.append(",");
sb.append(rgba.rgb.blue);
sb.append(",");
sb.append(rgba.alpha / 255.0);
sb.append(")");
return sb.toString();
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
fillLocalPullDown(bars.getMenuManager());
fillLocalToolBar(bars.getToolBarManager());
}
private void fillLocalPullDown(IMenuManager manager) {
manager.add(refreshAction);
}
private void fillLocalToolBar(IToolBarManager manager) {
manager.add(refreshAction);
}
private void makeActions() {
refreshAction = new Action() {
public void run() {
refresh();
}
};
refreshAction.setText("Refresh");
refreshAction.setToolTipText("Refresh Terminal");
refreshAction.setImageDescriptor(XtermPlugin.imageDescriptorFromPlugin(XtermPlugin.getDefault().getBundle().getSymbolicName(), "icons/refresh.png"));
}
@Override
public void setFocus() {
browser.setFocus();
}
@Override
public void dispose() {
PlatformUI.getWorkbench().getThemeManager().removePropertyChangeListener(PROPERTY_LISTENER);
}
}

View File

@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.xterm.views;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.springframework.ide.eclipse.xterm.XtermPlugin;
public class XtermPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
@Override
public void init(IWorkbench workbench) {
setDescription("Xterm terminal preferences");
setPreferenceStore(XtermPlugin.getDefault().getPreferenceStore());
}
@Override
protected void createFieldEditors() {
Composite parent = getFieldEditorParent();
addField(new StringFieldEditor(XtermPlugin.PREFS_DEFAULT_SHELL_CMD, "Shell Command", parent));
}
}

View File

@@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.xterm.views;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.springframework.ide.eclipse.xterm.XtermPlugin;
public class XtermPreferencesInitializer extends AbstractPreferenceInitializer {
public XtermPreferencesInitializer() {
}
@Override
public void initializeDefaultPreferences() {
XtermPlugin.getDefault().getPreferenceStore().setDefault(XtermPlugin.PREFS_DEFAULT_SHELL_CMD, Platform.OS_WIN32.equals(Platform.getOS()) ? "cmd.exe" : "/bin/bash");
}
}