1 Commits

Author SHA1 Message Date
Ruben
48e0bc5605 feat: remove Diagnostic if it has been selected 2025-06-28 10:13:59 +02:00
57 changed files with 1817 additions and 4539 deletions

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -0,0 +1,28 @@
plugins {
id 'java'
id 'org.jetbrains.intellij.platform' version '2.2.1'
}
group = 'org.example'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
maven { url "https://jitpack.io" }
intellijPlatform {
defaultRepositories()
}
}
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
implementation "com.github.Ballerina-Platform.lsp4intellij:lsp4intellij:master-SNAPSHOT"
}
test {
useJUnitPlatform()
}

View File

@@ -1,41 +0,0 @@
plugins {
id("org.jetbrains.intellij") version "1.17.3"
kotlin("jvm") version "1.9.0"
}
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation(kotlin("stdlib"))
implementation("com.github.ballerina-platform:lsp4intellij:0.9.0")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "17"
}
}
intellij {
version.set("2023.3")
}
tasks {
patchPluginXml {
sinceBuild.set("233")
untilBuild.set("233.*")
}
}

View File

@@ -1,13 +0,0 @@
# Installation des Intellij Language Clients
Um den Language Client für Intellij installieren zu können, kann die Erweiterung
[LSP4IJ](https://plugins.jetbrains.com/plugin/23257-lsp4ij) installiert werden.
## Installation
1. Installiere [LSP4IJ](https://plugins.jetbrains.com/plugin/23257-lsp4ij)
2. Öffne den Language Server Tab
3. Füge einen neuen Language-Server hinzu
4. Vergebe einen Namen und füge folgenden Command hinzu: `java -jar "<path/to/LanguageServer.jar>`
5. Füge unter Mappings -> File name patterns *.jav und Language ID java_tx hinzu
6. öffne eine Datei mit Dateiendung .jav
7. Fertig

View File

@@ -0,0 +1,17 @@
package org.example;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
public static void main(String[] args) {
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
// to see how IntelliJ IDEA suggests fixing it.
System.out.printf("Hello and welcome!");
for (int i = 1; i <= 5; i++) {
//TIP Press <shortcut actionId="Debug"/> to start debugging your code. We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.
System.out.println("i = " + i);
}
}
}

View File

@@ -1,14 +0,0 @@
package kotlin.com.example.lsp;
import com.intellij.openapi.application.PreloadingActivity;
import com.intellij.openapi.progress.ProgressIndicator;
import org.wso2.lsp4intellij.IntellijLanguageClient;
import org.wso2.lsp4intellij.client.languageserver.serverdefinition.RawCommandServerDefinition;
public class PreloadActivityTX extends PreloadingActivity {
@Override
public void preload(ProgressIndicator indicator) {
String[] command = new String[]{"java", "-jar", "/JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar"};
IntellijLanguageClient.addServerDefinition(new RawCommandServerDefinition("jav", command));
}
}

View File

@@ -13,7 +13,7 @@ see also jetbrains documentation: https://plugins.jetbrains.com/docs/intellij/pl
<extensions defaultExtensionNs="com.intellij">
<!-- register a preloading activity. You need to init IntellijLanguageClient with your config, see readme -->
<preloadingActivity implementation="kotlin.com.example.lsp.PreloadActivityTX" id="kotlin.com.example.lsp.PreloadActivityTX"/>
<preloadingActivity implementation="your.plugin.MyPreloadingActivity" id="your.plugin.MyPreloadingActivity"/>
<!-- register intellijLanguageClient as a Service OR as a plugin component (see readme)... -->
<applicationService serviceImplementation="org.wso2.lsp4intellij.IntellijLanguageClient"/>

View File

@@ -1,3 +1,3 @@
# Java-TX Language Server
# lspclient README
This is the official Plugin for Java-TX.
This is the README for the Java-TX LSP Client.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 902 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,8 @@
{
"icon": "TX.png",
"name": "java-tx-language-extension",
"displayName": "Java-TX Language Extension",
"description": "The Language Extension for Java-TX with Typehints and Syntax Checks",
"version": "0.0.16",
"name": "lspclient",
"displayName": "LSPClient",
"description": "",
"version": "0.0.1",
"engines": {
"vscode": "^1.94.0"
},
@@ -17,8 +16,8 @@
"contributes": {
"commands": [
{
"command": "tx.restartLanguageServer",
"title": "TX: Restart Language Server"
"command": "lspclient.helloWorld",
"title": "Hello World"
}
]
},
@@ -42,7 +41,6 @@
"typescript": "^5.6.2"
},
"dependencies": {
"@vscode/vsce": "^3.6.2",
"vscode-languageclient": "^9.0.1"
}
}

View File

