Merge pull request #564 from kgtkr/language-server

This commit is contained in:
Ben Fry
2022-11-26 12:25:25 -05:00
committed by GitHub
17 changed files with 712 additions and 21 deletions

View File

@@ -181,8 +181,8 @@
<!--
<antcall target="download-ant" />
<antcall target="download-flatlaf" />
<antcall target="download-jna" />
<antcall target="download-flatlaf" />
-->
</target>

View File

@@ -65,4 +65,4 @@
</library>
</orderEntry>
</component>
</module>
</module>

View File

@@ -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
View File

@@ -3,3 +3,4 @@ bin
bin-test
generated
mode/JavaMode.jar
ivy.jar

View File

@@ -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
View 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
View File

@@ -0,0 +1 @@
*.jar

View File

@@ -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())

View File

@@ -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 =

View File

@@ -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();

View File

@@ -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

View File

@@ -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;

View 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();
}
}

View File

@@ -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
);
});
}
}

View File

@@ -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;
}
}

View File

@@ -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()));
}
}

View File

@@ -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;
}
});
}
}
}