Skip to content

Commit

Permalink
Merge pull request #3129 from tgodzik/add-setting
Browse files Browse the repository at this point in the history
Add setting to enable indentation on paste
  • Loading branch information
tgodzik authored Sep 16, 2021
2 parents e2cb32b + c2a727f commit 545b1e5
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand Down Expand Up @@ -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 `""`.""",
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -412,6 +424,7 @@ object UserConfiguration {
showImplicitConversionsAndClasses,
remoteLanguageServer,
enableStripMarginOnTypeFormatting,
enableIndentOnPaste,
excludedPackages,
defaultScalaVersion
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -26,6 +27,9 @@ class IndentWhenPastingSuite

val blank = " "

override def userConfig: UserConfiguration =
super.userConfig.copy(enableIndentOnPaste = true)

check(
"single-line",
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package tests.rangeFormatting

import scala.meta.internal.metals.UserConfiguration

import munit.Location
import munit.TestOptions
import org.eclipse.lsp4j.FormattingOptions
Expand All @@ -14,6 +16,9 @@ class MultilineStringRangeFormattingWhenPastingSuite
true
)

override def userConfig: UserConfiguration =
super.userConfig.copy(enableIndentOnPaste = true)

check(
"lines",
s"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package tests.rangeFormatting

import scala.meta.internal.metals.UserConfiguration

import munit.Location
import munit.TestOptions
import org.eclipse.lsp4j.FormattingOptions
import tests.BaseLspSuite

class MultilineStringRangeFormattingWhenSelectingSuite
extends BaseLspSuite("rangeFormatting") {

override def userConfig: UserConfiguration =
super.userConfig.copy(enableIndentOnPaste = true)

check(
"start-misindent-line",
s"""
Expand Down

0 comments on commit 545b1e5

Please sign in to comment.