mirror of
https://github.com/processing/processing4.git
synced 2026-01-27 10:21:26 +01:00
Merge pull request #564 from kgtkr/language-server
This commit is contained in:
@@ -181,8 +181,8 @@
|
||||
|
||||
<!--
|
||||
<antcall target="download-ant" />
|
||||
<antcall target="download-flatlaf" />
|
||||
<antcall target="download-jna" />
|
||||
<antcall target="download-flatlaf" />
|
||||
-->
|
||||
</target>
|
||||
|
||||
|
||||
@@ -65,4 +65,4 @@
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
@@ -377,6 +377,11 @@
|
||||
<include name="theme/**" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="${target.path}/modes/java/mode" preservelastmodified="true">
|
||||
<fileset dir="../java/lib">
|
||||
<include name="**/*.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
|
||||
<!-- get the examples folder, but don't require it to buid -->
|
||||
<copy todir="${target.path}/modes/java/examples"
|
||||
|
||||
1
java/.gitignore
vendored
1
java/.gitignore
vendored
@@ -3,3 +3,4 @@ bin
|
||||
bin-test
|
||||
generated
|
||||
mode/JavaMode.jar
|
||||
ivy.jar
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="Java Mode" default="build">
|
||||
<project name="Java Mode" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
|
||||
|
||||
<property name="generated"
|
||||
value="${basedir}/generated/processing/mode/java/preproc" />
|
||||
@@ -18,6 +18,34 @@
|
||||
<classpath path="${antlr_jar}" />
|
||||
</classloader>
|
||||
|
||||
<property name="ivy.version" value="2.5.0" />
|
||||
<fileset id="ivy.files" dir="lib">
|
||||
<include name="ivy.jar" />
|
||||
</fileset>
|
||||
<property name="ivy.url"
|
||||
value="https://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.version}/ivy-${ivy.version}.jar" />
|
||||
|
||||
<available file="ivy.jar" property="ivy.present" />
|
||||
|
||||
<condition property="ivy.ignorable" value="false" else="true">
|
||||
<isset property="ivy.present" />
|
||||
</condition>
|
||||
|
||||
<target name="download-ivy" unless="ivy.present">
|
||||
<get src="${ivy.url}" dest="ivy.jar"
|
||||
ignoreerrors="${ivy.ignorable}"
|
||||
usetimestamp="true" />
|
||||
</target>
|
||||
|
||||
<target name="init-ivy" depends="download-ivy">
|
||||
<taskdef resource="org/apache/ivy/ant/antlib.xml"
|
||||
uri="antlib:org.apache.ivy.ant" classpath="ivy.jar"/>
|
||||
</target>
|
||||
|
||||
<target name="download-ivy-dependencies" depends="init-ivy">
|
||||
<ivy:retrieve />
|
||||
</target>
|
||||
|
||||
<target name="clean" description="Clean the build directories">
|
||||
<delete dir="bin" />
|
||||
<delete dir="bin-test" />
|
||||
@@ -32,6 +60,11 @@
|
||||
<include name="*.properties" />
|
||||
</fileset>
|
||||
</delete>
|
||||
<delete>
|
||||
<fileset dir="lib">
|
||||
<include name="*.jar" />
|
||||
</fileset>
|
||||
</delete>
|
||||
</target>
|
||||
|
||||
<target name="preproc" description="Compile ANTLR 4 grammar">
|
||||
@@ -71,6 +104,8 @@
|
||||
<pathelement location="mode/org.eclipse.jdt.core.jar" />
|
||||
<pathelement location="mode/org.eclipse.osgi.jar" />
|
||||
<pathelement location="mode/org.eclipse.text.jar" />
|
||||
<pathelement location="mode/org.eclipse.ui.workbench.jar" />
|
||||
<fileset dir="lib" includes="*.jar" />
|
||||
</path>
|
||||
|
||||
<path id="classpath.test">
|
||||
@@ -123,7 +158,7 @@
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<target name="test-compile" depends="preproc">
|
||||
<target name="test-compile" depends="preproc, download-ivy-dependencies">
|
||||
<compilecommon srcdir="src; test/processing" destdir="bin-test" classpath="classpath.test" />
|
||||
</target>
|
||||
|
||||
@@ -140,7 +175,7 @@
|
||||
</junit>
|
||||
</target>
|
||||
|
||||
<target name="compile" description="Compile sources" depends="preproc">
|
||||
<target name="compile" description="Compile sources" depends="preproc, download-ivy-dependencies">
|
||||
<compilecommon srcdir="src" destdir="bin" classpath="classpath.base" />
|
||||
</target>
|
||||
|
||||
|
||||
7
java/ivy.xml
Normal file
7
java/ivy.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ivy-module version="2.0">
|
||||
<info organisation="" module="" />
|
||||
<dependencies>
|
||||
<dependency org="org.eclipse.lsp4j" name="org.eclipse.lsp4j" rev="0.12.0"/>
|
||||
</dependencies>
|
||||
</ivy-module>
|
||||
1
java/lib/.gitignore
vendored
Normal file
1
java/lib/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.jar
|
||||
@@ -1762,7 +1762,7 @@ public class CompletionGenerator {
|
||||
}
|
||||
|
||||
|
||||
protected static DefaultListModel<CompletionCandidate> filterPredictions(List<CompletionCandidate> candidates) {
|
||||
public static DefaultListModel<CompletionCandidate> filterPredictions(List<CompletionCandidate> candidates) {
|
||||
Messages.log("* filterPredictions");
|
||||
DefaultListModel<CompletionCandidate> defListModel = new DefaultListModel<>();
|
||||
if (candidates.isEmpty())
|
||||
|
||||
@@ -28,7 +28,7 @@ import processing.app.Language;
|
||||
import processing.app.Problem;
|
||||
|
||||
|
||||
class ErrorChecker {
|
||||
public class ErrorChecker {
|
||||
// Delay delivering error check result after last sketch change
|
||||
// https://github.com/processing/processing/issues/2677
|
||||
private final static long DELAY_BEFORE_UPDATE = 650;
|
||||
@@ -41,11 +41,11 @@ class ErrorChecker {
|
||||
private final Consumer<PreprocSketch> errorHandlerListener =
|
||||
this::handleSketchProblems;
|
||||
|
||||
final private JavaEditor editor;
|
||||
final private Consumer<List<Problem>> editor;
|
||||
final private PreprocService pps;
|
||||
|
||||
|
||||
public ErrorChecker(JavaEditor editor, PreprocService pps) {
|
||||
public ErrorChecker(Consumer<List<Problem>> editor, PreprocService pps) {
|
||||
this.editor = editor;
|
||||
this.pps = pps;
|
||||
|
||||
@@ -69,7 +69,7 @@ class ErrorChecker {
|
||||
pps.registerListener(errorHandlerListener);
|
||||
} else {
|
||||
pps.unregisterListener(errorHandlerListener);
|
||||
editor.setProblemList(Collections.emptyList());
|
||||
editor.accept(Collections.emptyList());
|
||||
nextUiUpdate = 0;
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ class ErrorChecker {
|
||||
long delay = nextUiUpdate - System.currentTimeMillis();
|
||||
Runnable uiUpdater = () -> {
|
||||
if (nextUiUpdate > 0 && System.currentTimeMillis() >= nextUiUpdate) {
|
||||
EventQueue.invokeLater(() -> editor.setProblemList(problems));
|
||||
EventQueue.invokeLater(() -> editor.accept(problems));
|
||||
}
|
||||
};
|
||||
scheduledUiUpdate =
|
||||
|
||||
@@ -134,7 +134,7 @@ public class JavaEditor extends Editor {
|
||||
box.add(textAndError);
|
||||
*/
|
||||
|
||||
preprocService = new PreprocService(this);
|
||||
preprocService = new PreprocService(this.jmode, this.sketch);
|
||||
|
||||
// long t5 = System.currentTimeMillis();
|
||||
|
||||
@@ -146,7 +146,7 @@ public class JavaEditor extends Editor {
|
||||
astViewer = new ASTViewer(this, preprocService);
|
||||
}
|
||||
|
||||
errorChecker = new ErrorChecker(this, preprocService);
|
||||
errorChecker = new ErrorChecker(this::setProblemList, preprocService);
|
||||
|
||||
// long t7 = System.currentTimeMillis();
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@ public class JavaTextArea extends PdeTextArea {
|
||||
}
|
||||
|
||||
|
||||
protected static String parsePhrase(final String lineText) {
|
||||
public static String parsePhrase(final String lineText) {
|
||||
boolean overloading = false;
|
||||
|
||||
{ // Check if we can provide suggestions for this phrase ending
|
||||
|
||||
@@ -78,7 +78,8 @@ public class PreprocService {
|
||||
private final static int TIMEOUT_MILLIS = 100;
|
||||
private final static int BLOCKING_TIMEOUT_SECONDS = 3000;
|
||||
|
||||
protected final JavaEditor editor;
|
||||
protected final JavaMode javaMode;
|
||||
protected final Sketch sketch;
|
||||
|
||||
protected final ASTParser parser = ASTParser.newParser(AST.JLS11);
|
||||
|
||||
@@ -104,8 +105,9 @@ public class PreprocService {
|
||||
* Create a new preprocessing service to support an editor.
|
||||
* @param editor The editor supported by this service and receives issues.
|
||||
*/
|
||||
public PreprocService(JavaEditor editor) {
|
||||
this.editor = editor;
|
||||
public PreprocService(JavaMode javaMode, Sketch sketch) {
|
||||
this.javaMode = javaMode;
|
||||
this.sketch = sketch;
|
||||
|
||||
// Register listeners for first run
|
||||
whenDone(this::fireListeners);
|
||||
@@ -342,8 +344,7 @@ public class PreprocService {
|
||||
List<ImportStatement> codeFolderImports = result.codeFolderImports;
|
||||
List<ImportStatement> programImports = result.programImports;
|
||||
|
||||
JavaMode javaMode = (JavaMode) editor.getMode();
|
||||
Sketch sketch = result.sketch = editor.getSketch();
|
||||
result.sketch = this.sketch;
|
||||
String className = sketch.getMainName();
|
||||
|
||||
StringBuilder workBuffer = new StringBuilder();
|
||||
@@ -385,7 +386,7 @@ public class PreprocService {
|
||||
|
||||
// Core and default imports
|
||||
PdePreprocessor preProcessor =
|
||||
editor.createPreprocessor(editor.getSketch().getMainName());
|
||||
PdePreprocessor.builderFor(this.sketch.getName()).build();
|
||||
if (coreAndDefaultImports == null) {
|
||||
coreAndDefaultImports = buildCoreAndDefaultImports(preProcessor);
|
||||
}
|
||||
@@ -421,7 +422,7 @@ public class PreprocService {
|
||||
final int endNumLines = numLines;
|
||||
|
||||
preprocessorResult.getPreprocessIssues().stream()
|
||||
.map((x) -> ProblemFactory.build(x, tabLineStarts, endNumLines, editor))
|
||||
.map((x) -> ProblemFactory.build(x, tabLineStarts))
|
||||
.forEach(result.otherProblems::add);
|
||||
|
||||
result.hasSyntaxErrors = true;
|
||||
|
||||
26
java/src/processing/mode/java/languageServer/App.java
Normal file
26
java/src/processing/mode/java/languageServer/App.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package processing.mode.java.languageServer;
|
||||
|
||||
import org.eclipse.lsp4j.launch.LSPLauncher;
|
||||
import java.io.File;
|
||||
import java.net.ServerSocket;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class App {
|
||||
public static void main(String[] args) {
|
||||
var input = System.in;
|
||||
var output = System.out;
|
||||
System.setOut(System.err);
|
||||
|
||||
var server = new ProcessingLanguageServer();
|
||||
var launcher =
|
||||
LSPLauncher.createServerLauncher(
|
||||
server,
|
||||
input,
|
||||
output
|
||||
);
|
||||
var client = launcher.getRemoteProxy();
|
||||
server.connect(client);
|
||||
launcher.startListening();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package processing.mode.java.languageServer;
|
||||
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import org.eclipse.lsp4j.services.TextDocumentService;
|
||||
import org.eclipse.lsp4j.services.WorkspaceService;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.ServerCapabilities;
|
||||
import org.eclipse.lsp4j.TextDocumentSyncKind;
|
||||
import org.eclipse.lsp4j.CompletionOptions;
|
||||
import org.eclipse.lsp4j.CompletionParams;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
|
||||
import java.util.List;
|
||||
import processing.app.Base;
|
||||
import processing.app.Platform;
|
||||
import processing.app.Console;
|
||||
import processing.app.Language;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.contrib.ModeContribution;
|
||||
import processing.mode.java.JavaMode;
|
||||
import java.io.File;
|
||||
import processing.app.Sketch;
|
||||
import processing.mode.java.JavaBuild;
|
||||
import processing.mode.java.CompletionGenerator;
|
||||
import processing.mode.java.PreprocService;
|
||||
import org.eclipse.lsp4j.WorkspaceFoldersOptions;
|
||||
import org.eclipse.lsp4j.services.LanguageClientAware;
|
||||
import org.eclipse.lsp4j.services.LanguageClient;
|
||||
import processing.mode.java.ErrorChecker;
|
||||
import processing.app.Problem;
|
||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.DiagnosticSeverity;
|
||||
import processing.mode.java.PreprocSketch;
|
||||
import processing.mode.java.JavaTextArea;
|
||||
import java.util.Collections;
|
||||
import processing.mode.java.CompletionCandidate;
|
||||
import javax.swing.DefaultListModel;
|
||||
import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.jsoup.Jsoup;
|
||||
import java.net.URI;
|
||||
import processing.app.SketchCode;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import processing.mode.java.AutoFormat;
|
||||
import java.util.Optional;
|
||||
import java.util.HashSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.Map;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
class Offset {
|
||||
int line;
|
||||
int col;
|
||||
|
||||
Offset(int line, int col) {
|
||||
this.line = line;
|
||||
this.col = col;
|
||||
}
|
||||
}
|
||||
|
||||
class ProcessingAdapter {
|
||||
File rootPath;
|
||||
LanguageClient client;
|
||||
JavaMode javaMode;
|
||||
File pdeFile;
|
||||
Sketch sketch;
|
||||
CompletionGenerator completionGenerator;
|
||||
PreprocService preprocService;
|
||||
ErrorChecker errorChecker;
|
||||
CompletableFuture<PreprocSketch> cps;
|
||||
CompletionGenerator suggestionGenerator;
|
||||
Set<URI> prevDiagnosticReportUris = new HashSet<URI>();
|
||||
|
||||
|
||||
ProcessingAdapter(File rootPath, LanguageClient client) {
|
||||
this.rootPath = rootPath;
|
||||
this.client = client;
|
||||
this.javaMode = (JavaMode) ModeContribution
|
||||
.load(
|
||||
null,
|
||||
Platform.getContentFile("modes/java"),
|
||||
"processing.mode.java.JavaMode"
|
||||
)
|
||||
.getMode();
|
||||
this.pdeFile = new File(rootPath, rootPath.getName() + ".pde");
|
||||
this.sketch = new Sketch(pdeFile.toString(), javaMode);
|
||||
this.completionGenerator = new CompletionGenerator(javaMode);
|
||||
this.preprocService = new PreprocService(javaMode, sketch);
|
||||
this.errorChecker = new ErrorChecker(
|
||||
this::updateProblems,
|
||||
preprocService
|
||||
);
|
||||
this.cps = CompletableFutures.computeAsync(_x -> {
|
||||
throw new RuntimeException("unreachable");
|
||||
});
|
||||
this.suggestionGenerator = new CompletionGenerator(this.javaMode);
|
||||
|
||||
this.notifySketchChanged();
|
||||
}
|
||||
|
||||
static Optional<File> uriToPath(URI uri) {
|
||||
try {
|
||||
return Optional.of(new File(uri));
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
static URI pathToUri(File path) {
|
||||
return path.toURI();
|
||||
}
|
||||
|
||||
|
||||
static Offset toLineCol(String s, int offset) {
|
||||
int line = (int)s.substring(0, offset).chars().filter(c -> c == '\n').count();
|
||||
int col = offset - s.substring(0, offset).lastIndexOf('\n');
|
||||
return new Offset(line, col);
|
||||
}
|
||||
|
||||
static void init() {
|
||||
Base.setCommandLine();
|
||||
Platform.init();
|
||||
Preferences.init();
|
||||
}
|
||||
|
||||
void notifySketchChanged() {
|
||||
CompletableFuture<PreprocSketch> cps = new CompletableFuture<PreprocSketch>();
|
||||
this.cps = cps;
|
||||
preprocService.notifySketchChanged();
|
||||
errorChecker.notifySketchChanged();
|
||||
preprocService.whenDone(ps -> {
|
||||
cps.complete(ps);
|
||||
});
|
||||
}
|
||||
|
||||
Optional<SketchCode> findCodeByUri(URI uri) {
|
||||
return ProcessingAdapter.uriToPath(uri)
|
||||
.flatMap(path -> Arrays.stream(sketch.getCode())
|
||||
.filter(code -> code.getFile().equals(path))
|
||||
.findFirst()
|
||||
);
|
||||
}
|
||||
|
||||
void updateProblems(List<Problem> probs) {
|
||||
Map<URI, List<Diagnostic>> dias = probs.stream()
|
||||
.map(prob -> {
|
||||
SketchCode code = sketch.getCode(prob.getTabIndex());
|
||||
Diagnostic dia = new Diagnostic(
|
||||
new Range(
|
||||
new Position(
|
||||
prob.getLineNumber(),
|
||||
ProcessingAdapter
|
||||
.toLineCol(code.getProgram(), prob.getStartOffset())
|
||||
.col - 1
|
||||
),
|
||||
new Position(
|
||||
prob.getLineNumber(),
|
||||
ProcessingAdapter
|
||||
.toLineCol(code.getProgram(), prob.getStopOffset())
|
||||
.col - 1
|
||||
)
|
||||
),
|
||||
prob.getMessage()
|
||||
);
|
||||
dia.setSeverity(
|
||||
prob.isError()
|
||||
? DiagnosticSeverity.Error
|
||||
: DiagnosticSeverity.Warning
|
||||
);
|
||||
return new AbstractMap.SimpleEntry<URI, Diagnostic>(
|
||||
ProcessingAdapter.pathToUri(code.getFile()),
|
||||
dia
|
||||
);
|
||||
})
|
||||
.collect(Collectors.groupingBy(
|
||||
AbstractMap.SimpleEntry::getKey,
|
||||
Collectors.mapping(
|
||||
AbstractMap.SimpleEntry::getValue,
|
||||
Collectors.toList()
|
||||
)
|
||||
));
|
||||
|
||||
for (Map.Entry<URI, List<Diagnostic>> entry : dias.entrySet()) {
|
||||
PublishDiagnosticsParams params = new PublishDiagnosticsParams();
|
||||
params.setUri(entry.getKey().toString());
|
||||
params.setDiagnostics(entry.getValue());
|
||||
client.publishDiagnostics(params);
|
||||
}
|
||||
|
||||
for (URI uri : prevDiagnosticReportUris) {
|
||||
if (!dias.containsKey(uri)) {
|
||||
PublishDiagnosticsParams params = new PublishDiagnosticsParams();
|
||||
params.setUri(uri.toString());
|
||||
params.setDiagnostics(Collections.emptyList());
|
||||
client.publishDiagnostics(params);
|
||||
}
|
||||
}
|
||||
prevDiagnosticReportUris = dias.keySet();
|
||||
}
|
||||
|
||||
CompletionItem convertCompletionCandidate(CompletionCandidate c) {
|
||||
CompletionItem item = new CompletionItem();
|
||||
item.setLabel(c.getElementName());
|
||||
item.setInsertTextFormat(InsertTextFormat.Snippet);
|
||||
String insert = c.getCompletionString();
|
||||
if (insert.contains("( )")) {
|
||||
insert = insert.replace("( )", "($1)");
|
||||
} else if (insert.contains(",")) {
|
||||
int n = 1;
|
||||
char[] chs = insert.replace("(,", "($1,").toCharArray();
|
||||
insert = "";
|
||||
for (char ch : chs) {
|
||||
switch (ch) {
|
||||
case ',': {
|
||||
n += 1;
|
||||
insert += ",$" + n;
|
||||
}
|
||||
default: insert += ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
item.setInsertText(insert);
|
||||
CompletionItemKind kind;
|
||||
switch (c.getType()) {
|
||||
case 0: // PREDEF_CLASS
|
||||
kind = CompletionItemKind.Class;
|
||||
break;
|
||||
case 1: // PREDEF_FIELD
|
||||
kind = CompletionItemKind.Constant;
|
||||
break;
|
||||
case 2: // PREDEF_METHOD
|
||||
kind = CompletionItemKind.Function;
|
||||
break;
|
||||
case 3: // LOCAL_CLASS
|
||||
kind = CompletionItemKind.Class;
|
||||
break;
|
||||
case 4: // LOCAL_METHOD
|
||||
kind = CompletionItemKind.Method;
|
||||
break;
|
||||
case 5: // LOCAL_FIELD
|
||||
kind = CompletionItemKind.Field;
|
||||
break;
|
||||
case 6: // LOCAL_VARIABLE
|
||||
kind = CompletionItemKind.Variable;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown completion type: " + c.getType());
|
||||
}
|
||||
item.setKind(kind);
|
||||
item.setDetail(Jsoup.parse(c.getLabel()).text());
|
||||
return item;
|
||||
}
|
||||
|
||||
Optional<String> parsePhrase(String text) {
|
||||
return Optional.ofNullable(JavaTextArea.parsePhrase(text));
|
||||
}
|
||||
|
||||
List<CompletionCandidate> filterPredictions(
|
||||
List<CompletionCandidate> candidates
|
||||
) {
|
||||
return Collections.list(CompletionGenerator.filterPredictions(candidates).elements());
|
||||
}
|
||||
|
||||
CompletableFuture<List<CompletionItem>> generateCompletion(
|
||||
URI uri,
|
||||
int line,
|
||||
int col
|
||||
) {
|
||||
return cps.thenApply(ps -> {
|
||||
Optional<List<CompletionItem>> result =
|
||||
findCodeByUri(uri)
|
||||
.flatMap(code -> {
|
||||
int codeIndex = IntStream.range(0, sketch.getCodeCount())
|
||||
.filter(i -> sketch.getCode(i).equals(code))
|
||||
.findFirst()
|
||||
.getAsInt();
|
||||
int lineStartOffset = String.join(
|
||||
"\n",
|
||||
Arrays.copyOfRange(code.getProgram().split("\n"), 0, line + 1)
|
||||
)
|
||||
.length();
|
||||
int lineNumber = ps.tabOffsetToJavaLine(codeIndex, lineStartOffset);
|
||||
|
||||
String text = code.getProgram()
|
||||
.split("\n")[line] // TODO: 範囲外のエラー処理
|
||||
.substring(0, col);
|
||||
return parsePhrase(text)
|
||||
.map(phrase -> {
|
||||
System.out.println("phrase: " + phrase);
|
||||
System.out.println("lineNumber: " + lineNumber);
|
||||
return Optional.ofNullable(
|
||||
suggestionGenerator
|
||||
.preparePredictions(ps, phrase, lineNumber)
|
||||
)
|
||||
.filter(x -> !x.isEmpty())
|
||||
.map(candidates -> {
|
||||
Collections.sort(candidates);
|
||||
System.out.println("candidates: " + candidates);
|
||||
List<CompletionCandidate> filtered = filterPredictions(candidates);
|
||||
System.out.println("filtered: " + filtered);
|
||||
return filtered.stream()
|
||||
.map(this::convertCompletionCandidate)
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
})
|
||||
.orElse(Optional.empty());
|
||||
});
|
||||
|
||||
return result.orElse(Collections.emptyList());
|
||||
});
|
||||
}
|
||||
|
||||
void onChange(URI uri, String text) {
|
||||
findCodeByUri(uri)
|
||||
.ifPresent(code -> {
|
||||
code.setProgram(text);
|
||||
notifySketchChanged();
|
||||
});
|
||||
}
|
||||
|
||||
Optional<TextEdit> format(URI uri) {
|
||||
return findCodeByUri(uri)
|
||||
.map(SketchCode::getProgram)
|
||||
.map(code -> {
|
||||
String newCode = new AutoFormat().format(code);
|
||||
Offset end = ProcessingAdapter.toLineCol(code, code.length());
|
||||
return new TextEdit(
|
||||
new Range(
|
||||
new Position(0, 0),
|
||||
new Position(end.line, end.col)
|
||||
),
|
||||
newCode
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package processing.mode.java.languageServer;
|
||||
|
||||
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import org.eclipse.lsp4j.services.TextDocumentService;
|
||||
import org.eclipse.lsp4j.services.WorkspaceService;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.ServerCapabilities;
|
||||
import org.eclipse.lsp4j.TextDocumentSyncKind;
|
||||
import org.eclipse.lsp4j.CompletionOptions;
|
||||
import org.eclipse.lsp4j.CompletionParams;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
|
||||
import processing.app.Base;
|
||||
import processing.app.Platform;
|
||||
import processing.app.Console;
|
||||
import processing.app.Language;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.contrib.ModeContribution;
|
||||
import processing.mode.java.JavaMode;
|
||||
import java.io.File;
|
||||
import processing.app.Sketch;
|
||||
import processing.mode.java.JavaBuild;
|
||||
import processing.mode.java.CompletionGenerator;
|
||||
import processing.mode.java.PreprocService;
|
||||
import org.eclipse.lsp4j.WorkspaceFoldersOptions;
|
||||
import org.eclipse.lsp4j.services.LanguageClientAware;
|
||||
import org.eclipse.lsp4j.services.LanguageClient;
|
||||
import processing.mode.java.ErrorChecker;
|
||||
import processing.app.Problem;
|
||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.DiagnosticSeverity;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import java.util.HashMap;
|
||||
import java.util.Arrays;
|
||||
|
||||
class ProcessingLanguageServer implements LanguageServer, LanguageClientAware {
|
||||
static Optional<String> lowerExtension(File file) {
|
||||
String s = file.toString();
|
||||
int dot = s.lastIndexOf('.');
|
||||
if (dot == -1) return Optional.empty();
|
||||
else return Optional.of(s.substring(dot + 1).toLowerCase());
|
||||
}
|
||||
|
||||
HashMap<File, ProcessingAdapter> adapters = new HashMap<>();
|
||||
LanguageClient client = null;
|
||||
ProcessingTextDocumentService textDocumentService = new ProcessingTextDocumentService(this);
|
||||
ProcessingWorkspaceService workspaceService = new ProcessingWorkspaceService(this);
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
System.out.println("exit");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextDocumentService getTextDocumentService() {
|
||||
return textDocumentService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkspaceService getWorkspaceService() {
|
||||
return workspaceService;
|
||||
}
|
||||
|
||||
Optional<ProcessingAdapter> getAdapter(URI uri) {
|
||||
return ProcessingAdapter.uriToPath(uri).filter(file -> {
|
||||
String ext = lowerExtension(file).orElse("");
|
||||
return ext.equals("pde") || ext.equals("java");
|
||||
}).map(file -> {
|
||||
File rootDir = file.getParentFile();
|
||||
return adapters.computeIfAbsent(rootDir, _k -> new ProcessingAdapter(rootDir, client));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
|
||||
ProcessingAdapter.init();
|
||||
System.out.println("initialize");
|
||||
var capabilities = new ServerCapabilities();
|
||||
capabilities.setTextDocumentSync(TextDocumentSyncKind.Full);
|
||||
|
||||
var completionOptions = new CompletionOptions();
|
||||
completionOptions.setResolveProvider(true);
|
||||
completionOptions.setTriggerCharacters(
|
||||
Arrays.asList(
|
||||
"."
|
||||
)
|
||||
);
|
||||
capabilities.setCompletionProvider(completionOptions);
|
||||
capabilities.setDocumentFormattingProvider(true);
|
||||
var result = new InitializeResult(capabilities);
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Object> shutdown() {
|
||||
System.out.println("shutdown");
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(LanguageClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package processing.mode.java.languageServer;
|
||||
|
||||
import org.eclipse.lsp4j.services.TextDocumentService;
|
||||
import org.eclipse.lsp4j.CompletionParams;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import java.util.List;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
|
||||
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DocumentFormattingParams;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import java.io.File;
|
||||
import processing.mode.java.AutoFormat;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import java.util.Collections;
|
||||
import processing.mode.java.CompletionGenerator;
|
||||
import processing.mode.java.JavaTextArea;
|
||||
import java.util.Arrays;
|
||||
import processing.mode.java.CompletionCandidate;
|
||||
import javax.swing.DefaultListModel;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.eclipse.lsp4j.MarkupContent;
|
||||
import org.eclipse.lsp4j.MarkupKind;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import java.net.URI;
|
||||
|
||||
class ProcessingTextDocumentService implements TextDocumentService {
|
||||
ProcessingLanguageServer pls;
|
||||
ProcessingTextDocumentService(ProcessingLanguageServer pls) {
|
||||
this.pls = pls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChange(DidChangeTextDocumentParams params) {
|
||||
System.out.println("didChange");
|
||||
URI uri = URI.create(params.getTextDocument().getUri());
|
||||
pls.getAdapter(uri).ifPresent(adapter -> {
|
||||
var change = params.getContentChanges().get(0);
|
||||
adapter.onChange(uri, change.getText());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didClose(DidCloseTextDocumentParams params) {
|
||||
System.out.println("didClose");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didOpen(DidOpenTextDocumentParams params) {
|
||||
System.out.println("didOpen");
|
||||
URI uri = URI.create(params.getTextDocument().getUri());
|
||||
pls.getAdapter(uri).ifPresent(adapter -> {
|
||||
adapter.onChange(uri, params.getTextDocument().getText());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didSave(DidSaveTextDocumentParams params) {
|
||||
System.out.println("didSave");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
|
||||
System.out.println("completion");
|
||||
URI uri = URI.create(params.getTextDocument().getUri());
|
||||
return pls.getAdapter(uri).map(adapter -> {
|
||||
CompletableFuture<Either<List<CompletionItem>, CompletionList>> result = adapter.generateCompletion(
|
||||
uri,
|
||||
params.getPosition().getLine(),
|
||||
params.getPosition().getCharacter()
|
||||
).thenApply(Either::forLeft);
|
||||
return result;
|
||||
})
|
||||
.orElse(CompletableFutures.computeAsync(_x -> Either.forLeft(Collections.emptyList())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem params) {
|
||||
System.out.println("resolveCompletionItem");
|
||||
return CompletableFutures.computeAsync(_x -> {
|
||||
return params;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
|
||||
System.out.println("formatting");
|
||||
URI uri = URI.create(params.getTextDocument().getUri());
|
||||
return pls.getAdapter(uri).map(adapter -> {
|
||||
CompletableFuture<List<? extends TextEdit>> result = CompletableFutures.computeAsync(_x -> {
|
||||
return adapter.format(uri).map(Collections::singletonList).orElse(Collections.emptyList());
|
||||
});
|
||||
return result;
|
||||
})
|
||||
.orElse(CompletableFuture.completedFuture(Collections.emptyList()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package processing.mode.java.languageServer;
|
||||
|
||||
import org.eclipse.lsp4j.services.WorkspaceService;
|
||||
import org.eclipse.lsp4j.DidChangeConfigurationParams;
|
||||
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
|
||||
import org.eclipse.lsp4j.FileChangeType;
|
||||
import java.net.URI;
|
||||
import java.io.IOException;
|
||||
|
||||
class ProcessingWorkspaceService implements WorkspaceService {
|
||||
ProcessingLanguageServer pls;
|
||||
ProcessingWorkspaceService(ProcessingLanguageServer pls) {
|
||||
this.pls = pls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChangeConfiguration(DidChangeConfigurationParams params) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
|
||||
System.out.println("didChangeWatchedFiles: " + params);
|
||||
for (var change : params.getChanges()) {
|
||||
URI uri = URI.create(change.getUri());
|
||||
pls.getAdapter(uri).ifPresent(adapter -> {
|
||||
switch (change.getType()) {
|
||||
case Created:
|
||||
ProcessingAdapter.uriToPath(uri).ifPresent(path -> {
|
||||
adapter.sketch.loadNewTab(path.getName().toString(), "pde", true);
|
||||
adapter.notifySketchChanged();
|
||||
});
|
||||
break;
|
||||
case Changed:
|
||||
adapter.findCodeByUri(uri).ifPresent(code -> {
|
||||
try {
|
||||
code.load();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
adapter.notifySketchChanged();
|
||||
});
|
||||
break;
|
||||
case Deleted:
|
||||
adapter.findCodeByUri(uri).ifPresent(code -> {
|
||||
adapter.sketch.removeCode(code);
|
||||
adapter.notifySketchChanged();
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user