feat: Improved docs, more reliable file sync

This commit is contained in:
FalsePattern 2024-03-08 16:23:24 +01:00
parent 8a0c862446
commit 23b72086bc
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
8 changed files with 129 additions and 129 deletions

View file

@ -16,10 +16,9 @@
package com.falsepattern.zigbrains.common.util;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.project.NoAccessDuringPsiEvents;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Condition;
import lombok.val;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -50,16 +49,7 @@ public class ApplicationUtil {
}
static public <T> T computableReadAction(Computable<T> computable) {
if (ApplicationManager.getApplication().isDispatchThread() ||
ApplicationManagerEx.getApplicationEx().holdsReadLock()) {
return ApplicationManager.getApplication().runReadAction(computable);
} else {
var result = new Object() {
T value = null;
};
ApplicationManager.getApplication().invokeAndWait(() -> result.value = ApplicationManager.getApplication().runReadAction(computable));
return result.value;
}
}
static public void writeAction(Runnable runnable) {
@ -70,15 +60,15 @@ public class ApplicationUtil {
return ApplicationManager.getApplication().runWriteAction(computable);
}
static public void invokeAfterPsiEvents(Runnable runnable) {
static public void invokeAfterPsiEvents(Runnable runnable, boolean readLock, boolean writeLock) {
Runnable wrapper = () -> {
if (NoAccessDuringPsiEvents.isInsideEventProcessing()) {
invokeAfterPsiEvents(runnable);
invokeAfterPsiEvents(runnable, readLock, writeLock);
} else {
runnable.run();
}
};
ApplicationManager.getApplication().invokeLater(wrapper, (Condition<Void>) value -> false);
val app = ApplicationManager.getApplication();
app.invokeLater(writeLock ? wrapper : () -> app.executeOnPooledThread(readLock ? () -> app.runReadAction(wrapper) : wrapper));
}
}

View file

@ -23,6 +23,7 @@ import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter;
import com.intellij.model.Pointer;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
@ -33,6 +34,7 @@ import com.intellij.platform.backend.presentation.TargetPresentation;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiFileRange;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
@ -102,13 +104,15 @@ public class LSPDocumentationTargetProvider implements DocumentationTargetProvid
return null;
}
String string = HoverHandler.getHoverString(hover);
val markdown = HoverHandler.getHoverString(hover);
val string = ApplicationUtil.computableReadAction(() -> DocMarkdownToHtmlConverter
.convert(manager.getProject(), markdown));
if (StringUtils.isEmpty(string)) {
LOG.warn(String.format("Hover string returned is empty for file %s and pos (%d;%d)",
identifier.getUri(), serverPos.getLine(), serverPos.getCharacter()));
return null;
}
return DocumentationResult.documentation(string.lines().collect(Collectors.joining("<br>\n")));
return DocumentationResult.documentation(string);
} catch (TimeoutException e) {
LOG.warn(e);
wrapper.notifyFailure(Timeouts.HOVER);

View file

@ -78,48 +78,49 @@ public class DocumentEventManager {
DidChangeTextDocumentParams changesParams = new DidChangeTextDocumentParams(new VersionedTextDocumentIdentifier(),
Collections.singletonList(new TextDocumentContentChangeEvent()));
changesParams.getTextDocument().setUri(identifier.getUri());
changesParams.getTextDocument().setVersion(++version);
if (syncKind == TextDocumentSyncKind.Incremental) {
TextDocumentContentChangeEvent changeEvent = changesParams.getContentChanges().get(0);
CharSequence newText = event.getNewFragment();
int offset = event.getOffset();
int newTextLength = event.getNewLength();
EditorEventManager editorEventManager = EditorEventManagerBase.forUri(FileUtils.documentToUri(document));
if (editorEventManager == null) {
LOG.warn("no editor associated with document");
return;
}
Editor editor = editorEventManager.editor;
Position lspPosition = DocumentUtils.offsetToLSPPos(editor, offset);
if (lspPosition == null) {
return;
}
int startLine = lspPosition.getLine();
int startColumn = lspPosition.getCharacter();
CharSequence oldText = event.getOldFragment();
//if text was deleted/replaced, calculate the end position of inserted/deleted text
int endLine, endColumn;
if (oldText.length() > 0) {
endLine = startLine + StringUtil.countNewLines(oldText);
String content = oldText.toString();
String[] oldLines = content.split("\n");
int oldTextLength = oldLines.length == 0 ? 0 : oldLines[oldLines.length - 1].length();
endColumn = content.endsWith("\n") ? 0 : oldLines.length == 1 ? startColumn + oldTextLength : oldTextLength;
} else { //if insert or no text change, the end position is the same
endLine = startLine;
endColumn = startColumn;
}
Range range = new Range(new Position(startLine, startColumn), new Position(endLine, endColumn));
changeEvent.setRange(range);
changeEvent.setText(newText.toString());
} else if (syncKind == TextDocumentSyncKind.Full) {
// TODO this incremental update logic is kinda broken, investigate later...
// if (syncKind == TextDocumentSyncKind.Incremental) {
// TextDocumentContentChangeEvent changeEvent = changesParams.getContentChanges().get(0);
// CharSequence newText = event.getNewFragment();
// int offset = event.getOffset();
// int newTextLength = event.getNewLength();
//
// EditorEventManager editorEventManager = EditorEventManagerBase.forUri(FileUtils.documentToUri(document));
// if (editorEventManager == null) {
// LOG.warn("no editor associated with document");
// return;
// }
// Editor editor = editorEventManager.editor;
// Position lspPosition = DocumentUtils.offsetToLSPPos(editor, offset);
// if (lspPosition == null) {
// return;
// }
// int startLine = lspPosition.getLine();
// int startColumn = lspPosition.getCharacter();
// CharSequence oldText = event.getOldFragment();
//
// //if text was deleted/replaced, calculate the end position of inserted/deleted text
// int endLine, endColumn;
// if (oldText.length() > 0) {
// endLine = startLine + StringUtil.countNewLines(oldText);
// String content = oldText.toString();
// String[] oldLines = content.split("\n");
// int oldTextLength = oldLines.length == 0 ? 0 : oldLines[oldLines.length - 1].length();
// endColumn = content.endsWith("\n") ? 0 : oldLines.length == 1 ? startColumn + oldTextLength : oldTextLength;
// } else { //if insert or no text change, the end position is the same
// endLine = startLine;
// endColumn = startColumn;
// }
// Range range = new Range(new Position(startLine, startColumn), new Position(endLine, endColumn));
// changeEvent.setRange(range);
// changeEvent.setText(newText.toString());
// } else if (syncKind == TextDocumentSyncKind.Full) {
if (syncKind != TextDocumentSyncKind.None) {
changesParams.getContentChanges().get(0).setText(document.getText());
}
// }
ApplicationUtil.pool(() -> wrapper.getRequestManager().didChange(changesParams));
}

View file

@ -28,7 +28,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileMoveEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.FileChangeType;
import org.eclipse.lsp4j.FileEvent;
@ -80,9 +80,9 @@ class LSPFileEventManager {
ApplicationUtil.invokeAfterPsiEvents(() -> {
EditorEventManagerBase.documentSaved(uri);
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri,
FileUtils.projectToUri(p), FileChangeType.Changed));
});
FileUtils.findProjectsFor(file)
.forEach(p -> changedConfiguration(uri, FileUtils.projectToUri(p), FileChangeType.Changed));
}, false, false);
}
/**
@ -90,7 +90,7 @@ class LSPFileEventManager {
*
* @param event The file move event
*/
static void fileMoved(VirtualFileMoveEvent event) {
static void fileMoved(VFileMoveEvent event) {
try {
VirtualFile file = event.getFile();
if (!FileUtils.isFileSupported(file)) {
@ -102,7 +102,7 @@ class LSPFileEventManager {
if (newFileUri == null || oldParentUri == null) {
return;
}
String oldFileUri = String.format("%s/%s", oldParentUri, event.getFileName());
String oldFileUri = String.format("%s/%s", oldParentUri, event.getFile().getName());
closeAndReopenAffectedFile(file, oldFileUri);
} catch (Exception e) {
LOG.warn("LSP file move event failed due to :", e);
@ -125,7 +125,7 @@ class LSPFileEventManager {
ApplicationUtil.invokeAfterPsiEvents(() -> {
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri,
FileUtils.projectToUri(p), FileChangeType.Deleted));
});
}, true, true);
}
/**
@ -143,6 +143,7 @@ class LSPFileEventManager {
.flatMap(p -> searchFiles(newFileName, p).stream())
.collect(Collectors.toSet());
ApplicationUtil.invokeLater(() -> {
for (VirtualFile file : files) {
if (!FileUtils.isFileSupported(file)) {
continue;
@ -151,10 +152,11 @@ class LSPFileEventManager {
String oldFileUri = newFileUri.replace(file.getName(), oldFileName);
closeAndReopenAffectedFile(file, oldFileUri);
}
});
} catch (Exception e) {
LOG.warn("LSP file rename event failed due to : ", e);
}
});
}, true, false);
}
private static void closeAndReopenAffectedFile(VirtualFile file, String oldFileUri) {
@ -195,7 +197,7 @@ class LSPFileEventManager {
ApplicationUtil.invokeAfterPsiEvents(() -> {
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri,
FileUtils.projectToUri(p), FileChangeType.Created));
});
}, true, true);
}
}

View file

@ -21,9 +21,42 @@ import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.VirtualFileListener;
import com.intellij.openapi.vfs.VirtualFileMoveEvent;
import com.intellij.openapi.vfs.VirtualFilePropertyEvent;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent;
import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent;
import lombok.val;
import org.jetbrains.annotations.NotNull;
public class VFSListener implements VirtualFileListener {
import java.util.List;
public class VFSListener implements BulkFileListener {
@Override
public void before(@NotNull List<? extends @NotNull VFileEvent> events) {
}
@Override
public void after(@NotNull List<? extends @NotNull VFileEvent> events) {
for (val event: events) {
if (event instanceof VFilePropertyChangeEvent propEvent)
propertyChanged(propEvent);
else if (event instanceof VFileContentChangeEvent changeEvent)
contentsChanged(changeEvent);
else if (event instanceof VFileDeleteEvent deleteEvent)
fileDeleted(deleteEvent);
else if (event instanceof VFileMoveEvent moveEvent)
fileMoved(moveEvent);
else if (event instanceof VFileCopyEvent copyEvent)
fileCopied(copyEvent);
else if (event instanceof VFileCreateEvent createEvent)
fileCreated(createEvent);
}
}
/**
* Fired when a virtual file is renamed from within IDEA, or its writable status is changed.
@ -31,8 +64,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void propertyChanged(@NotNull VirtualFilePropertyEvent event) {
public void propertyChanged(@NotNull VFilePropertyChangeEvent event) {
if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) {
LSPFileEventManager.fileRenamed((String) event.getOldValue(), (String) event.getNewValue());
}
@ -43,8 +75,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void contentsChanged(@NotNull VirtualFileEvent event) {
public void contentsChanged(@NotNull VFileContentChangeEvent event) {
LSPFileEventManager.fileChanged(event.getFile());
}
@ -53,8 +84,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void fileDeleted(@NotNull VirtualFileEvent event) {
public void fileDeleted(@NotNull VFileDeleteEvent event) {
LSPFileEventManager.fileDeleted(event.getFile());
}
@ -63,8 +93,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void fileMoved(@NotNull VirtualFileMoveEvent event) {
public void fileMoved(@NotNull VFileMoveEvent event) {
LSPFileEventManager.fileMoved(event);
}
@ -73,9 +102,8 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void fileCopied(@NotNull VirtualFileCopyEvent event) {
fileCreated(event);
public void fileCopied(@NotNull VFileCopyEvent event) {
LSPFileEventManager.fileCreated(event.findCreatedFile());
}
/**
@ -83,44 +111,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
public void fileCreated(@NotNull VFileCreateEvent event) {
LSPFileEventManager.fileCreated(event.getFile());
}
/**
* Fired before the change of a name or writable status of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforePropertyChange(@NotNull VirtualFilePropertyEvent event) {
}
/**
* Fired before the change of contents of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforeContentsChange(@NotNull VirtualFileEvent event) {
}
/**
* Fired before the deletion of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforeFileDeletion(@NotNull VirtualFileEvent event) {
}
/**
* Fired before the movement of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforeFileMovement(@NotNull VirtualFileMoveEvent event) {
}
}

View file

@ -16,7 +16,6 @@
package com.falsepattern.zigbrains.lsp.requests;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.ui.UIUtil;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import org.eclipse.lsp4j.Hover;
@ -66,7 +65,7 @@ public class HoverHandler {
result.add(renderer.render(parser.parse(string)));
}
}
return "<html>" + String.join("\n\n", result) + "</html>";
return String.join("\n", result);
} else {
return "";
}
@ -75,9 +74,7 @@ public class HoverHandler {
if (markedContent.isEmpty()) {
return "";
}
Parser parser = Parser.builder().build();
HtmlRenderer renderer = HtmlRenderer.builder().build();
return "<html>" + renderer.render(parser.parse(markedContent)) + "</html>";
return markedContent;
} else {
return "";
}

View file

@ -17,9 +17,12 @@
package com.falsepattern.zigbrains.zig.lsp;
import com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition.RawCommandServerDefinition;
import lombok.val;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.PublishDiagnosticsCapabilities;
import java.util.List;
public class ZLSServerDefinition extends RawCommandServerDefinition {
public ZLSServerDefinition(String[] command) {
super("zig", command);
@ -31,5 +34,17 @@ public class ZLSServerDefinition extends RawCommandServerDefinition {
if (textCaps.getPublishDiagnostics() == null) {
textCaps.setPublishDiagnostics(new PublishDiagnosticsCapabilities());
}
val completion = textCaps.getCompletion();
if (completion != null) {
val completionItem = completion.getCompletionItem();
if (completionItem != null) {
completionItem.setDocumentationFormat(List.of("markdown"));
}
}
val hover = textCaps.getHover();
if (hover != null) {
hover.setContentFormat(List.of("markdown"));
}
}
}

View file

@ -141,10 +141,10 @@
<!-- region LSP -->
<!-- required for lsp file sync -->
<listener class="com.falsepattern.zigbrains.lsp.listeners.VFSListener"
topic="com.intellij.openapi.vfs.VirtualFileListener"/>
<listener class="com.falsepattern.zigbrains.lsp.listeners.LSPProjectManagerListener"
topic="com.intellij.openapi.project.ProjectManagerListener"/>
<listener class="com.falsepattern.zigbrains.lsp.listeners.VFSListener"
topic="com.intellij.openapi.vfs.newvfs.BulkFileListener"/>
<!-- endregion LSP -->
</applicationListeners>