Skip to content

Commit 727aafd

Browse files
committed
Add support for launching Eclipse Process in a system terminal session
Currently processes are launched as a forked process without a terminal session attached to them. Some features of processes require a terminal session (e.g. autocompletion) and currently not work when running from within eclipse (e.g. the gogo-shell). This is an attempt to bring terminal session support to Eclipse to support real terminal application similar to what is supported by IntelliJ.
1 parent 0d9f518 commit 727aafd

File tree

11 files changed

+219
-7
lines changed

11 files changed

+219
-7
lines changed

debug/org.eclipse.debug.core/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: %pluginName
44
Bundle-SymbolicName: org.eclipse.debug.core; singleton:=true
5-
Bundle-Version: 3.22.100.qualifier
5+
Bundle-Version: 3.23.0.qualifier
66
Bundle-Activator: org.eclipse.debug.core.DebugPlugin
77
Bundle-Vendor: %providerName
88
Bundle-Localization: plugin

debug/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ public class DebugPlugin extends Plugin {
388388
*/
389389
private static DebugPlugin fgDebugPlugin= null;
390390

391+
static ExecFactory factory;
392+
391393
/**
392394
* The singleton breakpoint manager.
393395
*/
@@ -981,7 +983,13 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
981983
* @since 3.14
982984
*/
983985
public static Process exec(String[] cmdLine, File workingDirectory, String[] envp, boolean mergeOutput) throws CoreException {
984-
Process p = null;
986+
ExecFactory builder;
987+
synchronized (DebugPlugin.class) {
988+
builder = factory;
989+
}
990+
if (builder != null) {
991+
return builder.exec(cmdLine, shortenWindowsPath(workingDirectory), envp, mergeOutput);
992+
}
985993
try {
986994
// starting with and without merged output could be done with the
987995
// same process builder approach but since the handling of
@@ -1004,11 +1012,11 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
10041012
}
10051013
}
10061014
}
1007-
p = pb.start();
1015+
return pb.start();
10081016
} else if (workingDirectory == null) {
1009-
p = Runtime.getRuntime().exec(cmdLine, envp);
1017+
return Runtime.getRuntime().exec(cmdLine, envp);
10101018
} else {
1011-
p = Runtime.getRuntime().exec(cmdLine, envp, shortenWindowsPath(workingDirectory));
1019+
return Runtime.getRuntime().exec(cmdLine, envp, shortenWindowsPath(workingDirectory));
10121020
}
10131021
} catch (IOException e) {
10141022
Status status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_0, e);
@@ -1021,17 +1029,20 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
10211029
if (handler != null) {
10221030
Object result = handler.handleStatus(status, null);
10231031
if (result instanceof Boolean resultValue && resultValue) {
1024-
p = exec(cmdLine, null);
1032+
return exec(cmdLine, null);
10251033
}
10261034
}
10271035
}
1028-
return p;
1036+
return null;
10291037
}
10301038

10311039
// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
10321040
private static final int WINDOWS_MAX_PATH = 258;
10331041

