Skip to content

Commit

Permalink
fix: Made code action edit apply logic order-independent
Browse files Browse the repository at this point in the history
- Added test case for Zig Language Server where the original algorithm failed
  • Loading branch information
FalsePattern authored and angelozerr committed Feb 9, 2025
1 parent 564e85c commit c88ec96
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 18 deletions.
44 changes: 26 additions & 18 deletions src/main/java/com/redhat/devtools/lsp4ij/LSPIJUtils.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/*******************************************************************************
* Copyright (c) 2019 Red Hat, Inc.
* Copyright (c) 2019-2025 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at https://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
* FalsePattern - Order-independent doApplyEdits
******************************************************************************/
package com.redhat.devtools.lsp4ij;

Expand All @@ -18,6 +19,7 @@
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
Expand All @@ -30,6 +32,7 @@
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.PsiElement;
Expand Down Expand Up @@ -1107,31 +1110,36 @@ private static void doApplyEdits(@Nullable Editor editor,
if (edits.isEmpty()) {
return;
}
// Sort text edits
if (edits.size() > 1) {
edits.sort(TEXT_EDITS_DESCENDING_COMPARATOR);
}
final int oldCaretOffset = editor != null ? editor.getCaretModel().getOffset() : -1;
int newCaretOffset = oldCaretOffset;
// Apply each text edit to update the given document and move the caret offset if needed
for (var textEdit : edits) {
Range range = textEdit.getRange();
// Convert TextEdit positions into RangeMarkers
final var pairs = new ArrayList<Pair<TextEdit, RangeMarker>>();
for (var textEdit: edits) {
var range = textEdit.getRange();
if (range != null) {
// Text edit range exists
int start = toOffset(range.getStart(), document);
int end = toOffset(range.getEnd(), document);
// Range is valid, add it to the converted list
if (end >= start) {
// Text edit range is valid, apply the text edit to the document
int increment = applyEdit(start, end, textEdit.getNewText(), document, oldCaretOffset);
if (newCaretOffset != -1) {
// Update the caret offset
newCaretOffset += increment;
}
var marker = document.createRangeMarker(start, end);
pairs.add(Pair.create(textEdit, marker));
}
}
}
if (pairs.isEmpty()) {
return;
}
final int oldCaretOffset = editor != null ? editor.getCaretModel().getOffset() : -1;
int newCaretOffset = oldCaretOffset;
// Apply each text edit to update the given document
for (var pair: pairs) {
var edit = pair.first;
var marker = pair.second;
int increment = applyEdit(marker.getStartOffset(), marker.getEndOffset(), edit.getNewText(), document, oldCaretOffset);
if (newCaretOffset != -1) {
newCaretOffset += increment;
}
marker.dispose();
}
if (newCaretOffset > -1 && oldCaretOffset != newCaretOffset) {
// The caret offset must be moved
editor.getCaretModel().moveToOffset(newCaretOffset);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*******************************************************************************
* Copyright (c) 2025 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* FalsePattern - initial API and implementation
******************************************************************************/
package com.redhat.devtools.lsp4ij;

import com.intellij.testFramework.fixtures.BasePlatformTestCase;
import org.eclipse.lsp4j.WorkspaceEdit;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import static com.redhat.devtools.lsp4ij.LSP4IJAssert.assertApplyWorkspaceEdit;

/**
* Tests for {@link LSPIJUtils#applyWorkspaceEdit(WorkspaceEdit)} with Zig ZLS.
*/
public class LSPIJUtils_applyWorkspaceEdit_ZigTest extends BasePlatformTestCase {

public void testOptimizeImports() throws IOException {
Path filePath = Files.createTempFile(null, ".zig");
String content = """
const vk = @import("vulkan");
///standard library
const std = @import("std");
const glfw = @import("mach-glfw");
const Allocator = std.mem.Allocator;
const x = 123;
""";
// optimize @imports
String json = """
{
"changes": {
"%s": [
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 0
}
},
"newText": "///standard library\\nconst std \\u003d @import(\\"std\\");\\nconst Allocator \\u003d std.mem.Allocator;\\n\\nconst glfw \\u003d @import(\\"mach-glfw\\");\\nconst vk \\u003d @import(\\"vulkan\\");\\n\\n"
},
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 1,
"character": 0
}
},
"newText": ""
},
{
"range": {
"start": {
"line": 1,
"character": 0
},
"end": {
"line": 3,
"character": 0
}
},
"newText": ""
},
{
"range": {
"start": {
"line": 3,
"character": 0
},
"end": {
"line": 4,
"character": 0
}
},
"newText": ""
},
{
"range": {
"start": {
"line": 4,
"character": 0
},
"end": {
"line": 5,
"character": 0
}
},
"newText": ""
}
]
}
}
""".formatted(filePath.toUri().toASCIIString());
String expected = """
///standard library
const std = @import("std");
const Allocator = std.mem.Allocator;
const glfw = @import("mach-glfw");
const vk = @import("vulkan");
const x = 123;
""";
assertApplyWorkspaceEdit(filePath, content, json, expected,getProject());
}

}

0 comments on commit c88ec96

Please sign in to comment.