@@ -1,70 +1,78 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import { workspace, ExtensionContext } from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
} from 'vscode-languageclient/node';
let client: LanguageClient | undefined;
function createClient(context: vscode.ExtensionContext): LanguageClient {
const extensionPath = context.extensionPath;
const jarPath = extensionPath + '/JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar';
const serverOptions: ServerOptions = {
run: { command: 'java', args: ['-Xss10m', '-jar', jarPath] },
debug: { command: 'java', args: ['-Xss10m', '-jar', jarPath] }
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', pattern: '**/*.jav' }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.jav')
},
revealOutputChannelOn: 4
};
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
return new LanguageClient(
'javaTxLanguageServer',
'Java-TX Language Server',
serverOptions,
clientOptions
);
const workspaceFolder = context.extensionPath;
const serverOptions: ServerOptions = {
run: {
command: 'java',
args: ['-jar', workspaceFolder + "/JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar"], // Absolute Pfadangabe
},
debug: {
command: 'java',
args: ['-jar', workspaceFolder + "/JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar"], // Absolute Pfadangabe für Debug
}
};
console.log("SERVER CREATED.")
console.log(workspaceFolder)
// Clientoptionen: definiere, welche Dateitypen der Client unterstützt
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'java' }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.jav')
},
revealOutputChannelOn: 4
};
// Language Client erstellen und starten
const client = new LanguageClient(
'javaTxLanguageServer', // ID des Clients
'Java-TX Language Server', // Name des Clients
serverOptions,
clientOptions
);
// Client starten
client.start().then(() => {
console.log("Language Client erfolgreich gestartet");
}).catch(error => {
console.error("Fehler beim Starten des Language Clients:", error);
});
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "tx" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
const disposable = vscode.commands.registerCommand('lspclient.helloWorld', () => {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World from TX!');
});
context.subscriptions.push(disposable);
}
export async function activate(context: vscode.ExtensionContext) {
client = createClient(context);
try {
await client.start();
console.log('Java-TX Language Server started successfully.');
} catch (err) {
console.error('Failed to start Language Client:', err);
}
const restart = vscode.commands.registerCommand('tx.restartLanguageServer', async () => {
if (!client) {
vscode.window.showWarningMessage('Language Client is not initialized.');
return;
}
try {
await client.stop();
client.dispose();
} catch (e) {
console.error('Error stopping Language Client:', e);
}
client = createClient(context);
try {
await client.start();
vscode.window.showInformationMessage('Java-TX Language Server restarted.');
} catch (e) {
console.error('Error restarting Language Client:', e);
vscode.window.showErrorMessage('Failed to restart Language Server.');
}
});
context.subscriptions.push(restart);
}
export function deactivate(): Thenable<void> | undefined {
return client?.stop();
}
// This method is called when your extension is deactivated
export function deactivate() { }

View File

@@ -1,44 +1 @@
# Welcome to your VS Code Extension
## What's in the folder
* This folder contains all of the files necessary for your extension.
* `package.json` - this is the manifest file in which you declare your extension and command.
* The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesnt yet need to load the plugin.
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
* The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
* We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
## Get up and running straight away
* Press `F5` to open a new window with your extension loaded.
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
* Find output from your extension in the debug console.
## Make changes
* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
## Explore the API
* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
## Run tests
* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner)
* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered.
* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A`
* See the output of the test result in the Test Results view.
* Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder.
* The provided test runner will only consider files matching the name pattern `**.test.ts`.
* You can create folders inside the `test` folder to structure your tests any way you want.
## Go further
* [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns.
* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace.
* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
* Integrate to the [report issue](https://code.visualstudio.com/api/get-started/wrapping-up#issue-reporting) flow to get issue and feature requests reported by users.
LSP-Client for Java-TX

View File

@@ -20,11 +20,6 @@
<artifactId>antlr4</artifactId>
<version>4.11.1</version>
</dependency>
<dependency>
<groupId>io.github.java-diff-utils</groupId>
<artifactId>java-diff-utils</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
@@ -80,12 +75,6 @@
<version>0.1</version>
</dependency>
</dependencies>
<properties>
<!-- Setzt Java 21 als globale Version -->
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
</properties>
<build>
<plugins>
@@ -107,7 +96,6 @@
<configuration>
<source>21</source>
<target>21</target>
<release>21</release>
</configuration>
</plugin>

View File

@@ -1,49 +1,81 @@
package de.dhbw;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.eclipse.lsp4j.services.LanguageServer;
import java.net.URI;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
*
* Configuration of the Language Server
*
* */
public class JavaTXLanguageServer implements LanguageServer {
private static final Logger logger = LogManager.getLogger(JavaTXLanguageServer.class);
private LanguageClient client;
public void connect(LanguageClient client) {
this.client = client;
textDocumentService.setClient(client);
}
private final JavaTXTextDocumentService textDocumentService = new JavaTXTextDocumentService();
private final JavaTXWorkspaceService workspaceService = new JavaTXWorkspaceService();
public void connect(LanguageClient client) {
textDocumentService.setClient(client);
@Override
public void setTrace(SetTraceParams params) {
}
/**
* Configure the Features of the LanguageServer
* */
@Override
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
ServerCapabilities capabilities = new ServerCapabilities();
capabilities.setDocumentFormattingProvider(true);
capabilities.setTextDocumentSync(TextDocumentSyncKind.Full);
capabilities.setHoverProvider(false);
capabilities.setInlayHintProvider(true);
capabilities.setCodeActionProvider(true);
capabilities.setTextDocumentSync(TextDocumentSyncKind.Full);
capabilities.setCompletionProvider(new CompletionOptions(true, List.of()));
capabilities.setWorkspaceSymbolProvider(false);
if (params.getWorkspaceFolders() != null && !params.getWorkspaceFolders().isEmpty()) {
if(params.getWorkspaceFolders() != null && !params.getWorkspaceFolders().isEmpty()) {
textDocumentService.setFileRoot(params.getWorkspaceFolders());
}
return CompletableFuture.completedFuture(new InitializeResult(capabilities));
return CompletableFuture.supplyAsync(() -> new InitializeResult(capabilities));
}
/**
* @return the TextDocumentService
* */
@Override
public TextDocumentService getTextDocumentService() {
return textDocumentService;
}
/**
* @return the WorkspaceService
* */
@Override
public WorkspaceService getWorkspaceService() {
return workspaceService;
}
/**
* @return the Client
* */
public LanguageClient getClient() {
return client;
}
@Override
public CompletableFuture<Object> shutdown() {
return CompletableFuture.completedFuture(null);

View File

@@ -1,15 +1,30 @@
package de.dhbw;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.LanguageClient;
/**
*
* Start the JavaTX language Server and use System In and System Out for Communication
*
* */
public class JavaTXLanguageServerLauncher {
public static void main(String[] args) throws Exception {
JavaTXLanguageServer server = new JavaTXLanguageServer();
var launcher = LSPLauncher.createServerLauncher(server, System.in, System.out);
LanguageClient client = launcher.getRemoteProxy();
server.connect(client);
launcher.startListening().get();
private static final Logger logger = LogManager.getLogger(JavaTXLanguageServerLauncher.class);
public static void main(String[] args) {
try {
JavaTXLanguageServer server = new JavaTXLanguageServer();
var launcher = LSPLauncher.createServerLauncher(server, System.in, System.out);
LanguageClient client = launcher.getRemoteProxy();
server.connect(client);
launcher.startListening().get();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -1,144 +1,741 @@
package de.dhbw;
import de.dhbw.handler.ChangeHandler;
import de.dhbw.handler.CodeActionHandler;
import de.dhbw.handler.FormattingHandler;
import de.dhbw.handler.SaveHandler;
import com.google.common.base.Stopwatch;
import de.dhbw.handler.UpdatePositionHandler;
import de.dhbw.helper.CodeSnippetOptions;
import de.dhbw.helper.ConversionHelper;
import de.dhbw.helper.TextHelper;
import de.dhbw.helper.TypeResolver;
import de.dhbw.model.LSPVariable;
import de.dhbw.model.LineCharPosition;
import de.dhbw.model.SnippetWithName;
import de.dhbw.service.CacheService;
import de.dhbw.service.ClientService;
import de.dhbw.service.DocumentStore;
import de.dhbw.service.LogService;
import de.dhbw.service.ParserService;
import de.dhbw.model.Type;
import de.dhbwstuttgart.languageServerInterface.LanguageServerInterface;
import de.dhbwstuttgart.languageServerInterface.ParserInterface;
import de.dhbwstuttgart.languageServerInterface.model.ParserError;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.TextDocumentService;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JavaTXTextDocumentService implements TextDocumentService {
/**
* Handles Actions in Documents, such as Autocompletion, Change-Events and Syntax-Checks
*/
public class JavaTXTextDocumentService implements org.eclipse.lsp4j.services.TextDocumentService {
private final FormattingHandler formattingHandler;
private final SaveHandler saveHandler;
private final ChangeHandler changeHandler;
private final CodeActionHandler codeActionHandler;
private final ParserService parserService;
private final DocumentStore documentStore;
private final LogService logService;
private final ClientService clientService;
private final CacheService cacheService;
private final CodeSnippetOptions codeSnippetOptions;
private final TypeResolver typeResolver;
private LanguageClient client;
public JavaTXTextDocumentService() {
TextHelper textHelper = new TextHelper();
this.cacheService = new CacheService();
this.clientService = new ClientService(null);
this.typeResolver = new TypeResolver();
this.documentStore = new DocumentStore();
this.conversionHelper = new ConversionHelper(textHelper, documentStore);
this.logService = new LogService(clientService);
this.formattingHandler = new FormattingHandler(documentStore);
this.parserService = new ParserService(conversionHelper);
this.codeActionHandler = new CodeActionHandler(documentStore, typeResolver, logService, conversionHelper);
this.saveHandler = new SaveHandler(typeResolver, documentStore, logService, cacheService, conversionHelper, clientService, parserService);
this.changeHandler = new ChangeHandler(documentStore, parserService, conversionHelper, clientService, typeResolver, cacheService, logService);
this.codeSnippetOptions = new CodeSnippetOptions();
}
private final ConversionHelper conversionHelper;
private static final Logger logger = LogManager.getLogger(JavaTXTextDocumentService.class);
LanguageClient client;
HashMap<String, List<InlayHint>> globalInlayHintMap = new HashMap<>();
Boolean currentlyCalculating = false;
HashMap<String, List<Diagnostic>> globalDiagnosticsMap = new HashMap<>();
HashMap<String, String> textDocuments = new HashMap<>();
CodeSnippetOptions codeSnippetOptions = new CodeSnippetOptions();
TextHelper textHelper = new TextHelper();
Boolean dontShowHints = false;
TypeResolver typeResolver = new TypeResolver();
Path fileRoot = null;
Boolean singleFileOpened = false;
List<LSPVariable> variables = new ArrayList<>();
public void setClient(LanguageClient client) {
this.client = client;
clientService.setClient(client);
}
public void setFileRoot(List<WorkspaceFolder> root) {
if (root != null && !root.isEmpty()) {
Path.of(URI.create(root.getFirst().getUri()));
if (root == null) {
singleFileOpened = true;
}
//TODO: Nicht nur das erste Element nehmen sondern alle beachten
fileRoot = Path.of(URI.create(root.get(0).getUri()));
}
/**
* Handles Completion Events.
* In this case you can select codeSnippets.
*
* @param params the completion Context
* @return the Auto-Completion Items that will be displayed
*/
@Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
List<CompletionItem> completions = new ArrayList<>();
for (SnippetWithName snippet : codeSnippetOptions.getSnippets()) {
CompletionItem item = new CompletionItem(snippet.name());
for (SnippetWithName elem : codeSnippetOptions.getSnippets()) {
CompletionItem item = new CompletionItem(elem.getName());
item.setKind(CompletionItemKind.Snippet);
item.setInsertText(snippet.snippet());
item.setInsertText(elem.getSnippet());
item.setInsertTextFormat(InsertTextFormat.Snippet);
completions.add(item);
}
return CompletableFuture.completedFuture(Either.forLeft(completions));
}
/**
* Handles didOpen-Event.
* Puts the Text-Document in the text-Document Hashmap
*
* @param params the Context of the newly opened Document
*/
@Override
public void didOpen(DidOpenTextDocumentParams params) {
cacheService.reset();
typeResolver.reset();
String uri = params.getTextDocument().getUri();
String text = params.getTextDocument().getText();
List<Diagnostic> syntaxErrors = parserService.getDiagnosticsOfErrors(text, uri);
if (!syntaxErrors.isEmpty()) {
clientService.publishDiagnostics(uri, syntaxErrors);
}
client.refreshDiagnostics();
client.refreshInlayHints();
documentStore.saveFile(uri, text);
textDocuments.put(params.getTextDocument().getUri(), params.getTextDocument().getText());
}
public ArrayList<String> difference(String first, String second) {
ArrayList<String> result = new ArrayList<>();
int i = 0, j = 0;
int startDiff = -1;
while (j < second.length()) {
if (i < first.length() && first.charAt(i) == second.charAt(j)) {
if (startDiff != -1) {
result.add(second.substring(startDiff, j));
startDiff = -1;
}
i++;
j++;
} else {
if (startDiff == -1) {
startDiff = j;
}
j++;
}
}
if (startDiff != -1) {
result.add(second.substring(startDiff));
}
return result;
}
public Map<LineCharPosition, String> differenceLinePos(String first, String second, int line) {
Map<LineCharPosition, String> result = new HashMap<>();
int i = 0, j = 0;
int startDiff = -1;
while (j < second.length()) {
if (i < first.length() && first.charAt(i) == second.charAt(j)) {
if (startDiff != -1) {
String diff = second.substring(startDiff, j);
result.put(new LineCharPosition(line, startDiff), diff);
startDiff = -1;
}
i++;
j++;
} else {
if (startDiff == -1) {
startDiff = j;
}
j++;
}
}
if (startDiff != -1) {
String diff = second.substring(startDiff);
result.put(new LineCharPosition(line, startDiff), diff);
}
return result;
}
/**
* Handles didChange-Event.
* updates textDocument-State on Server and run Syntax-Check. If an Error is found it will get displayed as a Diagnostic.
*
* @param params the Context of the Changed Document, including the incremental change as well as the whole Document
*/
@Override
public void didChange(DidChangeTextDocumentParams params) {
logService.log("[didChange] Client triggered didChange event.", MessageType.Info);
cacheService.reset();
String uri = params.getTextDocument().getUri();
String fileContent = documentStore.getFileContent(uri);
documentStore.saveFile(uri, fileContent);
changeHandler.didChange(params);
log("[didChange] Client triggered didChange Event.", MessageType.Info);
//TODO: Regenerate if Line-Count does not match -> Return new AST and see where the Positions are instead of calculating them from changes in Text-Document. Even possible?
String currentText = textDocuments.get(params.getTextDocument().getUri());
ArrayList<String> currentTextLines = new ArrayList<>(Arrays.stream(currentText.split("\n")).toList());
HashMap<LineCharPosition, String> preciseChanges = new HashMap<>();
String newText = params.getContentChanges().getFirst().getText();
ArrayList<String> newTextLines = new ArrayList<>(Arrays.stream(newText.split("\n")).toList());
ArrayList<Integer> offsetPerLine = new ArrayList<>();
HashMap<Integer, List<String>> textChanges = new HashMap<>();
int index = 0;
for (String newTextLine : newTextLines) {
if (!(currentTextLines.size() > index)) {
offsetPerLine.add(0);
} else {
Map<LineCharPosition, String> lineDiffs = differenceLinePos(currentTextLines.get(index), newTextLine, index);
preciseChanges.putAll(lineDiffs);
textChanges.put(index, difference(currentTextLines.get(index), newTextLine).stream().map(el -> el.replaceAll(" ", "")).toList());
client.logMessage(new MessageParams(MessageType.Info, "For Line " + index + " difference of " + (newTextLine.length() - currentTextLines.get(index).length())));
offsetPerLine.add(newTextLine.length() - currentTextLines.get(index).length());
}
index++;
}
AtomicReference<String> summedUp = new AtomicReference<>("");
params.getContentChanges().forEach(el -> summedUp.set(summedUp.get() + el.getText()));
textDocuments.put(params.getTextDocument().getUri(), summedUp.get());
String input = summedUp.get();
ParserInterface parserInterface = new ParserInterface();
List<ParserError> parserErrors = parserInterface.getParseErrors(input);
List<Diagnostic> diagnosticsList = parserErrors.stream().map(el -> {
Range errorRange = new Range(
new Position(el.getLine() - 1, el.getCharPositionInLine()), // Startposition
new Position(el.getLine() - 1, el.getEndCharPosition()) // Endposition
);
return new Diagnostic(
errorRange,
el.getMsg(),
DiagnosticSeverity.Error,
"JavaTX Language Server"
);
}).toList();
client.publishDiagnostics(new PublishDiagnosticsParams(params.getTextDocument().getUri(), diagnosticsList));
typeResolver.reduceCurrent(preciseChanges, variables, client);
//Reduce and display Typehints and Diagnostics
ArrayList<Diagnostic> diagnostics = new ArrayList<>();
var sWatch = Stopwatch.createUnstarted();
sWatch.start();
try {
if (textDocuments.get(params.getTextDocument().getUri()) == null) {
log("[didSave] Input of Text Document is null in TextDocument-Hashmap.", MessageType.Error);
}
ArrayList<LSPVariable> typesOfMethodAndParameters = typeResolver.infereInput(textDocuments.get(params.getTextDocument().getUri()), params.getTextDocument().getUri());
List<InlayHint> typeHint = new ArrayList<>();
for (var variable : typesOfMethodAndParameters) {
InlayHint inlayHint = getInlayHint(variable);
inlayHint.getPosition().setCharacter(inlayHint.getPosition().getCharacter() + offsetPerLine.get(inlayHint.getPosition().getLine()));
typeHint.add(inlayHint);
//Diagnostics of Types
for (var type : variable.getPossibleTypes()) {
Diagnostic diagnostic = getDiagnostic(variable, params.getTextDocument().getUri(), type);
diagnostic.getRange().getStart().setCharacter(diagnostic.getRange().getStart().getCharacter() + offsetPerLine.get(diagnostic.getRange().getStart().getLine()));
diagnostic.getRange().getEnd().setCharacter(diagnostic.getRange().getEnd().getCharacter() + offsetPerLine.get(diagnostic.getRange().getEnd().getLine()));
diagnostics.add(diagnostic);
}
}
globalDiagnosticsMap.put(params.getTextDocument().getUri(), diagnostics);
globalInlayHintMap.put(params.getTextDocument().getUri(), typeHint);
// updateGlobalMaps(diagnostics, typeHint, params.getTextDocument().getUri());
} catch (Exception e) {
log("[didSave] Error trying to get Inlay-Hints and Diagnostics for Client: " + e.getMessage(), MessageType.Error);
client.showMessage(new MessageParams(MessageType.Error, e.getMessage()));
updateGlobalMaps(new ArrayList<>(), new ArrayList<>(), params.getTextDocument().getUri());
} finally {
currentlyCalculating = false;
sWatch.stop();
log("[didSave] Finished Calculating in [" + sWatch.elapsed().toSeconds() + "s]", MessageType.Info);
}
globalInlayHintMap.put(params.getTextDocument().getUri(), globalInlayHintMap.get(params.getTextDocument().getUri()).stream().filter(el -> {
//TODO: Hier das Label aufspalten an | weil die Typehints immer mit und getrennt sind und so jeder Typhint für sich durchlaufen werden kann.
log(el.getLabel().getLeft().replaceAll(" ", "") + "<>" + String.join(",", textChanges.get(el.getPosition().getLine())) + ": " + (!textChanges.get(el.getPosition().getLine()).contains(el.getLabel().getLeft().replaceAll(" ", "")) ? "true" : "false"), MessageType.Info);
return !textChanges.get(el.getPosition().getLine()).contains(el.getLabel().getLeft().replaceAll(" ", ""));
}).toList());
globalDiagnosticsMap.put(params.getTextDocument().getUri(), globalDiagnosticsMap.get(params.getTextDocument().getUri()).stream().filter(el -> {
log(el.getMessage().replaceAll(" ", "") + "<>" + String.join(",", textChanges.get(el.getRange().getStart().getLine())) + ": " + (!textChanges.get(el.getRange().getStart().getLine()).contains(el.getMessage().replaceAll(" ", "")) ? "true" : "false"), MessageType.Info);
return !textChanges.get(el.getRange().getStart().getLine()).contains(el.getMessage().replaceAll(" ", ""));
}).toList());
updateClient(client);
client.publishDiagnostics(new PublishDiagnosticsParams(params.getTextDocument().getUri(), globalDiagnosticsMap.get(params.getTextDocument().getUri())));
}
/**
* Handles a Formatting-Event
*
* @param params the Context of the Formatting
*/
@Override
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
logService.log("[formatting] Client requested formatting.", MessageType.Info);
return CompletableFuture.completedFuture(formattingHandler.handleFormat(params));
log("[formatting] Client requested formatting.", MessageType.Info);
List<TextEdit> edits = new ArrayList<>();
String[] lines = textDocuments.get(params.getTextDocument().getUri()).split("\n");
StringBuilder formattedText = new StringBuilder();
for (String line : lines) {
formattedText.append(line.stripTrailing()).append("\n");
}
TextEdit edit = new TextEdit();
edit.setRange(new Range(new Position(0, 0), new Position(lines.length, 0)));
edit.setNewText(formattedText.toString().trim());
edits.add(edit);
return CompletableFuture.completedFuture(edits);
}
@Override
public void didClose(DidCloseTextDocumentParams params) {
}
private void log(String message, MessageType type) {
client.logMessage(new MessageParams(type, message));
switch (type) {
case Error -> logger.error(message);
case Warning -> logger.warn(message);
case Info -> logger.info(message);
default -> logger.debug(message);
}
}
private InlayHint getInlayHint(LSPVariable variable) {
InlayHint inlayHint = new InlayHint();
String typeDisplay = "";
for (Type type : variable.getPossibleTypes()) {
typeDisplay += " | " + type.getType().replaceAll("GTV ", "");
}
inlayHint.setLabel(typeDisplay.length() > 2 ? typeDisplay.substring(2) : typeDisplay);
inlayHint.setPosition(new Position(variable.getLine() - 1, variable.getCharPosition()));
inlayHint.setKind(InlayHintKind.Parameter);
inlayHint.setPaddingRight(true);
inlayHint.setPaddingRight(true);
return inlayHint;
}
private Diagnostic getDiagnostic(LSPVariable variable, String fileUri, Type type) {
Range errorRange = new Range(
new Position(variable.getLine() - 1, variable.getCharPosition()), // Startposition
new Position(variable.getLine() - 1, textHelper.getEndingCharOfStartingChar(variable.getLine() - 1, variable.getCharPosition(), textDocuments.get(fileUri))) // Endposition
);
Diagnostic diagnostic = new Diagnostic(
errorRange,
//TODO: REMOVE! Temporary Fix because GTV, like TPH can be thrown away in the TypeResolver
type.getType().replaceAll("GTV ", ""),
DiagnosticSeverity.Hint,
"JavaTX Language Server"
);
diagnostic.setCode(type.isGeneric() ? "GENERIC" : "TYPE");
return diagnostic;
}
private void updateGlobalMaps(List<Diagnostic> diagnostics, List<InlayHint> typeHint, String uri) {
globalDiagnosticsMap.put(uri, diagnostics);
globalInlayHintMap.put(uri, typeHint);
}
private void updateClient(LanguageClient client) {
client.refreshInlayHints();
client.refreshDiagnostics();
}
private void startLoading(String taskName, String title, LanguageClient client) {
Either<String, Integer> token = Either.forLeft(taskName);
client.createProgress(new WorkDoneProgressCreateParams(token));
WorkDoneProgressBegin begin = new WorkDoneProgressBegin();
begin.setTitle(title);
begin.setCancellable(false);
begin.setPercentage(0);
ProgressParams beginParams = new ProgressParams(token, Either.forLeft(begin));
client.notifyProgress(beginParams);
}
private void stopLoading(String taskName, String title, LanguageClient client) {
Either<String, Integer> token = Either.forLeft(taskName);
WorkDoneProgressEnd end = new WorkDoneProgressEnd();
end.setMessage(title);
ProgressParams endParams = new ProgressParams(token, Either.forLeft(end));
client.notifyProgress(endParams);
}
@Override
public void didSave(DidSaveTextDocumentParams params) {
logService.log("[didSave] Client triggered didSave event.");
clientService.startProgress("compile-task", "Inferring types...");
saveHandler.handleSave(params);
clientService.stopProgress("compile-task", "Types successfully inferred");
clientService.refreshClient();
public void didSave(DidSaveTextDocumentParams didSaveTextDocumentParams) {
log("[didSave] Client triggered didSave-Event.", MessageType.Info);
startLoading("compile-task", "Inferring types...", client);
if (!currentlyCalculating) {
currentlyCalculating = true;
ArrayList<Diagnostic> diagnostics = new ArrayList<>();
var sWatch = Stopwatch.createUnstarted();
sWatch.start();
try {
typeResolver.getCompilerInput(didSaveTextDocumentParams.getTextDocument().getUri(), textDocuments.get(didSaveTextDocumentParams.getTextDocument().getUri()));
if (textDocuments.get(didSaveTextDocumentParams.getTextDocument().getUri()) == null) {
log("[didSave] Input of Text Document is null in TextDocument-Hashmap.", MessageType.Error);
}
ArrayList<LSPVariable> typesOfMethodAndParameters = typeResolver.infereInput(textDocuments.get(didSaveTextDocumentParams.getTextDocument().getUri()), didSaveTextDocumentParams.getTextDocument().getUri());
variables = typesOfMethodAndParameters;
List<InlayHint> typeHint = new ArrayList<>();
for (var variable : typesOfMethodAndParameters) {
InlayHint inlayHint = getInlayHint(variable);
typeHint.add(inlayHint);
//Diagnostics of Types
for (var type : variable.getPossibleTypes()) {
Diagnostic diagnostic = getDiagnostic(variable, didSaveTextDocumentParams.getTextDocument().getUri(), type);
diagnostics.add(diagnostic);
}
}
updateGlobalMaps(diagnostics, typeHint, didSaveTextDocumentParams.getTextDocument().getUri());
PublishDiagnosticsParams diagnosticsParams = new PublishDiagnosticsParams(didSaveTextDocumentParams.getTextDocument().getUri(), diagnostics);
client.publishDiagnostics(diagnosticsParams);
} catch (Exception e) {
log("[didSave] Error trying to get Inlay-Hints and Diagnostics for Client: " + e.getMessage(), MessageType.Error);
client.showMessage(new MessageParams(MessageType.Error, e.getMessage()));
updateGlobalMaps(new ArrayList<>(), new ArrayList<>(), didSaveTextDocumentParams.getTextDocument().getUri());
} finally {
currentlyCalculating = false;
sWatch.stop();
log("[didSave] Finished Calculating in [" + sWatch.elapsed().toSeconds() + "s]", MessageType.Info);
}
}
stopLoading("compile-task", "Types successfully inferred", client);
dontShowHints = false;
updateClient(client);
ArrayList<File> files = new ArrayList<>();
if (fileRoot != null) {
try (Stream<Path> stream = Files.walk(Paths.get(fileRoot.toUri()))) {
stream.filter(Files::isRegularFile)
.forEach(el -> files.add(el.toFile()));
} catch (IOException e) {
}
}
}
@Override
public CompletableFuture<List<InlayHint>> inlayHint(InlayHintParams params) {
String uri = params.getTextDocument().getUri();
List<InlayHint> hints = cacheService.getInlayHintsByUri().get(uri);
return CompletableFuture.completedFuture(hints != null ? hints : Collections.emptyList());
log("[inlayHint] The Client requested Inlay-Hints.", MessageType.Info);
return CompletableFuture.supplyAsync(() -> dontShowHints ? Collections.emptyList() : globalInlayHintMap.get(params.getTextDocument().getUri()) == null ? Collections.emptyList() : globalInlayHintMap.get(params.getTextDocument().getUri()));
}
@Override
public void willSave(WillSaveTextDocumentParams params) {
TextDocumentService.super.willSave(params);
}
@Override
public CompletableFuture<List<TextEdit>> willSaveWaitUntil(WillSaveTextDocumentParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<DocumentLink>> documentLink(DocumentLinkParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<DocumentLink> documentLinkResolve(DocumentLink params) {
return CompletableFuture.completedFuture(new DocumentLink());
}
@Override
public CompletableFuture<List<ColorInformation>> documentColor(DocumentColorParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<ColorPresentation>> colorPresentation(ColorPresentationParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<FoldingRange>> foldingRange(FoldingRangeRequestParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<CallHierarchyItem>> prepareCallHierarchy(CallHierarchyPrepareParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<CallHierarchyIncomingCall>> callHierarchyIncomingCalls(CallHierarchyIncomingCallsParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<CallHierarchyOutgoingCall>> callHierarchyOutgoingCalls(CallHierarchyOutgoingCallsParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<SelectionRange>> selectionRange(SelectionRangeParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params) {
return TextDocumentService.super.semanticTokensFull(params);
}
@Override
public CompletableFuture<Either<SemanticTokens, SemanticTokensDelta>> semanticTokensFullDelta(SemanticTokensDeltaParams params) {
return CompletableFuture.completedFuture(Either.forLeft(new SemanticTokens()));
}
@Override
public CompletableFuture<SemanticTokens> semanticTokensRange(SemanticTokensRangeParams params) {
return CompletableFuture.completedFuture(new SemanticTokens());
}
@Override
public CompletableFuture<List<Moniker>> moniker(MonikerParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem unresolved) {
return CompletableFuture.completedFuture(new CompletionItem());
}
@Override
public CompletableFuture<Hover> hover(HoverParams params) {
return CompletableFuture.completedFuture(new Hover());
}
@Override
public CompletableFuture<SignatureHelp> signatureHelp(SignatureHelpParams params) {
return CompletableFuture.completedFuture(new SignatureHelp());
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> declaration(DeclarationParams params) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(DefinitionParams params) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> typeDefinition(TypeDefinitionParams params) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> implementation(ImplementationParams params) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(DocumentHighlightParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(DocumentSymbolParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
logService.log("[codeAction] Client requested code action.", MessageType.Info);
return CompletableFuture.supplyAsync(() -> codeActionHandler.handleCodeAction(params));
log("[codeAction] Client requested Insert at Line [" + params.getRange().getStart().getLine() + "] and from Char [" + params.getRange().getStart().getCharacter() + "] to [" + params.getRange().getEnd().getCharacter() + "].", MessageType.Info);
log("Code-Action Context was: " + params.getContext().getTriggerKind().name(), MessageType.Info);
List<Either<Command, CodeAction>> actions = new ArrayList<>();
List<Diagnostic> diagnosticInCurrentDocument = params.getContext().getDiagnostics();
Range requestedRange = params.getRange();
//All Diagnostics that are in range of the hover -> All Diagnostics of the selected Variable and thus all Types of the Variable
List<Diagnostic> diagnosticsOverlappingHover = diagnosticInCurrentDocument.stream()
.filter(diagnostic -> rangesOverlap(diagnostic.getRange(), requestedRange)).toList();
Range rangeOfInsert = params.getRange();
String documentUri = params.getTextDocument().getUri();
for (Diagnostic typeDiagnostic : diagnosticsOverlappingHover) {
try {
String typeWithReplacedVariable = typeDiagnostic.getMessage() +
" " +
textHelper.getTextOfChars(
textDocuments.get(params.getTextDocument().getUri()),
rangeOfInsert.getStart().getLine(),
rangeOfInsert.getStart().getCharacter(),
rangeOfInsert.getEnd().getCharacter()
);
ArrayList<TextEdit> listOfChanges = new ArrayList<>();
if (typeDiagnostic.getCode().getLeft().equals("GENERIC")) {
for (var diagnostic : globalDiagnosticsMap.get(params.getTextDocument().getUri())) {
if (diagnostic.getMessage().contains(typeDiagnostic.getMessage()) || typeDiagnostic.getMessage().contains(diagnostic.getMessage())) {
String genericType = diagnostic.getMessage() +
" " +
textHelper.getTextOfChars(
textDocuments.get(params.getTextDocument().getUri()),
diagnostic.getRange().getStart().getLine(),
diagnostic.getRange().getStart().getCharacter(),
diagnostic.getRange().getEnd().getCharacter()
);
listOfChanges.add(new TextEdit(diagnostic.getRange(), genericType));
}
}
} else {
listOfChanges.add(new TextEdit(rangeOfInsert, typeWithReplacedVariable));
}
var isTypeImported = false;
if (!typeDiagnostic.getCode().getLeft().equalsIgnoreCase("generic")) {
isTypeImported = typeResolver.isTypeImported(textDocuments.get(params.getTextDocument().getUri()), typeDiagnostic.getMessage());
}
if (!isTypeImported && !typeDiagnostic.getMessage().equals("void") && !typeDiagnostic.getCode().getLeft().equals("GENERIC")) {
Range importRange = new Range(new Position(0, 0), new Position(0, 0));
var typeWithoutGenerics = typeDiagnostic.getMessage().contains("<") ? typeDiagnostic.getMessage().substring(0, typeDiagnostic.getMessage().indexOf('<')) : typeDiagnostic.getMessage();
listOfChanges.add(new TextEdit(importRange, "import " + typeWithoutGenerics + ";\n"));
}
Map<String, List<TextEdit>> changes = new HashMap<>();
changes.put(documentUri, listOfChanges);
WorkspaceEdit edit = new WorkspaceEdit();
edit.setChanges(changes);
CodeAction action = new CodeAction("Insert " + typeDiagnostic.getMessage());
action.setKind(CodeActionKind.QuickFix);
action.setEdit(edit);
actions.add(Either.forRight(action));
} catch (Exception e) {
log("Error creating Actions, returning empty List. The Error was: " + e.getMessage(), MessageType.Error);
}
}
return CompletableFuture.supplyAsync(() -> {
return actions;
});
}
}
private boolean rangesOverlap(Range range1, Range range2) {
int start1 = range1.getStart().getLine() * 1000 + range1.getStart().getCharacter();
int end1 = range1.getEnd().getLine() * 1000 + range1.getEnd().getCharacter();
int start2 = range2.getStart().getLine() * 1000 + range2.getStart().getCharacter();
int end2 = range2.getEnd().getLine() * 1000 + range2.getEnd().getCharacter();
return start1 <= end2 && start2 <= end1;
}
@Override
public CompletableFuture<CodeAction> resolveCodeAction(CodeAction unresolved) {
return CompletableFuture.completedFuture(new CodeAction());
}
@Override
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<CodeLens> resolveCodeLens(CodeLens unresolved) {
return CompletableFuture.completedFuture(new CodeLens());
}
@Override
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends TextEdit>> onTypeFormatting(DocumentOnTypeFormattingParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
return CompletableFuture.completedFuture(new WorkspaceEdit());
}
@Override
public CompletableFuture<LinkedEditingRanges> linkedEditingRange(LinkedEditingRangeParams params) {
return CompletableFuture.completedFuture(new LinkedEditingRanges());
}
}

View File

@@ -1,16 +1,30 @@
package de.dhbw;
import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.WorkspaceService;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
*
* Handles Actions in Workspace
*
* */
public class JavaTXWorkspaceService implements WorkspaceService {
private static final Logger logger = LogManager.getLogger(JavaTXWorkspaceService.class);
@Override
public void didChangeConfiguration(DidChangeConfigurationParams params) {
public void didChangeConfiguration(DidChangeConfigurationParams didChangeConfigurationParams) {
}
@Override
public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
}
}

View File

@@ -1,95 +0,0 @@
package de.dhbw.handler;
import com.google.common.base.Stopwatch;
import de.dhbw.helper.ConversionHelper;
import de.dhbw.helper.TypeResolver;
import de.dhbw.model.DiagnosticsAndTypehints;
import de.dhbw.model.LSPVariable;
import de.dhbw.service.CacheService;
import de.dhbw.service.ClientService;
import de.dhbw.service.DocumentStore;
import de.dhbw.service.LogService;
import de.dhbw.service.ParserService;
import org.eclipse.lsp4j.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.net.URI;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class ChangeHandler {
private final DocumentStore documentStore;
private final ParserService parserService;
private final ConversionHelper conversionHelper;
private final ClientService clientService;
private final TypeResolver typeResolver;
private final CacheService cacheService;
private final LogService logService;
public ChangeHandler(DocumentStore documentStore, ParserService parserService,
ConversionHelper conversionHelper, ClientService clientService,
TypeResolver typeResolver, CacheService cacheService, LogService logService) {
this.documentStore = documentStore;
this.parserService = parserService;
this.conversionHelper = conversionHelper;
this.clientService = clientService;
this.typeResolver = typeResolver;
this.cacheService = cacheService;
this.logService = logService;
}
public void didChange(DidChangeTextDocumentParams params) {
String uri = params.getTextDocument().getUri();
StringBuilder content = new StringBuilder();
params.getContentChanges().forEach(change -> content.append(change.getText()));
String input = content.toString();
documentStore.saveFile(uri, input);
List<Diagnostic> syntaxErrors = parserService.getDiagnosticsOfErrors(input, uri);
if (!syntaxErrors.isEmpty()) {
clientService.publishDiagnostics(uri, syntaxErrors);
return;
}
clientService.refreshClient();
inferAndPublish(uri, input);
}
private void inferAndPublish(String uri, String input) {
Stopwatch stopwatch = Stopwatch.createStarted();
File tempFile = null;
try {
File tempDir = Path.of(new URI(uri)).getParent().toFile();
tempFile = File.createTempFile("jtx_temp", ".tmp", tempDir);
try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
writer.write(input);
}
List<LSPVariable> variables = typeResolver.inferTypes(tempFile.toURI().toString(), input);
cacheService.setVariables(variables);
DiagnosticsAndTypehints result = conversionHelper.variablesToDiagnosticsAndTypehints(variables, input);
cacheService.updateGlobalMaps(result.diagnostics(), result.inlayHints(), uri);
clientService.publishDiagnostics(uri, new ArrayList<>(result.diagnostics()));
} catch (Exception | VerifyError e) {
logService.log("[didChange] Error: " + e.getMessage(), MessageType.Error);
clientService.showMessage(MessageType.Error, e.getMessage() != null ? e.getMessage() : "Unknown error");
cacheService.updateGlobalMaps(List.of(), List.of(), uri);
} finally {
if (tempFile != null) {
tempFile.delete();
}
stopwatch.stop();
logService.log("[didChange] Completed in " + stopwatch.elapsed().toSeconds() + "s", MessageType.Info);
}
}
}

View File

@@ -1,126 +0,0 @@
package de.dhbw.handler;
import de.dhbw.helper.ConversionHelper;
import de.dhbw.helper.TypeResolver;
import de.dhbw.service.DocumentStore;
import de.dhbw.service.LogService;
import de.dhbw.model.PlaceholderVariable;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CodeActionHandler {
private final DocumentStore documentStore;
private final TypeResolver typeResolver;
private final LogService logService;
private final ConversionHelper conversionHelper;
public CodeActionHandler(DocumentStore documentStore, TypeResolver typeResolver,
LogService logService, ConversionHelper conversionHelper) {
this.documentStore = documentStore;
this.typeResolver = typeResolver;
this.logService = logService;
this.conversionHelper = conversionHelper;
}
public List<Either<Command, CodeAction>> handleCodeAction(CodeActionParams params) {
if (typeResolver.getInserts() == null) {
return List.of();
}
String uri = params.getTextDocument().getUri();
Range range = params.getRange();
Map<String, List<PlaceholderVariable>> overlapping = getOverlapping(
typeResolver.getInserts(),
range.getStart().getLine() + 1,
range.getStart().getCharacter(),
range.getEnd().getCharacter()
);
List<Either<Command, CodeAction>> actions = new ArrayList<>();
List<String> addedTitles = new ArrayList<>();
for (List<PlaceholderVariable> placeholders : overlapping.values()) {
for (PlaceholderVariable placeholder : placeholders) {
try {
String currentContent = documentStore.getFileContent(uri);
String inserted = placeholder.insert(currentContent);
String title = "Insert " + conversionHelper.cleanType(placeholder.getInsertString());
if (addedTitles.contains(title)) {
continue;
}
TextEdit edit = new TextEdit(wholeDocumentRange(currentContent), inserted);
WorkspaceEdit workspaceEdit = new WorkspaceEdit();
workspaceEdit.setChanges(Map.of(uri, List.of(edit)));
CodeAction action = new CodeAction(title);
action.setKind(CodeActionKind.QuickFix);
action.setEdit(workspaceEdit);
actions.add(Either.forRight(action));
addedTitles.add(title);
} catch (Exception e) {
logService.log("Error creating code action: " + e.getMessage(), MessageType.Error);
}
}
}
return actions;
}
static Range wholeDocumentRange(String text) {
if (text == null || text.isEmpty()) {
return new Range(new Position(0, 0), new Position(0, 0));
}
int lastLine = 0;
int lastBreak = -1;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == '\n') {
lastLine++;
lastBreak = i;
} else if (c == '\r') {
lastLine++;
if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
lastBreak = i + 1;
i++;
} else {
lastBreak = i;
}
}
}
int endChar = text.length() - (lastBreak + 1);
return new Range(new Position(0, 0), new Position(lastLine, endChar));
}
private static <V> Map<String, V> getOverlapping(Map<String, V> map, int line,
int startChar, int endChar) {
int s = Math.min(startChar, endChar);
int e = Math.max(startChar, endChar);
return map.entrySet().stream()
.filter(entry -> {
String[] parts = entry.getKey().split("\\s+");
if (parts.length != 2) return false;
try {
int kLine = Integer.parseInt(parts[0]);
int kChar = Integer.parseInt(parts[1]);
return kLine == line && kChar >= s && kChar <= e;
} catch (NumberFormatException ex) {
return false;
}
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}

View File

@@ -1,34 +0,0 @@
package de.dhbw.handler;
import de.dhbw.service.DocumentStore;
import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import java.util.List;
public class FormattingHandler {
private final DocumentStore documentStore;
public FormattingHandler(DocumentStore documentStore) {
this.documentStore = documentStore;
}
public List<TextEdit> handleFormat(DocumentFormattingParams params) {
String content = documentStore.getFileContent(params.getTextDocument().getUri());
String[] lines = content.split("\n");
StringBuilder formatted = new StringBuilder();
for (String line : lines) {
formatted.append(line.stripTrailing()).append("\n");
}
TextEdit edit = new TextEdit(
new Range(new Position(0, 0), new Position(lines.length, 0)),
formatted.toString().stripTrailing()
);
return List.of(edit);
}
}

View File

@@ -1,80 +0,0 @@
package de.dhbw.handler;
import com.google.common.base.Stopwatch;
import de.dhbw.model.DiagnosticsAndTypehints;
import de.dhbw.model.LSPVariable;
import de.dhbw.service.CacheService;
import de.dhbw.service.ClientService;
import de.dhbw.service.DocumentStore;
import de.dhbw.service.LogService;
import de.dhbw.service.ParserService;
import de.dhbw.helper.ConversionHelper;
import de.dhbw.helper.TypeResolver;
import org.eclipse.lsp4j.*;
import java.util.ArrayList;
import java.util.List;
public class SaveHandler {
private final TypeResolver typeResolver;
private final DocumentStore documentStore;
private final LogService logService;
private final CacheService cacheService;
private final ConversionHelper conversionHelper;
private final ClientService clientService;
private final ParserService parserService;
public SaveHandler(TypeResolver typeResolver, DocumentStore documentStore, LogService logService,
CacheService cacheService, ConversionHelper conversionHelper,
ClientService clientService, ParserService parserService) {
this.typeResolver = typeResolver;
this.documentStore = documentStore;
this.logService = logService;
this.cacheService = cacheService;
this.conversionHelper = conversionHelper;
this.clientService = clientService;
this.parserService = parserService;
}
public void handleSave(DidSaveTextDocumentParams params) {
cacheService.reset();
typeResolver.reset();
Stopwatch stopwatch = Stopwatch.createStarted();
String uri = params.getTextDocument().getUri();
try {
String fileContent = documentStore.getFileContent(uri);
List<Diagnostic> syntaxErrors = parserService.getDiagnosticsOfErrors(fileContent, uri);
if (!syntaxErrors.isEmpty()) {
clientService.publishDiagnostics(uri, syntaxErrors);
return;
}
clientService.refreshClient();
cacheService.getLastSavedFiles().put(uri, fileContent);
if (fileContent == null) {
logService.log("[didSave] File content is null.", MessageType.Error);
return;
}
List<LSPVariable> variables = typeResolver.inferTypes(uri, fileContent);
cacheService.setVariables(variables);
DiagnosticsAndTypehints result = conversionHelper.variablesToDiagnosticsAndTypehints(variables, fileContent);
cacheService.updateGlobalMaps(result.diagnostics(), result.inlayHints(), uri);
clientService.publishDiagnostics(uri, new ArrayList<>(result.diagnostics()));
} catch (Exception | VerifyError e) {
logService.log("[didSave] Error: " + e.getMessage(), MessageType.Error);
clientService.showMessage(MessageType.Error, e.getMessage() != null ? e.getMessage() : "Unknown error");
cacheService.updateGlobalMaps(List.of(), List.of(), uri);
} finally {
stopwatch.stop();
logService.log("[didSave] Completed in " + stopwatch.elapsed().toSeconds() + "s", MessageType.Info);
}
}
}

View File

@@ -0,0 +1,96 @@
package de.dhbw.handler;
import de.dhbw.model.LSPVariable;
import de.dhbwstuttgart.syntaxtree.SourceFile;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import java.util.ArrayList;
import java.util.List;
public class UpdatePositionHandler {
public static List<InlayHint> adjustInlayHintPositions(List<InlayHint> oldHints, List<String> newTextLines) {
List<InlayHint> adjusted = new ArrayList<>();
for (InlayHint hint : oldHints) {
Position oldPos = hint.getPosition();
int oldLine = oldPos.getLine();
String variableName = extractVariableNameFromHint(hint); // z.B. durch .getLabel()
int searchStart = Math.max(0, oldLine - 3);
int searchEnd = Math.min(newTextLines.size(), oldLine + 3);
boolean found = false;
for (int i = searchStart; i < searchEnd; i++) {
String lineText = newTextLines.get(i);
int column = lineText.indexOf(variableName);
if (column >= 0) {
Position newPos = new Position(i, column + variableName.length());
InlayHint adjustedHint = new InlayHint(newPos, hint.getLabel());
adjustedHint.setKind(hint.getKind());
adjustedHint.setPaddingLeft(false);
adjustedHint.setPaddingRight(true);
adjusted.add(adjustedHint);
found = true;
break;
}
}
if (!found) {
adjusted.add(hint); // fallback: alte Position beibehalten
}
}
return adjusted;
}
private static String extractVariableNameFromHint(InlayHint hint) {
// Beispiel: wenn dein Hint-Label z.B. "String ↤" ist, brauchst du Logik, um den Namen zu bestimmen
// Wenn du nur Typen zeigst, kannst du Position - Name aus anderer Quelle kombinieren
// Temporär: nutze z.B. eine Custom-Property
return hint.getLabel().getLeft(); // Pseudocode, je nach Labelstruktur anpassen
}
public static List<Diagnostic> adjustDiagnosticPositions(List<Diagnostic> oldDiagnostics, List<String> newTextLines) {
List<Diagnostic> adjusted = new ArrayList<>();
for (Diagnostic diag : oldDiagnostics) {
String keyword = extractKeywordFromDiagnostic(diag); // z.B. "inferred type"
int oldLine = diag.getRange().getStart().getLine();
int searchStart = Math.max(0, oldLine - 3);
int searchEnd = Math.min(newTextLines.size(), oldLine + 3);
boolean found = false;
for (int i = searchStart; i < searchEnd; i++) {
String lineText = newTextLines.get(i);
int col = lineText.indexOf(keyword);
if (col >= 0) {
Range newRange = new Range(new Position(i, col), new Position(i, col + keyword.length()));
Diagnostic adjustedDiag = new Diagnostic(newRange, diag.getMessage(), diag.getSeverity(), diag.getCode().getLeft(), diag.getSource());
adjusted.add(adjustedDiag);
found = true;
break;
}
}
if (!found) {
adjusted.add(diag); // fallback
}
}
return adjusted;
}
private static String extractKeywordFromDiagnostic(Diagnostic diag) {
// z.B. mit Regex oder fixer Konvention aus message
return "type"; // Beispiel
}
}

View File

@@ -1,90 +0,0 @@
package de.dhbw.helper;
import de.dhbw.model.PlaceholderPoint;
import de.dhbw.model.PlaceholderType;
import de.dhbw.model.PlaceholderVariable;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.SourceFile;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.target.generate.GenerateGenerics;
import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.target.generate.GenericsResultSet;
import de.dhbwstuttgart.typeinference.result.ResolvedType;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import org.antlr.v4.runtime.Token;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
public class ASTTransformationHelper {
private ASTTransformationHelper() {}
public static Set<PlaceholderVariable> createTypeInsertPoints(SourceFile source, ResultSet results,
GenericsResult generics) {
return new PlaceholderPlacer().getTypeInserts(source, results, generics);
}
public static PlaceholderVariable createInsertPoints(RefTypeOrTPHOrWildcardOrGeneric type, Token offset,
ClassOrInterface cl, Method m, ResultSet resultSet,
GenericsResultSet constraints,
GenericsResultSet classConstraints) {
ResolvedType resolvedType = resultSet.resolveType(type);
PlaceholderPoint insertPoint = new PlaceholderPoint(
offset,
new PlaceholderToInsertString(resolvedType.resolvedType, constraints, classConstraints).insert,
PlaceholderType.NORMAL_INSERT
);
Set<PlaceholderPoint> genericInserts = createGenericInserts(constraints, classConstraints, cl, m, resultSet, offset);
return new PlaceholderVariable(insertPoint, genericInserts, resolvedType.getResultPair());
}
private static synchronized Set<PlaceholderPoint> createGenericInserts(GenericsResultSet methodConstraints,
GenericsResultSet classConstraints,
ClassOrInterface cl, Method m,
ResultSet resultSet, Token mOffset) {
Set<PlaceholderPoint> result = createGenericClassInserts(classConstraints, cl);
if (m != null) {
Token offset = m.getOffset() != null ? m.getOffset() : mOffset;
result.addAll(createMethodConstraints(methodConstraints, offset));
}
return result;
}
private static Set<PlaceholderPoint> createMethodConstraints(GenericsResultSet constraints, Token offset) {
if (constraints == null || constraints.size() == 0) {
return new HashSet<>();
}
String insert = buildGenericString(constraints);
Set<PlaceholderPoint> result = new HashSet<>();
result.add(new PlaceholderPoint(offset, insert, PlaceholderType.GENERIC_METHOD_INSERT));
return result;
}
private static Set<PlaceholderPoint> createGenericClassInserts(GenericsResultSet classConstraints,
ClassOrInterface cl) {
if (classConstraints == null || classConstraints.size() == 0) {
return new HashSet<>();
}
String insert = buildGenericString(classConstraints);
Set<PlaceholderPoint> result = new HashSet<>();
result.add(new PlaceholderPoint(cl.getGenerics().getOffset(), insert, PlaceholderType.GENERIC_CLASS_INSERT));
return result;
}
private static String buildGenericString(GenericsResultSet constraints) {
StringJoiner joiner = new StringJoiner(", ", " <", ">");
for (var constraint : constraints) {
if (constraint instanceof GenerateGenerics.PairEQ peq) {
joiner.add(peq.left.resolve().getName());
} else if (constraint instanceof GenerateGenerics.PairLT psm) {
joiner.add(psm.left.resolve().getName() + " extends " + psm.right.resolve().getName());
}
}
return joiner.toString();
}
}

View File

@@ -2,17 +2,34 @@ package de.dhbw.helper;
import de.dhbw.model.SnippetWithName;
import java.util.List;
import java.util.ArrayList;
/**
* Helper-Class containing all Snippets
*
* */
public class CodeSnippetOptions {
private ArrayList<SnippetWithName> snippets = new ArrayList<>();
private static final List<SnippetWithName> SNIPPETS = List.of(
new SnippetWithName("main", "public main(args){\n ${1:}\n}\n"),
new SnippetWithName("forLoop", "for(i = 0; i < ${1:listSize}; i++){\n\n}"),
new SnippetWithName("forEachLoop", "for(el : ${1:listSize}){\n\n}")
);
public CodeSnippetOptions() {
snippets.add(getMainSnippet());
snippets.add(getForLoopSnippet());
snippets.add(getForEachSnippet());
}
public List<SnippetWithName> getSnippets() {
return SNIPPETS;
public SnippetWithName getMainSnippet(){
return new SnippetWithName("main", "public main(args){\n ${1:}\n}\n");
}
public SnippetWithName getForLoopSnippet(){
return new SnippetWithName("forLoop", "for(i = 0; i < ${1:listSize}; i++){\n\n}");
}
public SnippetWithName getForEachSnippet(){
return new SnippetWithName("forEachLoop", "for(el : ${1:listSize}){\n\n}");
}
public ArrayList<SnippetWithName> getSnippets() {
return snippets;
}
}

View File

@@ -1,82 +0,0 @@
package de.dhbw.helper;
import de.dhbw.model.DiagnosticsAndTypehints;
import de.dhbw.service.DocumentStore;
import de.dhbw.model.LSPVariable;
import de.dhbw.model.Type;
import de.dhbwstuttgart.languageServerInterface.model.ParserError;
import org.eclipse.lsp4j.*;
import java.util.ArrayList;
import java.util.List;
public class ConversionHelper {
private final TextHelper textHelper;
private final DocumentStore documentStore;
public ConversionHelper(TextHelper textHelper, DocumentStore documentStore) {
this.textHelper = textHelper;
this.documentStore = documentStore;
}
public String cleanType(String type) {
return type.replace("java.lang.", "").replace("java.util.", "");
}
public DiagnosticsAndTypehints variablesToDiagnosticsAndTypehints(List<LSPVariable> variables, String input) {
List<InlayHint> hints = new ArrayList<>();
List<Diagnostic> diagnostics = new ArrayList<>();
for (LSPVariable variable : variables) {
hints.add(createInlayHint(variable));
for (Type type : variable.getPossibleTypes()) {
diagnostics.add(createDiagnostic(variable, input, type));
}
}
return new DiagnosticsAndTypehints(diagnostics, hints);
}
public List<Diagnostic> parseErrorsToDiagnostics(List<ParserError> parserErrors) {
return parserErrors.stream().map(error -> new Diagnostic(
new Range(
new Position(error.getLine() - 1, error.getCharPositionInLine()),
new Position(error.getLine() - 1, error.getEndCharPosition())
),
error.getMsg(),
DiagnosticSeverity.Error,
"JavaTX Language Server"
)).toList();
}
private InlayHint createInlayHint(LSPVariable variable) {
String label = variable.getPossibleTypes().stream()
.map(type -> cleanType(type.type().replaceAll("GTV ", "")))
.reduce((a, b) -> a + " | " + b)
.orElse("");
InlayHint hint = new InlayHint();
hint.setLabel(label);
hint.setPosition(new Position(variable.getLine() - 1, variable.getCharPosition()));
hint.setKind(InlayHintKind.Parameter);
hint.setPaddingRight(true);
return hint;
}
private Diagnostic createDiagnostic(LSPVariable variable, String input, Type type) {
Range range = new Range(
new Position(variable.getLine() - 1, variable.getCharPosition()),
new Position(variable.getLine() - 1,
textHelper.getEndingCharOfStartingChar(variable.getLine() - 1, variable.getCharPosition(), input))
);
Diagnostic diagnostic = new Diagnostic(
range,
cleanType(type.type().replaceAll("GTV ", "")),
DiagnosticSeverity.Hint,
"JavaTX Language Server"
);
diagnostic.setCode(type.generic() ? "GENERIC" : "TYPE");
return diagnostic;
}
}

View File

@@ -1,31 +0,0 @@
package de.dhbw.helper;
import de.dhbw.model.PlaceholderVariable;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import java.util.HashSet;
import java.util.Set;
public class PlaceholderPlacer extends AbstractASTWalker {
private final Set<PlaceholderVariable> inserts = new HashSet<>();
private ResultSet withResults;
private GenericsResult genericsResult;
public Set<PlaceholderVariable> getTypeInserts(SourceFile sourceFile, ResultSet withResults,
GenericsResult genericsResult) {
this.withResults = withResults;
this.genericsResult = genericsResult;
sourceFile.accept(this);
inserts.forEach(PlaceholderVariable::reducePackage);
return inserts;
}
@Override
public void visit(ClassOrInterface classOrInterface) {
PlaceholderPlacerClass cl = new PlaceholderPlacerClass(classOrInterface, withResults, genericsResult);
inserts.addAll(cl.inserts);
}
}

View File

@@ -1,65 +0,0 @@
package de.dhbw.helper;
import de.dhbw.model.PlaceholderVariable;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.statement.LambdaExpression;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.target.generate.GenericsResultSet;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import java.util.HashSet;
import java.util.Set;
class PlaceholderPlacerClass extends AbstractASTWalker{
protected final ResultSet results;
private GenericsResult generatedGenerics;
protected final ClassOrInterface cl;
public final Set<PlaceholderVariable> inserts = new HashSet<>();
private Method method;
GenericsResultSet constraints;
GenericsResultSet classConstraints;
PlaceholderPlacerClass(ClassOrInterface forClass, ResultSet withResults, GenericsResult generatedGenerics){
this.cl = forClass;
this.method = null;
this.results = withResults;
this.generatedGenerics = generatedGenerics;
forClass.accept(this);
}
@Override
public void visit(Method method) {
this.method = method;
constraints = generatedGenerics.get(method);
classConstraints = generatedGenerics.get(cl);
if(method.getReturnType() instanceof TypePlaceholder)
inserts.add(ASTTransformationHelper.createInsertPoints(
method.getReturnType(), method.getReturnType().getOffset(), cl, method, results, constraints, classConstraints));
super.visit(method);
}
@Override
public void visit(Field field) {
if(field.getType() instanceof TypePlaceholder){
classConstraints = generatedGenerics.get(cl);
inserts.add(ASTTransformationHelper.createInsertPoints(
field.getType(), field.getType().getOffset(), cl, method, results, null, classConstraints));
}
super.visit(field);
}
@Override
public void visit(FormalParameter param) {
if(param.getType() instanceof TypePlaceholder)
inserts.add(ASTTransformationHelper.createInsertPoints(
param.getType(), param.getType().getOffset(), cl, method, results, constraints, classConstraints));
super.visit(param);
}
@Override
public void visit(LambdaExpression lambdaExpression) {
// Lambda expressions do not need type inserts
}
}

View File

@@ -1,77 +0,0 @@
package de.dhbw.helper;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.target.generate.GenericsResultSet;
import de.dhbwstuttgart.typeinference.result.*;
import java.util.Iterator;
public class PlaceholderToInsertString implements ResultSetVisitor{
public String insert = "";
private GenericsResultSet constraints;
private GenericsResultSet classConstraints;
public PlaceholderToInsertString(RefTypeOrTPHOrWildcardOrGeneric type, GenericsResultSet constraints, GenericsResultSet classConstraints){
this.constraints = constraints;
this.classConstraints = classConstraints;
type.accept(this);
}
@Override
public void visit(PairTPHsmallerTPH p) {
}
@Override
public void visit(PairTPHequalRefTypeOrWildcardType p) {
}
@Override
public void visit(PairTPHEqualTPH p) {
}
@Override
public void visit(RefType resolved) {
insert = resolved.getName().toString();
if(resolved.getParaList().size() > 0){
insert += "<";
Iterator<RefTypeOrTPHOrWildcardOrGeneric> iterator = resolved.getParaList().iterator();
while(iterator.hasNext()){
RefTypeOrTPHOrWildcardOrGeneric typeParam = iterator.next();
insert += new PlaceholderToInsertString(typeParam, constraints, classConstraints).insert;
if(iterator.hasNext())insert += ", ";
}
insert += ">";
}
}
@Override
public void visit(GenericRefType genericRefType) {
insert += genericRefType.getParsedName();
}
@Override
public void visit(SuperWildcardType superWildcardType) {
insert += "? super " + new PlaceholderToInsertString(superWildcardType.getInnerType(), constraints, classConstraints).insert;
}
@Override
public void visit(TypePlaceholder typePlaceholder) {
ResultPair<?, ?> resultPair = null;
if (constraints != null)
resultPair = constraints.getResultPairFor(typePlaceholder).orElse(null);
if (resultPair == null)
resultPair = classConstraints.getResultPairFor(typePlaceholder).orElse(null);
if (resultPair != null)
insert += ((TypePlaceholder)resultPair.getLeft()).getName();
}
@Override
public void visit(ExtendsWildcardType extendsWildcardType) {
insert += "? extends " + new PlaceholderToInsertString(extendsWildcardType.getInnerType(), constraints, classConstraints).insert;
}
}

View File

@@ -1,27 +1,85 @@
package de.dhbw.helper;
import java.util.Set;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TextHelper {
private static final Set<String> WORD_DELIMITERS = Set.of("(", ")", " ", "{", "}", ";", ",");
private static final Logger log = Logger.getLogger(TextHelper.class);
public Integer getClassPositionForGeneric(Integer line, String input, Integer startChar){
log.info("Calculating Position of Class-Generic Variable at Line [" + line + "] and Char [" + startChar + "].");
public int getEndingCharOfStartingChar(int line, int startChar, String input) {
String[] lines = input.split("\n");
if (lines.length <= line) {
return startChar;
if(lines.length < line){
log.warn("Returning hardcoded Value because the requested Line [" + line + "] does not exist in Text Document.");
return startChar+3;
}
String lineText = lines[line];
for (int i = startChar; i < lineText.length(); i++) {
if (WORD_DELIMITERS.contains(String.valueOf(lineText.charAt(i)))) {
String[] linesInChar = lines[line].split("");
var index = startChar;
var found = false;
for (int i = startChar; i < linesInChar.length; i++) {
if(linesInChar[i].contains("{")){
index = i;
found = true;
break;
}
}
if(!found){
index = linesInChar.length-1;
}
for(int j = index; j <= linesInChar.length; j--){
if(!linesInChar[j].isEmpty() && !linesInChar[j].equals(" ")){
return j;
}
}
return index;
}
public Integer getEndingCharOfStartingChar(Integer line, Integer startChar, String input){
log.info("Calculating ending-Position for Variable at Line [" + line + "] and Char [" + startChar + "].");
List<String> endingChars = List.of("(", ")", " ", "{", "}", ";", ",");
String[] lines = input.split("\n");
if(lines.length < line){
log.warn("Returning hardcoded Value because the requested Line [" + line + "] does not exist in Text Document.");
return startChar+3;
}
String[] linesInChar = lines[line].split("");
var index = startChar;
for (int i = startChar; i < linesInChar.length; i++) {
index++;
if(endingChars.contains(linesInChar[i])){
return i;
}
}
return lineText.length();
return index-1;
}
public String getTextOfChars(String textDocument, int line, int charStart, int charEnd) {
return textDocument.split("\n")[line].substring(charStart, charEnd);
public String getTextOfChars(String textDocument, Integer line, Integer charStart, Integer charEnd){
String[] splittedText = textDocument.split("\n");
return splittedText[line].substring(charStart, charEnd);
}
}

View File

@@ -1,111 +1,315 @@
package de.dhbw.helper;
import de.dhbw.model.LSPVariable;
import de.dhbw.model.PlaceholderVariable;
import de.dhbw.model.Type;
import de.dhbw.model.*;
import de.dhbwstuttgart.languageServerInterface.LanguageServerInterface;
import de.dhbwstuttgart.languageServerInterface.model.LanguageServerTransferObject;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.SourceFile;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.services.LanguageClient;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;
/**
* Helper-Class for finding the Type of a selected Word
*/
public class TypeResolver {
HashMap<Integer, LanguageServerTransferObject> dataCache = new HashMap<>();
private static final Logger logger = LogManager.getLogger(TypeResolver.class);
private List<GenericsResult> generatedGenerics;
private HashMap<String, List<PlaceholderVariable>> inserts;
private List<LSPVariable> currentTypes = new ArrayList<>();
private final boolean ENABLE_GENERICS = false;
private LanguageServerTransferObject current;
public HashMap<String, List<PlaceholderVariable>> getInserts() {
return inserts;
public LanguageServerTransferObject getCurrent() {
return current;
}
public void reset() {
inserts = null;
generatedGenerics = null;
current = null;
public void reduceCurrent() {
current.getResultSets().removeFirst();
}
public List<LSPVariable> inferTypes(String path, String input) throws IOException, ClassNotFoundException, URISyntaxException {
LanguageServerInterface lsi = new LanguageServerInterface();
LanguageServerTransferObject transfer = lsi.getResultSetAndAbstractSyntax(path);
private List<ResultSet> currentResults = new ArrayList<>();
SourceFile ast = transfer.getAst();
List<ResultSet> resultSets = transfer.getResultSets();
generatedGenerics = transfer.getGeneratedGenerics().get(ast);
current = transfer;
Set<PlaceholderVariable> insertPoints = new HashSet<>();
for (int i = 0; i < resultSets.size(); i++) {
insertPoints.addAll(ASTTransformationHelper.createTypeInsertPoints(ast, resultSets.get(i), generatedGenerics.get(i)));
}
inserts = groupInsertsByPosition(insertPoints);
List<LSPVariable> variables = createVariablesFromInserts(inserts);
deduplicateTypes(variables);
return variables;
public List<ResultSet> getCurrentResults() {
return currentResults;
}
public List<LSPVariable> inferTypesFromCurrent() throws IOException, ClassNotFoundException, URISyntaxException {
Set<PlaceholderVariable> insertPoints = new HashSet<>();
for (int i = 0; i < current.getResultSets().size(); i++) {
insertPoints.addAll(ASTTransformationHelper.createTypeInsertPoints(
current.getAst(), current.getResultSets().get(i), generatedGenerics.get(i)));
}
public void setCurrentResults(List<ResultSet> currentResults) {
this.currentResults = currentResults;
}
inserts = groupInsertsByPosition(insertPoints);
List<LSPVariable> variables = createVariablesFromInserts(inserts);
deduplicateTypes(variables);
return variables;
public void addToCurrentResults(List<ResultSet> additionalResults) {
this.currentResults.addAll(additionalResults);
}
public TypeResolver() {
}
public void updatePositions(String path, String input){
LanguageServerTransferObject obj = new LanguageServerInterface().getResultSetAndAbstractSyntax(path, input);
}
public boolean isTypeImported(String input, String type) {
// try {
// var transferObject = getCacheOrCalculate(input);
// var abstractSyntax = transferObject.getAst();
//
// var isAlreadyImported = false;
// for (var importStatement : abstractSyntax.getImports()) {
// isAlreadyImported = !isAlreadyImported && importStatement.toString().equals(type);
// }
//
// return isAlreadyImported;
//
// } catch (Exception e) {
// return true;
// }
return true;
}
private HashMap<String, List<PlaceholderVariable>> groupInsertsByPosition(Set<PlaceholderVariable> insertPoints) {
HashMap<String, List<PlaceholderVariable>> grouped = new HashMap<>();
for (PlaceholderVariable insert : insertPoints) {
String key = insert.point.point.getLine() + " " + insert.point.point.getCharPositionInLine();
grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(insert);
}
return grouped;
}
public ArrayList<Type> getAvailableTypes(List<ResultSet> resultSets, Method method) {
logger.info("Searching for resulting Types of Placeholder [" + method.getReturnType().toString() + "].");
ArrayList<String> normalType = new ArrayList<>();
private List<LSPVariable> createVariablesFromInserts(HashMap<String, List<PlaceholderVariable>> insertsMap) {
List<LSPVariable> variables = new ArrayList<>();
for (var entry : insertsMap.entrySet()) {
List<PlaceholderVariable> placeholders = entry.getValue();
List<Type> types = placeholders.stream()
.map(p -> new Type(p.getInsertString(), p.point.isGenericClassInsertPoint()))
.toList();
PlaceholderVariable first = placeholders.getFirst();
variables.add(new LSPVariable(
"test",
new ArrayList<>(types),
first.point.point.getLine(),
first.point.point.getCharPositionInLine(),
first.point.point.getStopIndex(),
first.getResultPair().getLeft()
));
}
return variables;
}
private void deduplicateTypes(List<LSPVariable> variables) {
for (LSPVariable variable : variables) {
LinkedHashMap<String, Type> unique = new LinkedHashMap<>();
for (Type type : variable.getPossibleTypes()) {
unique.putIfAbsent(type.type().replaceAll(" ", ""), type);
resultSets.forEach(conSet -> {
if (method.getReturnType().toString().toLowerCase().contains("tph ")) {
normalType.add(conSet.resolveType(method.getReturnType()).resolvedType.toString());
}
});
return new ArrayList<>(normalType.stream().filter(el -> !el.contains("TPH ")).map(el -> new Type(el, false)).toList());
}
public ArrayList<Type> getAvailableGenericTypes(List<GenericsResult> genericsResult, Method method) {
logger.info("Searching for resulting Types of Placeholder [" + method.getReturnType().toString() + "].");
ArrayList<String> genericTypes = new ArrayList<>();
genericsResult.forEach(conSet -> {
if (method.getReturnType().toString().toLowerCase().contains("tph ")) {
genericTypes.add(conSet.resolveTarget(method.getReturnType()).name().replaceAll("GTV ", ""));
}
});
return new ArrayList<>(genericTypes.stream().filter(el -> !el.contains("TPH ")).map(el -> new Type(el, true)).toList());
}
public ArrayList<Type> getAvailableGenericTypes(List<GenericsResult> genericsResult, RefTypeOrTPHOrWildcardOrGeneric parameter) {
logger.info("Searching for resulting Types of Placeholder [" + parameter.toString() + "].");
ArrayList<String> paramTypes = new ArrayList<>();
genericsResult.forEach(conSet -> {
if (parameter.toString().toLowerCase().contains("tph ")) {
paramTypes.add(conSet.resolveTarget(parameter).name());
}
});
return new ArrayList<>(paramTypes.stream().filter(el -> !el.contains("TPH ")).map(el -> new Type(el, true)).toList());
}
public ArrayList<Type> getAvailableTypes(List<ResultSet> resultSets, RefTypeOrTPHOrWildcardOrGeneric parameter) {
logger.info("Searching for resulting Types of Placeholder [" + parameter.toString() + "].");
ArrayList<String> paramTypes = new ArrayList<>();
resultSets.forEach(conSet -> {
if (parameter.toString().toLowerCase().contains("tph ")) {
paramTypes.add(conSet.resolveType(parameter).resolvedType.toString());
}
});
return new ArrayList<>(paramTypes.stream().filter(el -> !el.contains("TPH ")).map(el -> new Type(el, false)).toList());
}
public ArrayList<Type> filterOutDuplicates(List<Type> typeListOne, List<Type> typeListTwo) {
ArrayList<Type> filteredArrayList = new ArrayList<>();
typeListOne.forEach(el -> {
boolean found = false;
for (Type typeInListTwo : typeListTwo) {
found = found || typeInListTwo.getType().equals(el.getType());
}
if (!found) {
filteredArrayList.add(el);
}
});
return filteredArrayList;
}
public ArrayList<Type> filterOutDuplicates(List<Type> typeListOne) {
HashMap<String, Type> hashMap = new HashMap<>();
typeListOne.forEach(el -> {
hashMap.put(el.getType(), el);
});
return new ArrayList<>(hashMap.values());
}
public ArrayList<Type> getClassGenerics(Map<SourceFile, List<GenericsResult>> genericsResult, Method method, ClassOrInterface clazz) {
ArrayList<Type> genericTypes = new ArrayList<>();
genericsResult.forEach(((key, value) -> value.forEach(genericResultSet -> {
var result = genericResultSet.resolveTarget(method.getReturnType());
var genericResult = genericResultSet.getBounds(method.getReturnType(), clazz, method);
if (result != null && genericResult != null) {
genericResult.forEach(res -> {
if (res != null && !res.isOnMethod()) {
genericTypes.add(new Type("<" + result.name() + (res.bound().toString().equals("java.lang.Object") ? ">" : " extends " + res.bound().toString() + ">"), true));
}
}
);
}
})));
return genericTypes;
}
public void getCompilerInput(String path, String input) throws IOException, URISyntaxException, ClassNotFoundException {
LanguageServerInterface languageServer = new LanguageServerInterface();
var transferObj = languageServer.getResultSetAndAbstractSyntax(path, input);
current = transferObj;
}
/**
* Zum Erhalt für sowohl Parameter als auch Methoden.
*/
public ArrayList<LSPVariable> infereInput(String input, String path) throws IOException, ClassNotFoundException, URISyntaxException {
logger.info("Infering Types for Input.");
LanguageServerTransferObject transferObj;
if (current == null) {
LanguageServerInterface languageServer = new LanguageServerInterface();
transferObj = languageServer.getResultSetAndAbstractSyntax(path, input);
current = transferObj;
} else {
transferObj = current;
}
ArrayList<LSPVariable> methodsWithParametersLSPVariableList = new ArrayList<>();
if (!transferObj.getResultSets().isEmpty()) {
//TODO: Hier noch irgendwie die Klasse rausfinden oder durchgehen.
if (ENABLE_GENERICS) {
//GENERICS OF CLASS
for (var method : transferObj.getAst().getAllMethods()) {
for (var clazz : transferObj.getAst().getClasses()) {
ArrayList<Type> genericTypes = getClassGenerics(transferObj.getGeneratedGenerics(), method, clazz);
TextHelper helper = new TextHelper();
if (!genericTypes.isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPClass("test", genericTypes, clazz.getOffset().getLine(), helper.getClassPositionForGeneric(clazz.getOffset().getLine() - 1, input, clazz.getOffset().getStopIndex()), clazz.getOffset().getStopIndex(), clazz.getReturnType()));
}
}
}
}
for (var method : transferObj.getAst().getAllMethods()) {
var types = getAvailableTypes(transferObj.getResultSets(), method);
var generics = getAvailableGenericTypes(transferObj.getGeneratedGenerics().values().stream().flatMap(List::stream).collect(Collectors.toList()), method);
if (!filterOutDuplicates(types).isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPMethod(method.name, filterOutDuplicates(types), method.getOffset().getLine(), method.getOffset().getCharPositionInLine(), method.getOffset().getStopIndex(), method.getReturnType()));
}
if (ENABLE_GENERICS) {
if (!generics.isEmpty()) {
ArrayList<Type> typesThatAreGeneric = filterOutDuplicates(generics, types);
typesThatAreGeneric.forEach(el -> el.setType("<" + el.getType() + "> " + el.getType()));
//TODO: Muss Global und wird mehrmals so gemacht. Das if hier ist eigentlich falsch wegen unnötiger Berechnungszeit
if (!filterOutDuplicates(typesThatAreGeneric).isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPMethod(method.name, filterOutDuplicates(typesThatAreGeneric), method.getOffset().getLine(), method.getOffset().getCharPositionInLine(), method.getOffset().getStopIndex(), method.getReturnType()));
}
}
}
for (var param : method.getParameterList()) {
ArrayList<Type> typeParam = getAvailableTypes(transferObj.getResultSets(), param.getType());
ArrayList<Type> genericParam = getAvailableGenericTypes(transferObj.getGeneratedGenerics().values().stream().flatMap(List::stream).collect(Collectors.toList()), param.getType());
if (!typeParam.isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPParameter(method.name, filterOutDuplicates(typeParam), param.getOffset().getLine(), param.getOffset().getCharPositionInLine(), param.getOffset().getStopIndex(), param.getType()));
}
if (ENABLE_GENERICS) {
if (!genericParam.isEmpty()) {
ArrayList<Type> typesThatAreGeneric = filterOutDuplicates(genericParam, typeParam);
if (!filterOutDuplicates(typesThatAreGeneric).isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPParameter(method.name, filterOutDuplicates(typesThatAreGeneric), method.getOffset().getLine(), param.getOffset().getCharPositionInLine(), param.getOffset().getStopIndex(), param.getType()));
methodsWithParametersLSPVariableList.add(new LSPParameter(method.name, new ArrayList<>(filterOutDuplicates(typesThatAreGeneric).stream().map(el -> new Type("<" + el.getType() + ">", true)).toList()), method.getOffset().getLine(), method.getOffset().getCharPositionInLine(), method.getOffset().getStopIndex(), method.getReturnType()));
}
}
}
}
}
}
currentTypes.addAll(methodsWithParametersLSPVariableList);
return methodsWithParametersLSPVariableList;
}
public void updateTypehints(String input, String path) throws IOException, URISyntaxException, ClassNotFoundException {
LanguageServerTransferObject transferObj;
LanguageServerInterface languageServer = new LanguageServerInterface();
transferObj = languageServer.getResultSetAndAbstractSyntax(path, input);
var ast = transferObj.getAst();
}
public void reduceCurrent(HashMap<LineCharPosition, String> combinedList, List<LSPVariable> variables, LanguageClient client) {
client.logMessage(new MessageParams(MessageType.Info, "Lenght is: " + combinedList.size()));
for (var lines : combinedList.entrySet()) {
var contentChange = lines.getValue();
for (LSPVariable variable : variables) {
for (Type typeOfVariable : variable.getPossibleTypes()) {
client.logMessage(new MessageParams(MessageType.Info, "\"" + typeOfVariable.getType() + " : " + variable.getLine() + "\"< > \"" + contentChange + " : " + lines.getKey() + "\""));
if (typeOfVariable.getType().equalsIgnoreCase(contentChange.replaceAll(" ", ""))) {
if (variable.getLine() - 1 == lines.getKey().line && variable.getCharPosition() == lines.getKey().charPosition) {
current.getResultSets().removeIf(el -> !el.resolveType(variable.getOriginalTphName()).resolvedType.toString().equalsIgnoreCase(contentChange.replaceAll(" ", "")));
client.logMessage(new MessageParams(MessageType.Info, "Removing Resultset"));
return;
}
}
}
}
variable.setPossibleTypes(new ArrayList<>(unique.values()));
}
}
}
}

View File

@@ -1,9 +0,0 @@
package de.dhbw.model;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.InlayHint;
import java.util.List;
public record DiagnosticsAndTypehints(List<Diagnostic> diagnostics, List<InlayHint> inlayHints) {
}

View File

@@ -0,0 +1,11 @@
package de.dhbw.model;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import java.util.ArrayList;
public class LSPClass extends LSPVariable{
public LSPClass(String name, ArrayList<Type> possibleTypes, int line, int charPosition, int endPosition, RefTypeOrTPHOrWildcardOrGeneric originalTphName) {
super(name, possibleTypes, line, charPosition, endPosition, originalTphName);
}
}

View File

@@ -0,0 +1,11 @@
package de.dhbw.model;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import java.util.ArrayList;
public class LSPMethod extends LSPVariable {
public LSPMethod(String name, ArrayList<Type> type, int line, int charPosition, int endPosition, RefTypeOrTPHOrWildcardOrGeneric originalTphName) {
super(name, type, line, charPosition, endPosition, originalTphName);
}
}

View File

@@ -0,0 +1,12 @@
package de.dhbw.model;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import java.util.ArrayList;
public class LSPParameter extends LSPVariable {
public LSPParameter(String name, ArrayList<Type> type, int line, int charPosition, int endPosition, RefTypeOrTPHOrWildcardOrGeneric originalTphName) {
super(name, type, line, charPosition, endPosition, originalTphName);
}
}

View File

@@ -2,19 +2,18 @@ package de.dhbw.model;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import java.util.List;
import java.util.ArrayList;
public class LSPVariable {
String name;
ArrayList<Type> possibleTypes;
int line;
int charPosition;
int endPosition;
boolean needsInference;
RefTypeOrTPHOrWildcardOrGeneric originalTphName;
private final String name;
private final int line;
private final int charPosition;
private final int endPosition;
private final RefTypeOrTPHOrWildcardOrGeneric originalTphName;
private List<Type> possibleTypes;
public LSPVariable(String name, List<Type> possibleTypes, int line, int charPosition,
int endPosition, RefTypeOrTPHOrWildcardOrGeneric originalTphName) {
public LSPVariable(String name, ArrayList<Type> possibleTypes, int line, int charPosition, int endPosition, RefTypeOrTPHOrWildcardOrGeneric originalTphName) {
this.name = name;
this.possibleTypes = possibleTypes;
this.line = line;
@@ -23,11 +22,51 @@ public class LSPVariable {
this.originalTphName = originalTphName;
}
public String getName() { return name; }
public int getLine() { return line; }
public int getCharPosition() { return charPosition; }
public int getEndPosition() { return endPosition; }
public RefTypeOrTPHOrWildcardOrGeneric getOriginalTphName() { return originalTphName; }
public List<Type> getPossibleTypes() { return possibleTypes; }
public void setPossibleTypes(List<Type> possibleTypes) { this.possibleTypes = possibleTypes; }
public RefTypeOrTPHOrWildcardOrGeneric getOriginalTphName() {
return originalTphName;
}
public void setOriginalTphName(RefTypeOrTPHOrWildcardOrGeneric originalTphName) {
this.originalTphName = originalTphName;
}
public int getEndPosition() {
return endPosition;
}
public void setEndPosition(int endPosition) {
this.endPosition = endPosition;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ArrayList<Type> getPossibleTypes() {
return possibleTypes;
}
public void setPossibleTypes(ArrayList<Type> possibleTypes) {
this.possibleTypes = possibleTypes;
}
public int getLine() {
return line;
}
public void setLine(int line) {
this.line = line;
}
public int getCharPosition() {
return charPosition;
}
public void setCharPosition(int charPosition) {
this.charPosition = charPosition;
}
}

View File

@@ -0,0 +1,25 @@
package de.dhbw.model;
public class LineCharPosition {
public final int line;
public final int charPosition;
public LineCharPosition(int line, int charPosition) {
this.line = line;
this.charPosition = charPosition;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LineCharPosition)) return false;
LineCharPosition that = (LineCharPosition) o;
return line == that.line && charPosition == that.charPosition;
}
@Override
public String toString() {
return "Line " + line + ", Char " + charPosition;
}
}

View File

@@ -0,0 +1,55 @@
package de.dhbw.model.ParseError;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.atn.ATNConfigSet;
import org.antlr.v4.runtime.dfa.DFA;
import org.eclipse.lsp4j.*;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
public class DiagnoseErrorListener implements ANTLRErrorListener {
private final List<Diagnostic> errorMessages = new ArrayList<>();
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
int endCharPosition = charPositionInLine;
if (offendingSymbol instanceof Token) {
Token offendingToken = (Token) offendingSymbol;
// Berechne die Endposition anhand der Token-Länge
endCharPosition = charPositionInLine + offendingToken.getText().length();
}
// Erstelle den Range mit Start- und Endposition
Range errorRange = new Range(
new Position(line - 1, charPositionInLine), // Startposition
new Position(line - 1, endCharPosition) // Endposition
);
Diagnostic diagnostic = new Diagnostic(
errorRange,
msg,
DiagnosticSeverity.Error,
"JavaTX Language Server"
);
errorMessages.add(diagnostic);
}
@Override
public void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, boolean b, BitSet bitSet, ATNConfigSet atnConfigSet) {
}
@Override
public void reportAttemptingFullContext(Parser parser, DFA dfa, int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet) {
}
@Override
public void reportContextSensitivity(Parser parser, DFA dfa, int i, int i1, int i2, ATNConfigSet atnConfigSet) {
}
public List<Diagnostic> getErrorMessages() {
return errorMessages;
}
}

View File

@@ -1,61 +0,0 @@
package de.dhbw.model;
import org.antlr.v4.runtime.Token;
import java.util.Comparator;
import java.util.List;
public class PlaceholderPoint {
public final Token point;
private String insertString;
private int extraOffset = 0;
private final PlaceholderType kind;
public PlaceholderPoint(Token point, String toInsert, PlaceholderType kind) {
this.point = point;
this.kind = kind;
this.insertString = toInsert.endsWith(" ") ? toInsert : toInsert + " ";
}
public boolean isGenericClassInsertPoint() {
return kind == PlaceholderType.GENERIC_CLASS_INSERT;
}
public String insert(String intoSource, List<PlaceholderPoint> additionalOffset) {
return new StringBuilder(intoSource)
.insert(point.getStartIndex() + extraOffset, insertString)
.toString();
}
public String getInsertString() { return insertString; }
public void setInsertString(String insertString) { this.insertString = insertString; }
public PlaceholderType getKind() { return kind; }
public void addExtraOffset(int toAdd) { this.extraOffset += toAdd; }
public int getPositionInCode() { return point.getStartIndex() + extraOffset; }
@Override
public boolean equals(Object obj) {
return this == obj;
}
@Override
public int hashCode() {
return getPositionInCode() * 11 * insertString.hashCode();
}
@Override
public String toString() {
return point.getLine() + ":" + point.getCharPositionInLine() + ":" + insertString;
}
public static final class TypeInsertPointPositionComparator implements Comparator<PlaceholderPoint> {
@Override
public int compare(PlaceholderPoint o1, PlaceholderPoint o2) {
if (o1.point == null && o2.point == null) return 0;
if (o2.point == null) return 1;
if (o1.point == null) return -1;
return Integer.compare(o1.getPositionInCode(), o2.getPositionInCode());
}
}
}

View File

@@ -1,7 +0,0 @@
package de.dhbw.model;
public enum PlaceholderType {
NORMAL_INSERT,
GENERIC_CLASS_INSERT,
GENERIC_METHOD_INSERT
}

View File

@@ -1,66 +0,0 @@
package de.dhbw.model;
import de.dhbwstuttgart.typeinference.result.ResultPair;
import java.util.*;
public class PlaceholderVariable {
public final PlaceholderPoint point;
private final Set<PlaceholderPoint> inserts;
private final ResultPair<?, ?> resultPair;
public PlaceholderVariable(PlaceholderPoint point, Set<PlaceholderPoint> additionalPoints,
ResultPair<?, ?> resultPair) {
this.point = point;
this.inserts = additionalPoints;
this.resultPair = resultPair;
}
public String insert(String intoSource) {
List<PlaceholderPoint> insertsSorted = new ArrayList<>();
insertsSorted.add(point);
if (!point.getInsertString().contains("void")) {
insertsSorted.addAll(inserts);
}
insertsSorted.sort(new PlaceholderPoint.TypeInsertPointPositionComparator().reversed());
String result = intoSource;
for (PlaceholderPoint insertPoint : insertsSorted) {
result = insertPoint.insert(result, new ArrayList<>());
}
return result;
}
public String getInsertString() {
return point.getInsertString();
}
public void reducePackage() {
point.setInsertString(point.getInsertString()
.replaceAll("java\\.lang\\.", "")
.replaceAll("java\\.util\\.", ""));
}
public ResultPair<?, ?> getResultPair() {
return resultPair;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof PlaceholderVariable other)) return false;
return other.point.equals(this.point);
}
@Override
public int hashCode() {
return point.hashCode();
}
@Override
public String toString() {
return point.toString();
}
}

View File

@@ -1,4 +1,27 @@
package de.dhbw.model;
public record SnippetWithName(String name, String snippet) {
public class SnippetWithName {
private String name;
private String snippet;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSnippet() {
return snippet;
}
public void setSnippet(String snippet) {
this.snippet = snippet;
}
public SnippetWithName(String name, String snippet) {
this.name = name;
this.snippet = snippet;
}
}

View File

@@ -1,23 +1,27 @@
package de.dhbw.model;
import java.util.Objects;
public class Type {
String type;
boolean generic;
public record Type(String type, boolean generic) {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Type other)) return false;
return Objects.equals(type, other.type);
public Type(String type, boolean generic){
this.type = type;
this.generic = generic;
}
@Override
public int hashCode() {
return Objects.hashCode(type);
public void setGeneric(boolean generic) {
this.generic = generic;
}
@Override
public String toString() {
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public boolean isGeneric() {
return generic;
}
}

View File

@@ -1,35 +0,0 @@
package de.dhbw.service;
import de.dhbw.model.LSPVariable;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.InlayHint;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class CacheService {
private HashMap<String, List<InlayHint>> inlayHintsByUri = new HashMap<>();
private HashMap<String, List<Diagnostic>> diagnosticsByUri = new HashMap<>();
private HashMap<String, String> lastSavedFiles = new HashMap<>();
private List<LSPVariable> variables = new ArrayList<>();
public void reset() {
inlayHintsByUri.clear();
diagnosticsByUri.clear();
lastSavedFiles.clear();
variables.clear();
}
public void updateGlobalMaps(List<Diagnostic> diagnostics, List<InlayHint> hints, String uri) {
diagnosticsByUri.put(uri, diagnostics);
inlayHintsByUri.put(uri, hints);
}
public HashMap<String, List<InlayHint>> getInlayHintsByUri() { return inlayHintsByUri; }
public HashMap<String, List<Diagnostic>> getDiagnosticsByUri() { return diagnosticsByUri; }
public HashMap<String, String> getLastSavedFiles() { return lastSavedFiles; }
public List<LSPVariable> getVariables() { return variables; }
public void setVariables(List<LSPVariable> variables) { this.variables = variables; }
}

View File

@@ -1,59 +0,0 @@
package de.dhbw.service;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import java.util.List;
public class ClientService {
private LanguageClient client;
public ClientService(LanguageClient client) {
this.client = client;
}
public void setClient(LanguageClient client) {
this.client = client;
}
public LanguageClient getClient() {
return client;
}
public void publishDiagnostics(String uri, List<Diagnostic> diagnostics) {
client.publishDiagnostics(new PublishDiagnosticsParams(uri, diagnostics));
}
public void sendClientLog(MessageType type, String message) {
client.logMessage(new MessageParams(type, message));
}
public void showMessage(MessageType type, String message) {
client.showMessage(new MessageParams(type, message));
}
public void refreshClient() {
client.refreshInlayHints();
client.refreshDiagnostics();
}
public void startProgress(String taskName, String title) {
Either<String, Integer> token = Either.forLeft(taskName);
client.createProgress(new WorkDoneProgressCreateParams(token));
WorkDoneProgressBegin begin = new WorkDoneProgressBegin();
begin.setTitle(title);
begin.setCancellable(false);
begin.setPercentage(0);
client.notifyProgress(new ProgressParams(token, Either.forLeft(begin)));
}
public void stopProgress(String taskName, String message) {
Either<String, Integer> token = Either.forLeft(taskName);
WorkDoneProgressEnd end = new WorkDoneProgressEnd();
end.setMessage(message);
client.notifyProgress(new ProgressParams(token, Either.forLeft(end)));
}
}

View File

@@ -1,30 +0,0 @@
package de.dhbw.service;
import org.eclipse.lsp4j.Range;
import java.util.HashMap;
public class DocumentStore {
private final HashMap<String, String> files = new HashMap<>();
public String getFileContent(String uri) {
return files.get(uri);
}
public void saveFile(String uri, String content) {
files.put(uri, content);
}
public void clear() {
files.clear();
}
public boolean rangesOverlap(Range range1, Range range2) {
int start1 = range1.getStart().getLine() * 10000 + range1.getStart().getCharacter();
int end1 = range1.getEnd().getLine() * 10000 + range1.getEnd().getCharacter();
int start2 = range2.getStart().getLine() * 10000 + range2.getStart().getCharacter();
int end2 = range2.getEnd().getLine() * 10000 + range2.getEnd().getCharacter();
return start1 <= end2 && start2 <= end1;
}
}

View File

@@ -1,31 +0,0 @@
package de.dhbw.service;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.MessageType;
public class LogService {
private final Logger logger = LogManager.getLogger(LogService.class);
private final ClientService clientService;
public LogService(ClientService clientService) {
this.clientService = clientService;
}
public void log(String message, MessageType type) {
clientService.sendClientLog(type, message);
switch (type) {
case Error -> logger.error(message);
case Warning -> logger.warn(message);
case Info -> logger.info(message);
default -> logger.debug(message);
}
}
public void log(String message) {
clientService.sendClientLog(MessageType.Info, message);
logger.info(message);
}
}

View File

@@ -1,21 +0,0 @@
package de.dhbw.service;
import de.dhbw.helper.ConversionHelper;
import de.dhbwstuttgart.languageServerInterface.ParserInterface;
import org.eclipse.lsp4j.Diagnostic;
import java.util.List;
public class ParserService {
private final ConversionHelper conversionHelper;
public ParserService(ConversionHelper conversionHelper) {
this.conversionHelper = conversionHelper;
}
public List<Diagnostic> getDiagnosticsOfErrors(String input, String uri) {
ParserInterface parserInterface = new ParserInterface();
return conversionHelper.parseErrorsToDiagnostics(parserInterface.getParseErrors(input));
}
}

View File

@@ -1,23 +1,15 @@
import de.dhbw.helper.CodeSnippetOptions;
import de.dhbw.model.SnippetWithName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CodeSnippetOptionsTest {
@Test
public void testSnippetsAreProvided() {
public void testForLoop() {
CodeSnippetOptions options = new CodeSnippetOptions();
assertEquals(3, options.getSnippets().size());
}
@Test
public void testForLoopSnippet() {
CodeSnippetOptions options = new CodeSnippetOptions();
var forLoop = options.getSnippets().stream()
.filter(s -> s.name().equals("forLoop"))
.findFirst()
.orElseThrow();
assertEquals("for(i = 0; i < ${1:listSize}; i++){\n\n}", forLoop.snippet());
assertEquals(options.getForLoopSnippet().getSnippet(),"for(i = 0; i < ${1:listSize}; i++){\n\n}");
}
}

View File

@@ -0,0 +1,144 @@
import de.dhbw.helper.TextHelper;
import de.dhbw.helper.TypeResolver;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.net.URISyntaxException;
public class CompilerInterfaceTest {
@Test
public void testAbstractSyntaxAsString() throws IOException, ClassNotFoundException, URISyntaxException {
TypeResolver typeResolver = new TypeResolver();
var res = typeResolver.infereInput("import java.lang.Integer;\n import java.lang.String;\n" +
"public class test{\n" +
"public main(test){" +
"if(0>1){" +
"return \"w\";" +
"}" +
"Integer i = 0;" +
"return i;" +
"}" +
"}", "c%3A/Users/ruben/Neuer%20Ordner%20%282%29/LSP-Vortrag/images/test.jav");
var res2 = typeResolver.infereInput("import java.lang.Integer;\n import java.lang.String;\n" +
"public class test{\n" +
"public main(test){" +
"if(0>1){" +
"return \"w\";" +
"}" +
"Integer i = 0;" +
"return i;" +
"}" +
"}", "c%3A/Users/ruben/Neuer%20Ordner%20%282%29/LSP-Vortrag/images/test.jav");
System.out.println("TEST OUTPUT:");
//
// ArrayList<String> allTypes = new ArrayList<>();
//
// res.getResultSets().forEach(el -> allTypes.add(el.resolveType(res.getAst().getAllMethods().get(0).getReturnType()).resolvedType.toString()));
// var results = res.getGeneratedGenerics().entrySet().iterator().next();
//
//
// System.out.println(res.getResultSets().toString());
//
//
// System.out.println("\n\n\nPRINTED AST:");
// System.out.println(res.getPrintedAst());
}
@Test
public void testConstraintTypes() throws IOException, ClassNotFoundException {
// LanguageServerInterface languageServer = new LanguageServerInterface();
// TypeResolver typeResolver = new TypeResolver();
//
//
// var res = typeResolver.infereInput("import java.lang.Integer; public class test{\n" +
// " \n" +
// " public main(testa){\n" +
// " return testa;\n" +
// " }\n" +
// "}");
// res.forEach(el -> el.getPossibleTypes().forEach(el2 -> System.out.println(el2.getType() + " " + (el2.isGeneric() ? "GENERIC" : "NO GENERIC"))));
}
@Test
public void testTypeFinder() throws IOException, ClassNotFoundException {
// TypeResolver typeResolver = new TypeResolver();
// var inferedMethods = typeResolver.infereInput("import java.lang.Integer;\n import java.lang.String;\n" +
// "public class test{\n" +
// "public main(test){" +
// "if(0>1){" +
// "return \"w\";" +
// "}" +
// "Integer i = 0;" +
// "return i;" +
// "}" +
// "}"
// );
//
// inferedMethods.forEach(el -> System.out.println(el.getName() + ": " + el.getPossibleTypes() + " | " + el.getLine() + " " + el.getCharPosition()));
}
@Test
public void testGenericTypes() throws IOException, ClassNotFoundException {
// TypeResolver typeResolver = new TypeResolver();
// var inferedMethods = typeResolver.infereInput("import java.lang.Integer; public class test{\n" +
// " \n" +
// " public main(testa){\n" +
// " return testa;\n" +
// " }\n" +
// "}"
// );
//
// inferedMethods.forEach(el -> System.out.println(el.getName() + ": " + el.getPossibleTypes() + " | " + el.getLine() + " " + el.getCharPosition()));
}
@Test
public void testTypeFinderParameter() throws IOException, ClassNotFoundException {
// TypeResolver typeResolver = new TypeResolver();
// var inferedMethods = typeResolver.infereInput("import java.lang.Integer;\n" +
// "import java.lang.String; \n" +
// "public class test{\n" +
// " public main(test, test2){\n" +
// " if(1>0){\n" +
// " return test;\n" +
// " }\n" +
// " String i = test;\n" +
// " return 1;\n" +
// "\n" +
// "\n" +
// " }\n" +
// "}"
// );
//
// inferedMethods.forEach(el -> System.out.println(el.getName() + ": " + el.getPossibleTypes()));
}
@Test
public void testCharEnding() throws IOException, ClassNotFoundException {
TextHelper textHelper = new TextHelper();
var endingChar = textHelper.getEndingCharOfStartingChar(3, 13, "import java.lang.Integer;\n" +
"import java.lang.String; \n" +
"public class test{\n" +
" public main(test, test2){\n" +
" if(1>0){\n" +
" return test;\n" +
" }\n" +
" String i = test;\n" +
" return 1;\n" +
"\n" +
"\n" +
" }\n" +
"}"
);
Assert.assertEquals(15, (int) endingChar);
}
}

View File

@@ -0,0 +1,17 @@
import de.dhbw.JavaTXTextDocumentService;
import de.dhbw.helper.CodeSnippetOptions;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JavaTXLanguageDocumentServiceTest {
@Test
@Ignore
public void testWordExtraction() {
// JavaTXTextDocumentService service = new JavaTXTextDocumentService();
// service.didSave(new DidSaveTextDocumentParams(new TextDocumentIdentifier("file:///c%3A/Users/ruben/Neuer%20Ordner%20%282%29/LSP-Vortrag/images/test.jav")));
}
}

View File

@@ -1,28 +0,0 @@
import de.dhbw.helper.TextHelper;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TextHelperTest {
@Test
public void testEndingCharOfStartingChar() {
TextHelper textHelper = new TextHelper();
String input = "import java.lang.Integer;\n" +
"import java.lang.String;\n" +
"public class test{\n" +
" public main(test, test2){\n" +
" }\n" +
"}";
int endingChar = textHelper.getEndingCharOfStartingChar(3, 13, input);
assertEquals(17, endingChar);
}
@Test
public void testGetTextOfChars() {
TextHelper textHelper = new TextHelper();
String input = "line zero\nline one\nline two";
assertEquals("line", textHelper.getTextOfChars(input, 1, 0, 4));
}
}

View File

@@ -55,16 +55,11 @@ The Language Server in itself can be used for any Client. The Clients task is to
![Diagram](docs/diagram.png)
## Additional Information
- Uses the [LSP-Interface](https://gitea.hb.dhbw-stuttgart.de/JavaTX/JavaCompilerCore/src/branch/LSP-Interface) Branch of the Java-TX Compiler Repository
## Update JavaTX Compiler Dependency as Maven Package
If you make changes in the Compiler Interface, you have to change the jar and therefore the Dependency in the Java TX Language Server
You can follow this steps:
1. package the JavaTX Compiler
2. create a lib Folder at ./LangaugeServer -> ./LanguageServer/lib
2. take the Jar-File and copy it into the /lib Folder at
2. take the Jar-File and copy it into the /lib Folder
3. execute this Maven command to add the Jar in your local Repository: ```mvn install:install-file -Dfile=lib/JavaTXcompiler-0.1-jar-with-dependencies.jar -DgroupId=de.dhbwstuttgart -DartifactId=JavaTXcompiler -Dversion=0.1 -Dpackaging=jar```
4. run ```maven clean```, ```validate``` and ```install``` to load the new Dependency
5. you can now package the Language Server or change the code accordingly.

View File

@@ -1,5 +0,0 @@
public class t{
public test(){
return 1;
}
}