diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala index 048790c4b6a..6db3c746630 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala @@ -213,7 +213,7 @@ class MetalsLanguageServer( private val onTypeFormattingProvider = new OnTypeFormattingProvider(buffers, trees, () => userConfig) private val rangeFormattingProvider = - new RangeFormattingProvider(buffers, trees) + new RangeFormattingProvider(buffers, trees, () => userConfig) private val classFinder = new ClassFinder(trees) private val foldingRangeProvider = new FoldingRangeProvider(trees, buffers) // These can't be instantiated until we know the workspace root directory. diff --git a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala index 6efe7453b39..d1272cb7e4c 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala @@ -40,6 +40,7 @@ case class UserConfiguration( showImplicitConversionsAndClasses: Boolean = false, remoteLanguageServer: Option[String] = None, enableStripMarginOnTypeFormatting: Boolean = true, + enableIndentOnPaste: Boolean = false, excludedPackages: Option[List[String]] = None, fallbackScalaVersion: Option[String] = None ) { @@ -211,6 +212,15 @@ object UserConfiguration { |shown in the hover. |""".stripMargin ), + UserConfigurationOption( + "enable-indent-on-paste", + "false", + "false", + "Should try adjust indentation on range formatting.", + """|When this option is enabled, when user pastes any snippet into a Scala file, Metals + |will try to adjust the indentation to that of the current cursor. + |""".stripMargin + ), UserConfigurationOption( "remote-language-server", """empty string `""`.""", @@ -384,6 +394,8 @@ object UserConfiguration { getStringKey("remote-language-server") val enableStripMarginOnTypeFormatting = getBooleanKey("enable-strip-margin-on-type-formatting").getOrElse(true) + val enableIndentOnPaste = + getBooleanKey("enable-indent-on-paste").getOrElse(true) val excludedPackages = getStringListKey("excluded-packages") // `automatic` should be treated as None @@ -412,6 +424,7 @@ object UserConfiguration { showImplicitConversionsAndClasses, remoteLanguageServer, enableStripMarginOnTypeFormatting, + enableIndentOnPaste, excludedPackages, defaultScalaVersion ) diff --git a/metals/src/main/scala/scala/meta/internal/metals/formatting/IndentOnPaste.scala b/metals/src/main/scala/scala/meta/internal/metals/formatting/IndentOnPaste.scala index b45bf324f1f..6060b7e75cc 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/formatting/IndentOnPaste.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/formatting/IndentOnPaste.scala @@ -1,13 +1,15 @@ package scala.meta.internal.metals.formatting import scala.util.matching.Regex +import scala.meta.internal.metals.UserConfiguration import scala.meta.internal.mtags.MtagsEnrichments._ import org.eclipse.lsp4j.Position import org.eclipse.lsp4j.Range import org.eclipse.lsp4j.TextEdit -object IndentOnPaste extends RangeFormatter { +case class IndentOnPaste(userConfig: () => UserConfiguration) + extends RangeFormatter { private val indentRegex: Regex = raw"\S".r @@ -48,67 +50,71 @@ object IndentOnPaste extends RangeFormatter { override def contribute( rangeFormatterParams: RangeFormatterParams ): Option[List[TextEdit]] = { - val formattingOptions = rangeFormatterParams.formattingOptions - val startPos = rangeFormatterParams.startPos - val endPos = rangeFormatterParams.endPos - val splitLines = rangeFormatterParams.splitLines - - val rangeStart = startPos.toLSP.getStart - val originalStart = rangeStart.getCharacter() - rangeStart.setCharacter(0) - // we format full lines even if not everything was pasted - val realEndColumn = - if (endPos.endLine < splitLines.size) splitLines(endPos.endLine).size - else endPos.endColumn - val pastedRange = - new Range(rangeStart, new Position(endPos.endLine, realEndColumn)) - val startLine = startPos.toLSP.getStart.getLine - val endLine = endPos.toLSP.getEnd.getLine - - val opts = - if (formattingOptions.isInsertSpaces) - FmtOptions.spaces(formattingOptions.getTabSize) - else - FmtOptions.tabs - - val pastedLines = splitLines.slice(startLine, endLine + 1) - - // Do not adjust indentation if we pasted into an existing line content - def pastedIntoNonEmptyLine = { - pastedLines match { - case array if array.length > 0 => - val firstLine = array.head - val originalLine = firstLine.substring(0, originalStart) - originalLine.trim().nonEmpty - case _ => false + if (userConfig().enableIndentOnPaste) { + val formattingOptions = rangeFormatterParams.formattingOptions + val startPos = rangeFormatterParams.startPos + val endPos = rangeFormatterParams.endPos + val splitLines = rangeFormatterParams.splitLines + + val rangeStart = startPos.toLSP.getStart + val originalStart = rangeStart.getCharacter() + rangeStart.setCharacter(0) + // we format full lines even if not everything was pasted + val realEndColumn = + if (endPos.endLine < splitLines.size) splitLines(endPos.endLine).size + else endPos.endColumn + val pastedRange = + new Range(rangeStart, new Position(endPos.endLine, realEndColumn)) + val startLine = startPos.toLSP.getStart.getLine + val endLine = endPos.toLSP.getEnd.getLine + + val opts = + if (formattingOptions.isInsertSpaces) + FmtOptions.spaces(formattingOptions.getTabSize) + else + FmtOptions.tabs + + val pastedLines = splitLines.slice(startLine, endLine + 1) + + // Do not adjust indentation if we pasted into an existing line content + def pastedIntoNonEmptyLine = { + pastedLines match { + case array if array.length > 0 => + val firstLine = array.head + val originalLine = firstLine.substring(0, originalStart) + originalLine.trim().nonEmpty + case _ => false + } } - } - - if (pastedIntoNonEmptyLine) { - None - } else { - val currentFirstLineIndent = pastedLines.headOption - .flatMap(codeStartPosition) - .getOrElse(originalStart) - val currentIndentationLevel = - Math.min(originalStart, currentFirstLineIndent) - val formatted = - processLines( - currentIndentationLevel, - pastedLines, - opts, - originalStart - ) - if (formatted.nonEmpty) - Some( - new TextEdit( - pastedRange, - formatted.mkString(System.lineSeparator) - ) :: Nil - ) - else + if (pastedIntoNonEmptyLine) { None + } else { + val currentFirstLineIndent = pastedLines.headOption + .flatMap(codeStartPosition) + .getOrElse(originalStart) + val currentIndentationLevel = + Math.min(originalStart, currentFirstLineIndent) + val formatted = + processLines( + currentIndentationLevel, + pastedLines, + opts, + originalStart + ) + + if (formatted.nonEmpty) + Some( + new TextEdit( + pastedRange, + formatted.mkString(System.lineSeparator) + ) :: Nil + ) + else + None + } + } else { + None } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/formatting/RangeFormattingProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/formatting/RangeFormattingProvider.scala index 45514a91113..0a1910913b0 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/formatting/RangeFormattingProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/formatting/RangeFormattingProvider.scala @@ -31,12 +31,12 @@ trait RangeFormatter { class RangeFormattingProvider( buffers: Buffers, - trees: Trees + trees: Trees, + userConfig: () => UserConfiguration ) { val formatters: List[RangeFormatter] = List( - // enableStripMargin is not used on rangeFormatting - MultilineString(() => UserConfiguration()), - IndentOnPaste + MultilineString(userConfig), + IndentOnPaste(userConfig) ) def format( diff --git a/tests/unit/src/test/scala/tests/rangeFormatting/IndentWhenPastingSuite.scala b/tests/unit/src/test/scala/tests/rangeFormatting/IndentWhenPastingSuite.scala index a41db6748b2..41c8bd0ee82 100644 --- a/tests/unit/src/test/scala/tests/rangeFormatting/IndentWhenPastingSuite.scala +++ b/tests/unit/src/test/scala/tests/rangeFormatting/IndentWhenPastingSuite.scala @@ -1,6 +1,7 @@ package tests.rangeFormatting import scala.meta.internal.metals.BuildInfo +import scala.meta.internal.metals.UserConfiguration import munit.Location import munit.TestOptions @@ -26,6 +27,9 @@ class IndentWhenPastingSuite val blank = " " + override def userConfig: UserConfiguration = + super.userConfig.copy(enableIndentOnPaste = true) + check( "single-line", """ diff --git a/tests/unit/src/test/scala/tests/rangeFormatting/MultilineStringRangeFormattingWhenPastingSuite.scala b/tests/unit/src/test/scala/tests/rangeFormatting/MultilineStringRangeFormattingWhenPastingSuite.scala index e1e4fa42409..f5b0d1548f2 100644 --- a/tests/unit/src/test/scala/tests/rangeFormatting/MultilineStringRangeFormattingWhenPastingSuite.scala +++ b/tests/unit/src/test/scala/tests/rangeFormatting/MultilineStringRangeFormattingWhenPastingSuite.scala @@ -1,5 +1,7 @@ package tests.rangeFormatting +import scala.meta.internal.metals.UserConfiguration + import munit.Location import munit.TestOptions import org.eclipse.lsp4j.FormattingOptions @@ -14,6 +16,9 @@ class MultilineStringRangeFormattingWhenPastingSuite true ) + override def userConfig: UserConfiguration = + super.userConfig.copy(enableIndentOnPaste = true) + check( "lines", s""" diff --git a/tests/unit/src/test/scala/tests/rangeFormatting/MultilineStringRangeFormattingWhenSelectingSuite.scala b/tests/unit/src/test/scala/tests/rangeFormatting/MultilineStringRangeFormattingWhenSelectingSuite.scala index b15899b0d67..9e0bc53bc0e 100644 --- a/tests/unit/src/test/scala/tests/rangeFormatting/MultilineStringRangeFormattingWhenSelectingSuite.scala +++ b/tests/unit/src/test/scala/tests/rangeFormatting/MultilineStringRangeFormattingWhenSelectingSuite.scala @@ -1,5 +1,7 @@ package tests.rangeFormatting +import scala.meta.internal.metals.UserConfiguration + import munit.Location import munit.TestOptions import org.eclipse.lsp4j.FormattingOptions @@ -7,6 +9,10 @@ import tests.BaseLspSuite class MultilineStringRangeFormattingWhenSelectingSuite extends BaseLspSuite("rangeFormatting") { + + override def userConfig: UserConfiguration = + super.userConfig.copy(enableIndentOnPaste = true) + check( "start-misindent-line", s"""