Skip to content

Commit 58ef2d9

Browse files
authored
Merge pull request #562 from koxudaxi/feat-partial-formatting
fix: Add support for partial formatting using --range option
2 parents 07a9aae + 72a91b1 commit 58ef2d9

File tree

3 files changed

+59
-20
lines changed

3 files changed

+59
-20
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
## [Unreleased]
4+
- fix: Add support for partial formatting using --range option [[#562](https://github.com/koxudaxi/ruff-pycharm-plugin/pull/562)]
45
- fix: fix format and fix action [[#559](https://github.com/koxudaxi/ruff-pycharm-plugin/pull/559)]
56
- fix: Improve error handling [[#554](https://github.com/koxudaxi/ruff-pycharm-plugin/pull/554)]
67
- feat: add features to lsp [[#550](https://github.com/koxudaxi/ruff-pycharm-plugin/pull/550)]

src/com/koxudaxi/ruff/Ruff.kt

+33
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ val NO_FIX_FORMAT_ARGS = ARGS_BASE + listOf("--no-fix", "--format", "json")
9090
val NO_FIX_OUTPUT_FORMAT_ARGS = ARGS_BASE + listOf("--no-fix", "--output-format", "json")
9191
val FORMAT_ARGS = listOf("format", "--force-exclude", "--quiet")
9292
val FORMAT_CHECK_ARGS = FORMAT_ARGS + listOf("--check")
93+
val FORMAT_RANGE_ARGS = listOf("--range")
9394
val LSP_ARGS_BASE = listOf("server")
9495
val PREVIEW_ARGS = listOf("--preview")
9596
val Project.LSP_ARGS: List<String>
@@ -714,3 +715,35 @@ private val RUFF_CONFIG: List<String> = listOf(PY_PROJECT_TOML, RUFF_TOML)
714715
val VirtualFile.isRuffConfig: Boolean
715716
get() = name in RUFF_CONFIG || name.endsWith(RUFF_TOML_SUFFIX)
716717
val Project.configService: RuffConfigService get() = RuffConfigService.getInstance(this)
718+
719+
720+
/**
721+
* Returns the 1-indexed line and column numbers for the given offset in the string.
722+
*
723+
* @param offset the character offset within the string.
724+
* @return a Pair where the first component is the line number and the second is the column number.
725+
*/
726+
fun String.getLineAndColumn(offset: Int): Pair<Int, Int> {
727+
// Count the number of newline characters before the offset to determine the line number.
728+
val line = this.substring(0, offset).count { it == '\n' } + 1
729+
// Determine the column number by finding the position of the last newline.
730+
val lastNewline = this.lastIndexOf('\n', offset - 1)
731+
val column = if (lastNewline == -1) offset + 1 else offset - lastNewline
732+
return line to column
733+
}
734+
735+
/**
736+
* Converts the given TextRange into a formatted string "<start_line>:<start_column>-<end_line>:<end_column>"
737+
* using the provided full text to calculate the line and column numbers.
738+
*
739+
* @param text the full text content from which the TextRange was obtained.
740+
* @return a formatted string representing the range.
741+
*/
742+
fun TextRange.formatRange(text: String): String {
743+
val (startLine, startColumn) = text.getLineAndColumn(startOffset)
744+
val (endLine, endColumn) = text.getLineAndColumn(endOffset)
745+
return "$startLine:$startColumn-$endLine:$endColumn"
746+
}
747+
748+
749+
fun TextRange.formatRangeArgs(text: String): List<String> = FORMAT_RANGE_ARGS + listOf(formatRange(text))

src/com/koxudaxi/ruff/RuffAsyncFormatter.kt

+25-20
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import java.io.FileNotFoundException
1010

1111

1212
class RuffAsyncFormatter : AsyncDocumentFormattingService() {
13-
private val FEATURES: Set<FormattingService.Feature> = setOf(FormattingService.Feature.AD_HOC_FORMATTING)
13+
private val FEATURES: Set<FormattingService.Feature> = setOf(FormattingService.Feature.FORMAT_FRAGMENTS)
1414

1515
override fun getFeatures(): Set<FormattingService.Feature> {
1616
return FEATURES
@@ -51,13 +51,29 @@ class RuffAsyncFormatter : AsyncDocumentFormattingService() {
5151
return@runCatching
5252
}
5353
val sourceFile = formattingContext.containingFile.sourceFile
54-
val fixCommandArgs =
55-
generateCommandArgs(sourceFile, formattingContext.project.FIX_ARGS, false) ?: return@runCatching
5654
val currentText = ioFile.readText()
57-
if (cancelled) {
58-
noUpdate()
55+
56+
// Check if a formatting range is specified.
57+
val formatRange = request.formattingRanges.firstOrNull()?.let { selectedRange ->
58+
if (selectedRange.endOffset >= currentText.length) null
59+
else selectedRange
60+
}
61+
62+
if (formatRange != null) {
63+
// When a range is specified, only run the format command.
64+
val formatCommandArgs = generateCommandArgs(sourceFile, FORMAT_ARGS + formatRange.formatRangeArgs(currentText), false)
65+
?: return@runCatching
66+
val formatCommandStdout = runRuff(formatCommandArgs, currentText.toByteArray())
67+
if (formatCommandStdout == null) {
68+
request.onTextReady(null)
69+
return@runCatching
70+
}
71+
updateText(currentText, formatCommandStdout)
5972
return@runCatching
6073
}
74+
75+
val fixCommandArgs = generateCommandArgs(sourceFile, formattingContext.project.FIX_ARGS, false)
76+
?: return@runCatching
6177
val fixCommandStdout = runRuff(fixCommandArgs, currentText.toByteArray())
6278
if (fixCommandStdout == null) {
6379
request.onTextReady(null)
@@ -68,10 +84,7 @@ class RuffAsyncFormatter : AsyncDocumentFormattingService() {
6884
return@runCatching
6985
}
7086
val formatCommandArgs = generateCommandArgs(sourceFile, FORMAT_ARGS, false)
71-
if (formatCommandArgs == null) {
72-
updateText(currentText, fixCommandStdout)
73-
return@runCatching
74-
}
87+
?: return@runCatching
7588
if (cancelled) {
7689
noUpdate()
7790
return@runCatching
@@ -85,22 +98,14 @@ class RuffAsyncFormatter : AsyncDocumentFormattingService() {
8598

8699
}.onFailure { exception ->
87100
when (exception) {
88-
is ProcessCanceledException -> { /* ignore */
89-
}
90-
91-
is FileNotFoundException -> {
92-
noUpdate()
93-
}
94-
95-
else -> {
96-
request.onError("Ruff Error", exception.localizedMessage)
97-
}
101+
is ProcessCanceledException -> { /* ignore */ }
102+
is FileNotFoundException -> noUpdate()
103+
else -> request.onError("Ruff Error", exception.localizedMessage)
98104
}
99105
}
100106
}
101107

102108
override fun cancel(): Boolean {
103-
// Signal cancellation.
104109
cancelled = true
105110
return true
106111
}

0 commit comments

Comments
 (0)