Skip to content

Commit

Permalink
Merge pull request #23 from sillydan1/feature/lsp
Browse files Browse the repository at this point in the history
Feature/lsp
  • Loading branch information
sillydan1 authored Dec 8, 2023
2 parents 2a5547c + ca9d452 commit 95e63a0
Show file tree
Hide file tree
Showing 33 changed files with 1,295 additions and 331 deletions.
37 changes: 19 additions & 18 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,36 +106,37 @@
- [x] TODOs round
- [x] update readme (screenshots etc)
- [x] Release `v1.1.0
- [ ] GraphDiff algorithm (look at https://stackoverflow.com/questions/16553343/diff-for-directed-acyclic-graphs)
- [ ] Additions (new syntactic elements), Deletions (B does not have some syntactic element), Edits (Some syntactic element's properties has been changed)
- [ ] Cut / Copy / Paste support
- [ ] Plan for git integration
- [ ] Diff view?
- [x] GraphDiff algorithm (look at https://stackoverflow.com/questions/16553343/diff-for-directed-acyclic-graphs)
- [x] Additions (new syntactic elements), Deletions (B does not have some syntactic element), Edits (Some syntactic element's properties has been changed)
- [x] Cut / Copy / Paste support
- [x] Lint layer
- [ ] Lint protobuf specification
- [ ] Implement `ILint` / `ILinter` interfaces
- [x] Implement `ILint` / `ILinter` interfaces
- [x] Release core as a library on ossrh
- [ ] Additional Syntaxes
- [x] Additional Syntaxes
- [x] Simple Text
- [x] LTS
- [ ] NTTA
- [ ] HAWK
- [x] P/N
- [ ] TIOA
- [ ] Plugin Manager
- [ ] Fix TODOs
- [ ] Polish
- [ ] Release `v1.2.0
- [ ] Third party language plugins
- [ ] TIOA
- [ ] NTTA
- [ ] HAWK
- [ ] expr richtextfx.CodeArea implementation
- [ ] Trace-traverser & specification
- [ ] Release `v1.3.0`
- [ ] LSP like specification (use docusaurus, or github wiki)
- [ ] Plan for git integration
- [ ] Diff view?
- [x] Add plugin API and
- [x] LSP like specification (use docusaurus, or github wiki)
- [ ] Protobuf specification (that way, you are language agnostic)
- [ ] Implement `ILsp` / `ILspEngine` interfaces
- [ ] Release `v1.4.0`
- [x] Implement `ILsp` / `ILspEngine` interfaces
- [ ] Release `v1.3.0`
- [ ] DAP like specification
- [ ] Protobuf specification (that way, you are language agnostic)
- [ ] Implement `IDap` / `IDapEngine` interfaces
- [ ] Release `v1.5.0`
- [ ] Add plugin API and [LuaJava](https://github.com/gudzpoz/luajava/tree/main)
- [ ] Custom keybinds
- [ ] Rewrite the default plugins as a lua plugin. This will simplify the codebase tremendously
- [ ] Release `v1.4.0`
- [ ] Example lua plugin [LuaJava](https://github.com/gudzpoz/luajava/tree/main).
- [ ] Release `v2.0.0`
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ subprojects {
implementation group: 'dk.yalibs', name: 'yafunc', version: '1.0.0';
implementation group: 'dk.yalibs', name: 'yastreamgobbler', version: '1.0.0';
implementation group: 'dk.yalibs', name: 'yaundo', version: '1.0.0';
implementation group: 'dk.yalibs', name: 'yafunc', version: '1.0.0';
implementation group: 'io.github.mkpaz', name: 'atlantafx-base', version: '2.0.1';
implementation group: 'org.apache.tika', name: 'tika-core', version: '2.9.0'
implementation group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.11.0'
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/java/dk/gtz/graphedit/model/ModelLint.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package dk.gtz.graphedit.model;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

public record ModelLint(
String modelKey,
String lintIdentifier,
ModelLintSeverity severity,
String title,
String message,
Optional<String> lintDescription,
List<UUID> affectedElements,
List<List<ModelPoint>> affectedRegions) {}

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package dk.gtz.graphedit.model;

public enum ModelLintSeverity {
HINT,
INFO,
WARNING,
ERROR
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dk.gtz.graphedit.model.lsp;

public record ModelLanguageServerProgress(
String token,
ModelLanguageServerProgressType type,
String title,
String message) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dk.gtz.graphedit.model.lsp;

public enum ModelLanguageServerProgressType {
BEGIN,
PROGRESS,
END
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package dk.gtz.graphedit.model.lsp;

public record ModelNotification(
String level,
String message) {}
51 changes: 51 additions & 0 deletions core/src/main/java/dk/gtz/graphedit/spi/ILanguageServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package dk.gtz.graphedit.spi;

import java.io.File;
import java.util.Collection;

import dk.gtz.graphedit.model.ModelLint;
import dk.gtz.graphedit.model.lsp.ModelLanguageServerProgress;
import dk.gtz.graphedit.model.lsp.ModelNotification;
import dk.gtz.graphedit.viewmodel.IBufferContainer;
import dk.yalibs.yafunc.IRunnable1;

public interface ILanguageServer {
/**
* Get the name of the language that the server supports.
* Expected to match what {@link ISyntaxFactory#getSyntaxName} provides
* @return A string with the name of the supported language.
*/
String getLanguageName();

/**
* Get the name of the language server (not to be confused with {@link getLanguageName})
* @return A string representing the name of the language server
*/
String getServerName();

/**
* Get the version string of the language server.
* i.e. "v1.0.0"
* @return A string representing the version of the language server
*/
String getServerVersion();

/**
* Function to initialize (not start) the language server
* This will be called right before any callback functions are added
* @param projectFile The Graphedit project file.
* @param bufferContainer The collection of buffers, likely (but not guaranteed) to be empty at initialization
*/
void initialize(File projectFile, IBufferContainer bufferContainer);

/**
* Start the language server.
* This will be called from a seperate thread and will be interrupted via {@link Thread#interrupt} when it's time to close.
* This means that this function should be a blocking call
*/
void start();
Collection<ModelLint> getDiagnostics();
void addDiagnosticsCallback(IRunnable1<Collection<ModelLint>> callback);
void addNotificationCallback(IRunnable1<ModelNotification> callback);
void addProgressCallback(IRunnable1<ModelLanguageServerProgress> callback);
}
4 changes: 4 additions & 0 deletions core/src/main/java/dk/gtz/graphedit/spi/IPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@ default Collection<ISyntaxFactory> getSyntaxFactories() throws Exception {
default Collection<IPluginPanel> getPanels() throws Exception {
return List.of();
}

default Collection<ILanguageServer> getLanguageServers() throws Exception {
return List.of();
}
}
187 changes: 187 additions & 0 deletions core/src/main/java/dk/gtz/graphedit/tool/ClipboardTool.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package dk.gtz.graphedit.tool;

import java.util.HashMap;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

import org.kordamp.ikonli.bootstrapicons.BootstrapIcons;
import org.kordamp.ikonli.javafx.FontIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import dk.gtz.graphedit.events.ViewportKeyEvent;
import dk.gtz.graphedit.exceptions.SerializationException;
import dk.gtz.graphedit.exceptions.UncomparableException;
import dk.gtz.graphedit.model.ModelGraph;
import dk.gtz.graphedit.serialization.IModelSerializer;
import dk.gtz.graphedit.viewmodel.IBufferContainer;
import dk.gtz.graphedit.viewmodel.ViewModelDiff;
import dk.gtz.graphedit.viewmodel.ViewModelEdge;
import dk.gtz.graphedit.viewmodel.ViewModelGraph;
import dk.gtz.graphedit.viewmodel.ViewModelPoint;
import dk.gtz.graphedit.viewmodel.ViewModelProjectResource;
import dk.gtz.graphedit.viewmodel.ViewModelSelection;
import dk.gtz.graphedit.viewmodel.ViewModelVertex;
import dk.yalibs.yadi.DI;
import dk.yalibs.yaundo.IUndoSystem;
import dk.yalibs.yaundo.Undoable;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

/**
* Tool that enables cut/copy/paste support
*
* - ctrl + x to cut
* - ctrl + c to copy
* - ctrl + v to paste
*/
public class ClipboardTool extends AbstractBaseTool {
private final Logger logger = LoggerFactory.getLogger(ClipboardTool.class);
private final ObservableList<ViewModelSelection> selectedElements;
private final IModelSerializer serializer;
private final IBufferContainer buffers;
private final Clipboard clipboard;
private final IUndoSystem undoSystem;
private final MassDeleteTool deleteTool;

public ClipboardTool() {
selectedElements = DI.get("selectedElements");
serializer = DI.get(IModelSerializer.class);
buffers = DI.get(IBufferContainer.class);
undoSystem = DI.get(IUndoSystem.class);
clipboard = Clipboard.getSystemClipboard();
deleteTool = new MassDeleteTool();
}

@Override
public String getHelpDescription() {
return """
Tool that enables cut/copy/paste support
- <Shortcut> + X to cut
- <Shortcut> + C to copy
- <Shortcut> + V to paste
""";
}

@Override
public Optional<String> getTooltip() {
return Optional.of("copy and paste sections of models");
}

@Override
public Node getGraphic() {
return new FontIcon(BootstrapIcons.CLIPBOARD);
}

@Override
public void onKeyEvent(ViewportKeyEvent e) {
if(!e.event().isShortcutDown())
return;
if(!e.event().getEventType().equals(KeyEvent.KEY_PRESSED))
return;
if(e.event().getCode().equals(KeyCode.C))
copySelection(e);
if(e.event().getCode().equals(KeyCode.V))
pasteModel(e);
if(e.event().getCode().equals(KeyCode.X))
cutSelection(e);
}

public void copySelection(ViewportKeyEvent e) {
try {
if(selectedElements.isEmpty())
return;
var buffer = buffers.get(e.bufferId());
var selectedVertices = buffer.syntax().vertices().entrySet().stream()
.filter(elem -> selectedElements.stream().anyMatch(s -> s.id().equals(elem.getKey())))
.collect(Collectors.toMap(
elem -> elem.getKey(),
elem -> elem.getValue().toModel()));
var selectedEdges = buffer.syntax().edges().entrySet().stream()
.filter(elem -> selectedElements.stream().anyMatch(s -> s.id().equals(elem.getKey())))
.collect(Collectors.toMap(
elem -> elem.getKey(),
elem -> elem.getValue().toModel()));
for(var edge : selectedEdges.entrySet()) {
if(!selectedVertices.containsKey(edge.getValue().source))
selectedVertices.put(edge.getValue().source, buffer.syntax().vertices().get(edge.getValue().source).toModel());
if(!selectedVertices.containsKey(edge.getValue().target))
selectedVertices.put(edge.getValue().target, buffer.syntax().vertices().get(edge.getValue().target).toModel());
}
var modelGraph = new ModelGraph("", selectedVertices, selectedEdges); // NOTE: Declarations string cannot be copied by this tool
var resource = new ViewModelProjectResource(buffer.metadata(), new ViewModelGraph(modelGraph, e.syntax())).toModel();
var serializedModel = serializer.serialize(resource);
var content = new ClipboardContent();
content.putString(serializedModel);
clipboard.setContent(content);
logger.trace("copied {} vertices and {} edges to clipboard", selectedVertices.size(), selectedEdges.size());
} catch (Exception exc) {
throw new RuntimeException(exc);
}
}

public void pasteModel(ViewportKeyEvent e) {
try {
if(!clipboard.hasString()) {
logger.error("no plain text in clipboard");
return;
}
var syntaxFactory = e.syntax();
var buffer = buffers.get(e.bufferId());
var content = clipboard.getString();
var resource = new ViewModelProjectResource(serializer.deserializeProjectResource(content), e.syntax());
var vertexOffset = new ViewModelPoint(e.editorSettings().gridSizeX().get(), e.editorSettings().gridSizeY().get());
var rerolledVertex = new HashMap<UUID,ViewModelVertex>();
var rerolledEdges = new HashMap<UUID,ViewModelEdge>();
var remapping = new HashMap<UUID,UUID>();
for(var vertex : resource.syntax().vertices().entrySet()) {
var v = vertex.getValue();
var rerolled = UUID.randomUUID();
v.position().setValue(v.position().add(vertexOffset));
rerolledVertex.put(rerolled, syntaxFactory.createVertexViewModel(rerolled, v.toModel()));
remapping.put(vertex.getKey(), rerolled);
}
for(var edge : resource.syntax().edges().entrySet()) {
var ev = edge.getValue();
var rerolled = UUID.randomUUID();
ev.source().set(remapping.get(ev.source().get()));
ev.target().set(remapping.get(ev.target().get()));
assert ev.source().get() != null;
assert ev.target().get() != null;
rerolledEdges.put(rerolled, syntaxFactory.createEdgeViewModel(rerolled, ev.toModel()));
}
resource.syntax().vertices().clear();
resource.syntax().vertices().putAll(rerolledVertex);
resource.syntax().edges().clear();
resource.syntax().edges().putAll(rerolledEdges);
var diff = ViewModelDiff.compare(buffer, resource);
ViewModelDiff.applyAdditiveOnly(buffer, diff);
undoSystem.push(new Undoable("paste content",
() -> ViewModelDiff.revertAdditiveOnly(buffer, diff),
() -> ViewModelDiff.applyAdditiveOnly(buffer, diff)));
} catch (SerializationException exc) {
logger.trace("clipboard value not deserializable {}", exc.getMessage());
} catch (UncomparableException exc) {
logger.warn("clipboard model and target model are different syntaxes");
} catch (Exception exc) {
throw new RuntimeException(exc);
}
}

public void cutSelection(ViewportKeyEvent e) {
try {
if(selectedElements.isEmpty())
return;
copySelection(e);
deleteTool.deleteSelectedElements(e);
} catch(Exception exc) {
throw new RuntimeException(exc);
}
}
}
Loading

0 comments on commit 95e63a0

Please sign in to comment.