Skip to content

Commit

Permalink
Narrow text edits
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Feb 2, 2024
1 parent 2e67c8e commit 0f16d4f
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 9 deletions.
47 changes: 39 additions & 8 deletions ruff_lsp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from pathlib import Path
from typing import Any, List, NamedTuple, Sequence, Union, cast

from lsprotocol import validators
from lsprotocol.types import (
CODE_ACTION_RESOLVE,
INITIALIZE,
Expand Down Expand Up @@ -139,12 +138,12 @@ class VersionModified(NamedTuple):
TOOL_DISPLAY = "Ruff"

# Require at least Ruff v0.0.291 for formatting, but allow older versions for linting.
VERSION_REQUIREMENT_FORMATTER = SpecifierSet(">=0.0.291,<0.2.0")
VERSION_REQUIREMENT_LINTER = SpecifierSet(">=0.0.189,<0.2.0")
VERSION_REQUIREMENT_FORMATTER = SpecifierSet(">=0.0.291")
VERSION_REQUIREMENT_LINTER = SpecifierSet(">=0.0.189")
# Version requirement for use of the `--output-format` option
VERSION_REQUIREMENT_OUTPUT_FORMAT = SpecifierSet(">=0.0.291,<0.2.0")
VERSION_REQUIREMENT_OUTPUT_FORMAT = SpecifierSet(">=0.0.291")
# Version requirement after which Ruff avoids writing empty output for excluded files.
VERSION_REQUIREMENT_EMPTY_OUTPUT = SpecifierSet(">=0.1.6,<0.2.0")
VERSION_REQUIREMENT_EMPTY_OUTPUT = SpecifierSet(">=0.1.6")

# Arguments provided to every Ruff invocation.
CHECK_ARGS = [
Expand Down Expand Up @@ -1379,13 +1378,45 @@ def _fixed_source_to_edits(
if new_source == original_source:
return []

# Reduce the text edit by omitting the common suffix and postfix (lines)
# from the text edit. I chose this basic diffing because "proper" diffing has
# the downside that it can be very slow in some cases. Black uses a diffing approach
# that takes time into consideration, but it requires spawning a thread to stop
# the diffing after a given time, which feels very heavy weight.
# This basic "diffing" has a guaranteed `O(n)` runtime and is sufficient to
# prevent transmitting the entire source document when formatting a range
# or formatting a document where most of the code remains unchanged.
#
# https://github.com/microsoft/vscode-black-formatter/blob/main/bundled/tool/lsp_edit_utils.py
new_lines = new_source.splitlines(True)
original_lines = original_source.splitlines(True)

start_offset = 0
end_offset = 0

for new_line, original_line in zip(new_lines, original_lines):
if new_line == original_line:
start_offset += 1
else:
break

for new_line, original_line in zip(
reversed(new_lines[start_offset:]), reversed(original_lines[start_offset:])
):
if new_line == original_line:
end_offset += 1
else:
break

trimmed_new_source = "".join(new_lines[start_offset : len(new_lines) - end_offset])

return [
TextEdit(
range=Range(
start=Position(line=0, character=0),
end=Position(line=validators.UINTEGER_MAX_VALUE, character=0),
start=Position(line=start_offset, character=0),
end=Position(line=len(original_lines) - end_offset, character=0),
),
new_text=new_source,
new_text=trimmed_new_source,
)
]

Expand Down
6 changes: 5 additions & 1 deletion tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from contextlib import nullcontext

import pytest
from lsprotocol.types import Position, Range
from packaging.version import Version
from pygls.workspace import Workspace

Expand Down Expand Up @@ -46,7 +47,10 @@ async def test_format(tmp_path, ruff_version: Version):
[edit] = _fixed_source_to_edits(
original_source=document.source, fixed_source=result.stdout.decode("utf-8")
)
assert edit.new_text == expected
assert edit.new_text == ""
assert edit.range == Range(
start=Position(line=0, character=0), end=Position(line=1, character=0)
)


@pytest.mark.asyncio
Expand Down

0 comments on commit 0f16d4f

Please sign in to comment.