10341042
private static File shortenWindowsPath(File path) {
1043+
if (path == null) {
1044+
return null;
1045+
}
10351046
if (path.getPath().length() > WINDOWS_MAX_PATH && Platform.OS.isWindows()) {
10361047
// When spawning new processes on Windows, there is no uniform way
10371048
// to use long working directory paths that exceed the default path
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Christoph Läubrich and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Christoph Läubrich - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.debug.core;
15+
16+
import java.io.File;
17+
18+
import org.eclipse.core.runtime.CoreException;
19+
20+
/**
21+
* A {@link ExecFactory} can be used to control how Eclipse forks a
22+
* new {@link Process}. As this is a global behavior, only one factory can be
23+
* set for the whole application lifetime.
24+
*
25+
* @since 3.23
26+
*/
27+
public interface ExecFactory {
28+
29+
Process exec(String[] cmdLine, File workingDirectory, String[] envp, boolean mergeOutput) throws CoreException;
30+
31+
static void setDefault(ExecFactory factory) {
32+
synchronized (DebugPlugin.class) {
33+
if (DebugPlugin.factory != null) {
34+
throw new IllegalStateException("A factory was already set for this application"); //$NON-NLS-1$
35+
}
36+
DebugPlugin.factory = factory;
37+
}
38+
}
39+
40+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<classpath>
3+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21"/>
4+
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
5+
<classpathentry kind="src" path="src"/>
6+
<classpathentry kind="output" path="bin"/>
7+
</classpath>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<projectDescription>
3+
<name>org.eclipse.debug.terminal</name>
4+
<comment></comment>
5+
<projects>
6+
</projects>
7+
<buildSpec>
8+
<buildCommand>
9+
<name>org.eclipse.jdt.core.javabuilder</name>
10+
<arguments>
11+
</arguments>
12+
</buildCommand>
13+
<buildCommand>
14+
<name>org.eclipse.pde.ManifestBuilder</name>
15+
<arguments>
16+
</arguments>
17+
</buildCommand>
18+
<buildCommand>
19+
<name>org.eclipse.pde.SchemaBuilder</name>
20+
<arguments>
21+
</arguments>
22+
</buildCommand>
23+
</buildSpec>
24+
<natures>
25+
<nature>org.eclipse.pde.PluginNature</nature>
26+
<nature>org.eclipse.jdt.core.javanature</nature>
27+
</natures>
28+
</projectDescription>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
eclipse.preferences.version=1
2+
encoding/<project>=UTF-8
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
eclipse.preferences.version=1
2+
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
3+
org.eclipse.jdt.core.compiler.compliance=21
4+
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
5+
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
6+
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
7+
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
8+
org.eclipse.jdt.core.compiler.release=enabled
9+
org.eclipse.jdt.core.compiler.source=21
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Manifest-Version: 1.0
2+
Bundle-ManifestVersion: 2
3+
Bundle-Name: Terminal Session Support for Eclipse
4+
Bundle-SymbolicName: org.eclipse.debug.terminal
5+
Bundle-Version: 1.0.0.qualifier
6+
Import-Package: com.pty4j;version="0.13.2"
7+
Bundle-Activator: org.eclipse.debug.terminal.Activator
8+
Require-Bundle: org.eclipse.core.runtime,
9+
org.eclipse.debug.core;bundle-version="3.23.0"
10+
Bundle-RequiredExecutionEnvironment: JavaSE-21
11+
Automatic-Module-Name: org.eclipse.debug.terminal
12+
Bundle-ActivationPolicy: lazy
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source.. = src/
2+
output.. = bin/
3+
bin.includes = META-INF/,\
4+
.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Christoph Läubrich and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Christoph Läubrich - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.debug.terminal;
15+
16+
import org.eclipse.debug.core.ExecFactory;
17+
import org.osgi.framework.BundleActivator;
18+
import org.osgi.framework.BundleContext;
19+
20+
public class Activator implements BundleActivator {
21+
22+
private static BundleContext context;
23+
24+
static BundleContext getContext() {
25+
return context;
26+
}
27+
28+
@Override
29+
public void start(BundleContext bundleContext) {
30+
try {
31+
System.out.println("Activate terminal support...");
32+
ExecFactory.setDefault(new Pty4jExecFactory());
33+
} catch (Throwable t) {
34+
System.err.println("Can't activate terminal support");
35+
}
36+
}
37+
38+
@Override
39+
public void stop(BundleContext bundleContext) throws Exception {
40+
Activator.context = null;
41+
}
42+
43+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Christoph Läubrich and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Christoph Läubrich - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.debug.terminal;
15+
16+
import java.io.File;
17+
import java.io.IOException;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
21+
import org.eclipse.core.runtime.CoreException;
22+
import org.eclipse.core.runtime.Status;
23+
import org.eclipse.debug.core.ExecFactory;
24+
25+
import com.pty4j.PtyProcessBuilder;
26+
27+
class Pty4jExecFactory implements ExecFactory {
28+
29+
@Override
30+
public Process exec(String[] cmdLine, File workingDirectory, String[] envp, boolean mergeOutput)
31+
throws CoreException {
32+
Map<String, String> env = new HashMap<>(System.getenv());
33+
env.put("TERM", "xterm-256color");
34+
if (envp != null) {
35+
for (String e : envp) {
36+
int index = e.indexOf('=');
37+
if (index != -1) {
38+
env.put(e.substring(0, index), e.substring(index + 1));
39+
}
40+
}
41+
}
42+
try {
43+
PtyProcessBuilder builder = new PtyProcessBuilder().setRedirectErrorStream(true).setInitialRows(80)
44+
.setInitialColumns(25).setCommand(cmdLine).setEnvironment(env);
45+
if (workingDirectory != null) {
46+
builder.setDirectory(workingDirectory.getAbsolutePath());
47+
}
48+
return builder.start();
49+
} catch (IOException e) {
50+
throw new CoreException(Status.error("Can't start process", e));
51+
} catch (RuntimeException e) {
52+
throw new CoreException(Status.error("Internal error launching process", e));
53+
}
54+
}
55+
56+
}

0 commit comments

Comments
 (0)