From cb3a1527363ee84586a5444d2449f7fb0bfd0b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Thu, 3 Oct 2024 15:44:06 +0200 Subject: [PATCH] LSP client stops working on file after renaming it The LSP protocol requires that when a file is renamed the client signals this to the server by sending a didClose for the old location and after that a didOpen with the new location. The change assumes, that the IDE always goes through the DataObject to issue the rename/move. Changes through the raw FileObject are not tracked. Closes: #7806 --- .../modules/lsp/client/LSPBindings.java | 5 +- .../netbeans/modules/lsp/client/Utils.java | 41 ++++- .../lsp/client/bindings/FoldManagerImpl.java | 2 +- .../client/bindings/LanguageClientImpl.java | 2 +- .../bindings/LspStructureNavigatorPanel.java | 16 +- ...xtDocumentSyncServerCapabilityHandler.java | 150 +++++++++++++++--- .../bindings/refactoring/Refactoring.java | 10 +- .../modules/lsp/client/UtilsTest.java | 6 + ...cumentSyncServerCapabilityHandlerTest.java | 67 +++++++- 9 files changed, 247 insertions(+), 52 deletions(-) diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java index b4d2acaac50a..baea94d5b9cd 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java @@ -45,6 +45,7 @@ import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -143,7 +144,7 @@ public class LSPBindings { } private static final Map> backgroundTasks = new WeakHashMap<>(); - private final Set openedFiles = new HashSet<>(); + private final Map openedFiles = new ConcurrentHashMap<>(); public static synchronized LSPBindings getBindings(FileObject file) { for (Entry> e : workspace2Extension2Server.entrySet()) { @@ -542,7 +543,7 @@ private static Map backgroundTasksMapFor(FileObject file) return backgroundTasks.computeIfAbsent(file, f -> new IdentityHashMap<>()); } - public Set getOpenedFiles() { + public Map getOpenedFiles() { return openedFiles; } diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java index f1de03741003..0dce389dc6ec 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java @@ -57,6 +57,7 @@ import org.openide.filesystems.URLMapper; import org.openide.loaders.DataFolder; import org.openide.loaders.DataObject; +import org.openide.loaders.DataObjectNotFoundException; import org.openide.text.Line; import org.openide.text.NbDocument; import org.openide.util.Exceptions; @@ -71,6 +72,23 @@ public static String toURI(FileObject file) { return file.toURI().toString().replace("file:/", "file:///"); } + public static String uriReplaceFilename(String inputUriString, String filename) { + try { + URI inputUri = URI.create(inputUriString); + URI newUri = inputUri.resolve(new URI(null, null, filename, null)); + if ("file".equals(newUri.getScheme())) { + // By default the java URI routines drop empty hostnames from + // file URIs. Normalize this to the empty hostname. See also + // #toURI + return new URI("file", "", newUri.getPath(), null).toString(); + } else { + return newUri.toString(); + } + } catch (URISyntaxException ex) { + throw new IllegalArgumentException(ex); + } + } + public static Position createPosition(Document doc, int offset) throws BadLocationException { return new Position(LineDocumentUtils.getLineIndex((LineDocument) doc, offset), offset - LineDocumentUtils.getLineStart((LineDocument) doc, offset)); @@ -137,7 +155,7 @@ public static void applyWorkspaceEdit(WorkspaceEdit edit) { private static void applyEdits(String uri, List edits) { try { FileObject file = URLMapper.findFileObject(new URI(uri).toURL()); - EditorCookie ec = file.getLookup().lookup(EditorCookie.class); + EditorCookie ec = lookupForFile(file, EditorCookie.class); Document doc = ec != null ? ec.openDocument() : null; if (doc == null) { return ; @@ -274,7 +292,7 @@ public static void open(String targetUri, Range targetRange) { FileObject targetFile = fromURI(targetUri); if (targetFile != null) { - LineCookie lc = targetFile.getLookup().lookup(LineCookie.class); + LineCookie lc = lookupForFile(targetFile, LineCookie.class); //TODO: expecting lc != null! @@ -288,6 +306,25 @@ public static void open(String targetUri, Range targetRange) { } } + public static T lookupForFile(FileObject targetFile, Class clazz) { + if(targetFile == null) { + return null; + } + T lc = null; + try { + if (lc == null) { + DataObject dataObject = DataObject.find(targetFile); + lc = dataObject.getLookup().lookup(clazz); + } + } catch (DataObjectNotFoundException ex) { + // Ignore + } + if (lc == null) { + lc = targetFile.getLookup().lookup(clazz); + } + return lc; + } + private static final Comparator rangeReverseSort = (s1, s2) -> { int l1 = s1.getRange().getEnd().getLine(); int l2 = s2.getRange().getEnd().getLine(); diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/FoldManagerImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/FoldManagerImpl.java index 427070c63f65..72cf40d05f50 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/FoldManagerImpl.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/FoldManagerImpl.java @@ -110,7 +110,7 @@ public void release() { @Override public void run(LSPBindings bindings, FileObject file) { - EditorCookie ec = file.getLookup().lookup(EditorCookie.class); + EditorCookie ec = Utils.lookupForFile(file, EditorCookie.class); Document doc = ec != null ? ec.getDocument() : null; if (doc == null) { return; diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java index 832626724ad8..a33ee128e6c0 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java @@ -159,7 +159,7 @@ public void notifyProgress(ProgressParams params) { public void publishDiagnostics(PublishDiagnosticsParams pdp) { try { FileObject file = URLMapper.findFileObject(new URI(pdp.getUri()).toURL()); - EditorCookie ec = file != null ? file.getLookup().lookup(EditorCookie.class) : null; + EditorCookie ec = Utils.lookupForFile(file, EditorCookie.class); Document doc = ec != null ? ec.getDocument() : null; if (doc == null) { return ; //ignore... diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspStructureNavigatorPanel.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspStructureNavigatorPanel.java index 35fdbce6a53e..e8e2c53d41b5 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspStructureNavigatorPanel.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspStructureNavigatorPanel.java @@ -18,32 +18,24 @@ */ package org.netbeans.modules.lsp.client.bindings; -import java.awt.BorderLayout; -import java.awt.EventQueue; import java.util.Collection; import java.util.logging.Level; -import java.util.logging.Logger; import javax.swing.Action; -import javax.swing.JComponent; -import javax.swing.JPanel; import javax.swing.text.StyledDocument; import org.netbeans.api.actions.Openable; import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.api.lsp.StructureElement; +import org.netbeans.modules.lsp.client.Utils; import org.netbeans.spi.lsp.StructureProvider; -import org.netbeans.spi.navigator.NavigatorPanel; import org.openide.awt.Actions; import org.openide.cookies.EditorCookie; import org.openide.cookies.LineCookie; -import org.openide.explorer.ExplorerManager; -import org.openide.explorer.view.BeanTreeView; import org.openide.filesystems.FileObject; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.text.Line; import org.openide.text.NbDocument; -import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; import org.openide.util.lookup.AbstractLookup; @@ -75,7 +67,7 @@ void removeBackgroundTask(FileObject fo) { void refreshStructure(FileObject fo) { LOG.log(Level.INFO, "panelActivated: {0}", fo); - EditorCookie ec = fo.getLookup().lookup(EditorCookie.class); + EditorCookie ec = Utils.lookupForFile(fo, EditorCookie.class); if (ec != null) { StyledDocument doc = ec.getDocument(); if (doc != null) { @@ -130,12 +122,12 @@ private StructureElementNode(StructureElement e, FileObject fo, InstanceContent setShortDescription(e.getDetail()); setIconBaseWithExtension(Icons.getSymbolIconBase(e.getKind())); Openable open = () -> { - EditorCookie ec = fo.getLookup().lookup(EditorCookie.class); + EditorCookie ec = Utils.lookupForFile(fo, EditorCookie.class); if (ec != null) { StyledDocument doc = ec.getDocument(); if (doc != null) { int lineNumber = NbDocument.findLineNumber(doc, e.getSelectionStartOffset()); - LineCookie lines = fo.getLookup().lookup(LineCookie.class); + LineCookie lines = Utils.lookupForFile(fo, LineCookie.class); if (lines != null) { Line at = lines.getLineSet().getOriginal(lineNumber); if (at != null) { diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java index 2659afddcf67..aa030db67948 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java @@ -18,14 +18,18 @@ */ package org.netbeans.modules.lsp.client.bindings; +import java.lang.ref.WeakReference; +import org.netbeans.modules.lsp.client.Utils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; @@ -51,9 +55,12 @@ import org.netbeans.lib.editor.util.swing.DocumentUtilities; import org.netbeans.modules.editor.*; import org.netbeans.modules.lsp.client.LSPBindings; -import org.netbeans.modules.lsp.client.Utils; +import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataLoaderPool; +import org.openide.loaders.OperationAdapter; +import org.openide.loaders.OperationEvent; import org.openide.modules.OnStart; import org.openide.text.NbDocument; import org.openide.util.Exceptions; @@ -65,6 +72,55 @@ */ public class TextDocumentSyncServerCapabilityHandler { + static { + DataLoaderPool.getDefault().addOperationListener(new OperationAdapter() { + @Override + public void operationRename(OperationEvent.Rename ev) { + FileObject file = ev.getObject().getPrimaryFile(); + + LSPBindings server = LSPBindings.getBindings(file); + + if (server == null) { + return; //ignore + } + + EditorCookie ec = Utils.lookupForFile(file, EditorCookie.class); + + String newUri = Utils.toURI(file); + String oldUri = Utils.uriReplaceFilename(newUri, ev.getOriginalName()); + reopenFile(server, ec.getDocument(), oldUri, file); + LSPBindings.scheduleBackgroundTasks(file); + } + + @Override + public void operationMove(OperationEvent.Move ev) { + FileObject originalFile = ev.getOriginalPrimaryFile(); + FileObject newFile = ev.getObject().getPrimaryFile(); + + LSPBindings server = LSPBindings.getBindings(newFile); + + if (server == null) { + return; //ignore + } + + EditorCookie ec = Utils.lookupForFile(newFile, EditorCookie.class); + Document doc = ec.getDocument(); + + server.getOpenedFiles().remove(originalFile); + server.getOpenedFiles().put(newFile, Boolean.TRUE); + reopenFile(server, doc, Utils.toURI(originalFile), newFile); + HandlerRegistry hr = (HandlerRegistry) doc.getProperty(HandlerRegistry.class); + if (hr != null) { + hr.forEach(bt -> { + LSPBindings.removeBackgroundTask(originalFile, bt); + LSPBindings.addBackgroundTask(newFile, bt); + }); + } + LSPBindings.scheduleBackgroundTasks(newFile); + } + }); + } + private final RequestProcessor WORKER = new RequestProcessor(TextDocumentSyncServerCapabilityHandler.class.getName(), 1, false, false); private final Set lastOpened = Collections.newSetFromMap(new IdentityHashMap<>()); @@ -125,10 +181,6 @@ public void run() { private final Map openDocument2PanesCount = new HashMap<>(); private void documentOpened(Document doc) { - FileObject file = NbEditorUtilities.getFileObject(doc); - - if (file == null) - return; //ignore openDocument2PanesCount.computeIfAbsent(doc, d -> { doc.putProperty(TextDocumentSyncServerCapabilityHandler.class, true); @@ -149,6 +201,11 @@ public void removeUpdate(DocumentEvent e) { } private void fireEvent(int start, String newText, String oldText) { try { + FileObject file = NbEditorUtilities.getFileObject(doc); + + if (file == null) + return; //ignore + Position startPos = Utils.createPosition(doc, start); Position endPos = Utils.computeEndPositionForRemovedText(startPos, oldText); TextDocumentContentChangeEvent[] event = new TextDocumentContentChangeEvent[1]; @@ -283,35 +340,50 @@ private void ensureDidOpenSent(Document doc) { if (server == null) return ; //ignore - if (!server.getOpenedFiles().add(file)) { + if (server.getOpenedFiles().put(file, Boolean.TRUE) != null) { //already opened: - return ; + return; } doc.putProperty(HyperlinkProviderImpl.class, true); - String uri = Utils.toURI(file); - String[] text = new String[1]; - - doc.render(() -> { - try { - text[0] = doc.getText(0, doc.getLength()); - } catch (BadLocationException ex) { - Exceptions.printStackTrace(ex); - text[0] = ""; - } - }); - - TextDocumentItem textDocumentItem = new TextDocumentItem(uri, - FileUtil.getMIMEType(file), - 0, - text[0]); + TextDocumentItem textDocumentItem = toTextDocumentItem(doc, file); server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(textDocumentItem)); LSPBindings.scheduleBackgroundTasks(file); }); } + private static TextDocumentItem toTextDocumentItem(Document doc, FileObject file) { + String[] text = new String[1]; + String uri = Utils.toURI(file); + + doc.render(() -> { + try { + text[0] = doc.getText(0, doc.getLength()); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + text[0] = ""; + } + }); + TextDocumentItem textDocumentItem = new TextDocumentItem(uri, + FileUtil.getMIMEType(file), + 0, + text[0]); + return textDocumentItem; + } + + private static void reopenFile(LSPBindings server, Document doc, String oldUri, FileObject newFile) { + DidCloseTextDocumentParams closeParams = new DidCloseTextDocumentParams(); + closeParams.setTextDocument(new TextDocumentIdentifier(oldUri)); + DidOpenTextDocumentParams openParams = new DidOpenTextDocumentParams(); + openParams.setTextDocument(toTextDocumentItem(doc, newFile)); + server.getTextDocumentService().didClose(closeParams); + server.getOpenedFiles().remove(newFile); + server.getTextDocumentService().didOpen(openParams); + server.getOpenedFiles().put(newFile, Boolean.TRUE); + } + private void registerBackgroundTasks(JTextComponent c) { Document doc = c.getDocument(); WORKER.post(() -> { @@ -325,23 +397,55 @@ private void registerBackgroundTasks(JTextComponent c) { if (server == null) return ; //ignore + synchronized(HandlerRegistry.class) { + if(doc.getProperty(HandlerRegistry.class) == null) { + doc.putProperty(HandlerRegistry.class, new HandlerRegistry()); + } + } + HandlerRegistry hr = (HandlerRegistry) doc.getProperty(HandlerRegistry.class); + SwingUtilities.invokeLater(() -> { if (c.getClientProperty(MarkOccurrences.class) == null) { MarkOccurrences mo = new MarkOccurrences(c); LSPBindings.addBackgroundTask(file, mo); c.putClientProperty(MarkOccurrences.class, mo); + hr.add(mo); } if (c.getClientProperty(BreadcrumbsImpl.class) == null) { BreadcrumbsImpl bi = new BreadcrumbsImpl(c); LSPBindings.addBackgroundTask(file, bi); c.putClientProperty(BreadcrumbsImpl.class, bi); + hr.add(bi); } if (c.getClientProperty(SemanticHighlight.class) == null) { SemanticHighlight sh = new SemanticHighlight(c); LSPBindings.addBackgroundTask(file, sh); c.putClientProperty(SemanticHighlight.class, sh); + hr.add(sh); } }); }); } + + private static class HandlerRegistry { + private final List> handlers = new ArrayList<>(); + + public synchronized void add(LSPBindings.BackgroundTask backgroundTask) { + handlers.add(new WeakReference<>(backgroundTask)); + } + + public synchronized void forEach(Consumer consumer) { + Iterator> it = handlers.iterator(); + + while(it.hasNext()) { + WeakReference handlerRef = it.next(); + LSPBindings.BackgroundTask backgroundTask = handlerRef.get(); + if(backgroundTask != null) { + consumer.accept(backgroundTask); + } else { + it.remove(); + } + } + } + } } diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java index 7ebc214ab1d8..2cabb8aa1bdc 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java @@ -140,8 +140,8 @@ public Problem prepare(RefactoringElementsBag refactoringElements) { if (file != null) { PositionBounds bounds; try { - CloneableEditorSupport es = file.getLookup().lookup(CloneableEditorSupport.class); - EditorCookie ec = file.getLookup().lookup(EditorCookie.class); + CloneableEditorSupport es = Utils.lookupForFile(file, CloneableEditorSupport.class); + EditorCookie ec = Utils.lookupForFile(file, EditorCookie.class); StyledDocument doc = ec.openDocument(); bounds = new PositionBounds(es.createPositionRef(Utils.getOffset(doc, l.getRange().getStart()), Position.Bias.Forward), @@ -150,7 +150,7 @@ public Problem prepare(RefactoringElementsBag refactoringElements) { Exceptions.printStackTrace(ex); bounds = null; } - LineCookie lc = file.getLookup().lookup(LineCookie.class); + LineCookie lc = Utils.lookupForFile(file, LineCookie.class); Line startLine = lc.getLineSet().getCurrent(l.getRange().getStart().getLine()); String lineText = startLine.getText(); int highlightEnd = Math.min(lineText.length(), l.getRange().getEnd().getCharacter()); @@ -341,9 +341,9 @@ private Problem chain(Problem current, Problem existing) { private Difference textEdit2Difference(FileObject file, TextEdit edit) { if (file != null) { try { - EditorCookie ec = file.getLookup().lookup(EditorCookie.class); + EditorCookie ec = Utils.lookupForFile(file, EditorCookie.class); StyledDocument doc = ec.openDocument(); - CloneableEditorSupport es = file.getLookup().lookup(CloneableEditorSupport.class); + CloneableEditorSupport es = Utils.lookupForFile(file, CloneableEditorSupport.class); PositionRef start = es.createPositionRef(Utils.getOffset(doc, edit.getRange().getStart()), Position.Bias.Forward); PositionRef end = es.createPositionRef(Utils.getOffset(doc, edit.getRange().getEnd()), Position.Bias.Forward); diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/UtilsTest.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/UtilsTest.java index 1e276002c529..4ce2c9a33a52 100644 --- a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/UtilsTest.java +++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/UtilsTest.java @@ -203,6 +203,12 @@ public void testRemovedTestPosition() throws Exception { assertEquals(new Position(4, 1), Utils.computeEndPositionForRemovedText(new Position(2, 2), "aaaaa\naaaaaaaa\na")); } + public void testUri() throws Exception { + String inputUri = "file:/home/demo/test%20with%20space.txt"; + String resultUri = Utils.uriReplaceFilename(inputUri, "replacement filename with spaces.txt"); + assertEquals("file:///home/demo/replacement%20filename%20with%20spaces.txt", resultUri); + } + private void assertContent(String expectedContent, FileObject sourceFile) throws Exception { EditorCookie ec = sourceFile.getLookup().lookup(EditorCookie.class); StyledDocument doc = ec.openDocument(); diff --git a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java index 52cd00538263..103ef2ce949c 100644 --- a/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java +++ b/ide/lsp.client/test/unit/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandlerTest.java @@ -18,6 +18,7 @@ */ package org.netbeans.modules.lsp.client.bindings; +import org.netbeans.modules.lsp.client.Utils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -47,19 +48,23 @@ import org.junit.Test; import org.netbeans.junit.MockServices; import org.netbeans.modules.editor.lib2.EditorApiPackageAccessor; -import org.netbeans.modules.lsp.client.Utils; import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataObject; import org.openide.text.CloneableEditorSupport; import org.openide.text.NbDocument; + import static org.junit.Assert.*; + import org.netbeans.modules.lsp.client.TestUtils.BaseWorkspaceServiceImpl; + import static org.netbeans.modules.lsp.client.TestUtils.MIME_TYPE; + import org.netbeans.modules.lsp.client.TestUtils.MimeDataProviderImpl; import org.netbeans.modules.lsp.client.TestUtils.MockLSP; import org.netbeans.modules.lsp.client.TestUtils.MockMimeResolver; +import org.openide.loaders.DataFolder; /** * @@ -92,7 +97,7 @@ public boolean isFocusOwner() { String uri = Utils.toURI(file); - SwingUtilities.invokeLater(() -> { + SwingUtilities.invokeAndWait(() -> { EditorApiPackageAccessor.get().register(pane); }); @@ -125,28 +130,78 @@ public boolean isFocusOwner() { // // assertEvents("didSave: " + uri); - SwingUtilities.invokeLater(() -> { + SwingUtilities.invokeAndWait(() -> { EditorApiPackageAccessor.get().forceRelease(pane); }); assertEvents("didClose: " + uri); - SwingUtilities.invokeLater(() -> { + SwingUtilities.invokeAndWait(() -> { EditorApiPackageAccessor.get().register(pane); }); assertEvents("didOpen: " + uri + "/" + MIME_TYPE + "/0/tet"); - SwingUtilities.invokeLater(() -> { + SwingUtilities.invokeAndWait(() -> { EditorApiPackageAccessor.get().forceRelease(pane); }); assertEvents("didClose: " + uri); } + @Test + public void testRename() throws Exception { + MockLSP.createServer = () -> new TestLanguageServer(); + MockServices.setServices(MimeDataProviderImpl.class, MockMimeResolver.class); + + new TextDocumentSyncServerCapabilityHandler.Init().run(); + + FileObject folder = FileUtil.createMemoryFileSystem().getRoot().createFolder("myfolder"); + FileObject folder2 = FileUtil.createMemoryFileSystem().getRoot().createFolder("myfolder2"); + FileObject file = folder.createData("data.mock-txt"); + DataObject dataObject = DataObject.find(file); + EditorCookie ec = file.getLookup().lookup(EditorCookie.class); + ((CloneableEditorSupport) ec).setMIMEType(MIME_TYPE); + Document doc = ec.openDocument(); + JEditorPane pane = new JEditorPane() { + @Override + public boolean isFocusOwner() { + return true; + } + }; + + pane.setDocument(doc); + + String uri = Utils.toURI(file); + + SwingUtilities.invokeAndWait(() -> { + EditorApiPackageAccessor.get().register(pane); + }); + + assertEvents("didOpen: " + uri + "/" + MIME_TYPE + "/0/"); + + dataObject.rename("data-renamed.mock-txt"); + + String uri2 = Utils.toURI(file); + + assertEvents("didClose: " + uri, "didOpen: " + uri2 + "/" + MIME_TYPE + "/0/"); + + dataObject.move(DataFolder.findFolder(folder2)); + + String uri3 = Utils.toURI(dataObject.getPrimaryFile()); + + assertEvents("didClose: " + uri2, "didOpen: " + uri3 + "/" + MIME_TYPE + "/0/"); + + SwingUtilities.invokeAndWait(() -> { + EditorApiPackageAccessor.get().forceRelease(pane); + }); + + assertEvents("didClose: " + uri3); + } + private void assertEvents(String... events) { synchronized (eventLog) { - long timeout = System.currentTimeMillis() + 10000000; + long timeout = System.currentTimeMillis() + 10000; while (System.currentTimeMillis() < timeout && eventLog.size() < events.length) { try {