Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format text lines #2274

Merged
merged 13 commits into from
Jul 31, 2018
Merged
49 changes: 33 additions & 16 deletions Src/VimCore/CommandUtil.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1021,26 +1021,40 @@ type internal CommandUtil

CommandResult.Completed ModeSwitch.SwitchPreviousMode

/// Format the 'count' lines in the buffer
member x.FormatLines count =
/// Format the 'count' code lines in the buffer
member x.FormatCodeLines count =
let range = SnapshotLineRangeUtil.CreateForLineAndMaxCount x.CaretLine count
_commonOperations.FormatLines range
_commonOperations.FormatCodeLines range
CommandResult.Completed ModeSwitch.NoSwitch

/// Format the selected lines
member x.FormatLinesVisual (visualSpan: VisualSpan) =
/// Format the 'count' text lines in the buffer
member x.FormatTextLines count preserveCaretPosition =
let range = SnapshotLineRangeUtil.CreateForLineAndMaxCount x.CaretLine count
_commonOperations.FormatTextLines range preserveCaretPosition
CommandResult.Completed ModeSwitch.NoSwitch

// Use a transaction so the formats occur as a single operation
x.EditWithUndoTransaction "Format" (fun () ->
visualSpan.Spans
|> Seq.map SnapshotLineRangeUtil.CreateForSpan
|> Seq.iter _commonOperations.FormatLines)
/// Format the selected code lines
member x.FormatCodeLinesVisual (visualSpan: VisualSpan) =
visualSpan.EditSpan.OverarchingSpan
|> SnapshotLineRangeUtil.CreateForSpan
|> _commonOperations.FormatCodeLines
CommandResult.Completed ModeSwitch.SwitchPreviousMode

/// Format the selected text lines
member x.FormatTextLinesVisual (visualSpan: VisualSpan) (preserveCaretPosition: bool) =
visualSpan.EditSpan.OverarchingSpan
|> SnapshotLineRangeUtil.CreateForSpan
|> (fun lineRange -> _commonOperations.FormatTextLines lineRange preserveCaretPosition)
CommandResult.Completed ModeSwitch.SwitchPreviousMode

/// Format the lines in the Motion
member x.FormatMotion (result: MotionResult) =
_commonOperations.FormatLines result.LineRange
/// Format the code lines in the Motion
member x.FormatCodeMotion (result: MotionResult) =
_commonOperations.FormatCodeLines result.LineRange
CommandResult.Completed ModeSwitch.NoSwitch

/// Format the text lines in the Motion
member x.FormatTextMotion (result: MotionResult) preserveCaretPosition =
_commonOperations.FormatTextLines result.LineRange preserveCaretPosition
CommandResult.Completed ModeSwitch.NoSwitch

/// Get the appropriate register for the CommandData
Expand Down Expand Up @@ -2546,8 +2560,10 @@ type internal CommandUtil
| NormalCommand.FilterMotion motion -> x.RunWithMotion motion x.FilterMotion
| NormalCommand.FoldLines -> x.FoldLines data.CountOrDefault
| NormalCommand.FoldMotion motion -> x.RunWithMotion motion x.FoldMotion
| NormalCommand.FormatLines -> x.FormatLines count
| NormalCommand.FormatMotion motion -> x.RunWithMotion motion x.FormatMotion
| NormalCommand.FormatCodeLines -> x.FormatCodeLines count
| NormalCommand.FormatCodeMotion motion -> x.RunWithMotion motion x.FormatCodeMotion
| NormalCommand.FormatTextLines preserveCaretPosition -> x.FormatTextLines count preserveCaretPosition
| NormalCommand.FormatTextMotion (preserveCaretPosition, motion) -> x.RunWithMotion motion (fun motion -> x.FormatTextMotion motion preserveCaretPosition)
| NormalCommand.GoToDefinition -> x.GoToDefinition()
| NormalCommand.GoToFileUnderCaret useNewWindow -> x.GoToFileUnderCaret useNewWindow
| NormalCommand.GoToGlobalDeclaration -> x.GoToGlobalDeclaration()
Expand Down Expand Up @@ -2636,7 +2652,8 @@ type internal CommandUtil
| VisualCommand.DeleteSelection -> x.DeleteSelection registerName visualSpan
| VisualCommand.DeleteLineSelection -> x.DeleteLineSelection registerName visualSpan
| VisualCommand.FilterLines -> x.FilterLinesVisual visualSpan
| VisualCommand.FormatLines -> x.FormatLinesVisual visualSpan
| VisualCommand.FormatCodeLines -> x.FormatCodeLinesVisual visualSpan
| VisualCommand.FormatTextLines preserveCaretPosition -> x.FormatTextLinesVisual visualSpan preserveCaretPosition
| VisualCommand.FoldSelection -> x.FoldSelection visualSpan
| VisualCommand.GoToFileInSelectionInNewWindow -> x.GoToFileInSelectionInNewWindow visualSpan
| VisualCommand.GoToFileInSelection -> x.GoToFileInSelection visualSpan
Expand Down
153 changes: 150 additions & 3 deletions Src/VimCore/CommonOperations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,156 @@ type internal CommonOperations
TextViewUtil.MoveCaretToPoint _textView firstLine.Start
_editorOperations.MoveToStartOfLineAfterWhiteSpace(false)

/// Format the specified line range
member x.FormatLines range =
/// Format the code lines in the specified line range
member x.FormatCodeLines range =
_vimHost.FormatLines _textView range

// Place the cursor on the first non-blank character of the first line formatted.
let firstLine = SnapshotUtil.GetLine _textView.TextSnapshot range.StartLineNumber
TextViewUtil.MoveCaretToPoint _textView firstLine.Start
_editorOperations.MoveToStartOfLineAfterWhiteSpace(false)

/// Format the text lines in the specified line range
member x.FormatTextLines (range: SnapshotLineRange) preserveCaretPosition =

// Get formatting configuration values.
let autoIndent = _localSettings.AutoIndent
let textWidth =
if _localSettings.TextWidth = 0 then
VimConstants.DefaultFormatTextWidth
else
_localSettings.TextWidth
let comments = _localSettings.Comments
let tabStop = _localSettings.TabStop

// Extract the lines to be formatted and the first line.
let lines = range.Lines |> Seq.map SnapshotLineUtil.GetText
let firstLine = lines |> Seq.head

// Extract the leader string from a comment specification, e.g. "//".
let getLeaderFromSpec (spec: string) =
let colonIndex = spec.IndexOf(':')
if colonIndex = -1 then
spec
else
spec.Substring(colonIndex + 1)

// Get the leader pattern for a leader string.
let getLeaderPattern (leader: string) =
if leader = "" then
@"^\s*"
else
@"^\s*" + Regex.Escape(leader) + @"+\s*"

// Convert the comment specifications into leader patterns.
let patterns =
comments + ",:"
|> StringUtil.Split ','
|> Seq.map getLeaderFromSpec
|> Seq.map getLeaderPattern

// Check the first line against a potential comment pattern.
let checkPattern pattern =
let capture = Regex.Match(firstLine, pattern)
if capture.Success then
true, pattern, capture.Value
else
false, pattern, ""

// Choose a pattern and a leader.
let _, pattern, leader =
patterns
|> Seq.map checkPattern
|> Seq.filter (fun (matches, _, _) -> matches)
|> Seq.head

// Decide whether to use the leader for all lines.
let useLeaderForAllLines =
not (StringUtil.IsWhiteSpace leader) || autoIndent

// Strip the leader from a line.
let stripLeader (line: string) =
let capture = Regex.Match(line, pattern)
if capture.Success then
line.Substring(capture.Length)
else
line

// Strip the leader from all the lines.
let strippedLines =
lines
|> Seq.map stripLeader

// Split a line into words on whitespace.
let splitWords (line: string) =
if StringUtil.IsWhiteSpace line then
seq { yield "" }
else
Regex.Matches(line + " ", @"\S+\s+")
|> Seq.cast<Capture>
|> Seq.map (fun capture -> capture.Value)

// Concatenate a reversed list of words into a line.
let concatWords (words: string list) =
words
|> Seq.rev
|> String.concat ""
|> (fun line -> line.TrimEnd())

// Concatenate words into a line and prepend it to a list of lines.
let prependLine (words: string list) (lines: string list) =
if words.IsEmpty then
lines
else
concatWords words :: lines

// Calculate the length of the leader with tabs expanded.
let leaderLength =
StringUtil.ExpandTabsForColumn leader 0 tabStop
|> StringUtil.GetLength

// Aggregrate individual words into lines of limited length.
let takeWord ((column: int), (words: string list), (lines: string list)) (word: string) =

// Calculate the working limit for line length.
let limit =
if lines.IsEmpty || useLeaderForAllLines then
textWidth - leaderLength
else
textWidth

if word = "" then
0, List.Empty, "" :: prependLine words lines
elif column = 0 || column + word.TrimEnd().Length <= limit then
column + word.Length, word :: words, lines
else
word.Length, word :: List.Empty, concatWords words :: lines

// Add a leader to the formatted line if appropriate.
let addLeader (i: int) (line: string) =
if i = 0 || useLeaderForAllLines then
(leader + line).TrimEnd()
else
line

// Split the lines into words and then format them into lines using the aggregator.
let formattedLines =
let _, words, lines =
strippedLines
|> Seq.collect splitWords
|> Seq.fold takeWord (0, List.Empty, List.Empty)
prependLine words lines
|> Seq.rev
|> Seq.mapi addLeader

// Concatenate the formatted lines.
let newLine = EditUtil.NewLine _editorOptions
let replacement = formattedLines |> String.concat newLine

// Replace the old lines with the formatted lines.
_textBuffer.Replace(range.Extent.Span, replacement) |> ignore


// Place the cursor on the first non-blank character of the first line formatted.
let firstLine = SnapshotUtil.GetLine _textView.TextSnapshot range.StartLineNumber
TextViewUtil.MoveCaretToPoint _textView firstLine.Start
Expand Down Expand Up @@ -1843,7 +1989,8 @@ type internal CommonOperations
member x.EnsureAtPoint point viewFlags = x.EnsureAtPoint point viewFlags
member x.FillInVirtualSpace() = x.FillInVirtualSpace()
member x.FilterLines range command = x.FilterLines range command
member x.FormatLines range = x.FormatLines range
member x.FormatCodeLines range = x.FormatCodeLines range
member x.FormatTextLines range preserveCaretPosition = x.FormatTextLines range preserveCaretPosition
member x.GetRegister registerName = x.GetRegister registerName
member x.GetNewLineText point = x.GetNewLineText point
member x.GetNewLineIndent contextLine newLine = x.GetNewLineIndent contextLine newLine
Expand Down
5 changes: 5 additions & 0 deletions Src/VimCore/Constants.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ module VimConstants =
[<Literal>]
let DefaultHistoryLength = 20

/// The default text width that FormatTextLines will use if the 'textwidth'
/// setting is zero (see vim ':help gq')
[<Literal>]
let DefaultFormatTextWidth = 79

[<Literal>]
let IncrementalSearchTagName = "vsvim_incrementalsearch"

Expand Down
29 changes: 20 additions & 9 deletions Src/VimCore/CoreInterfaces.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2721,11 +2721,17 @@ type NormalCommand =
/// Create a fold over the specified motion
| FoldMotion of MotionData

/// Format the specified lines
| FormatLines
/// Format the code in the specified lines
| FormatCodeLines

/// Format the specified motion
| FormatMotion of MotionData
/// Format the code in the specified motion
| FormatCodeMotion of MotionData

/// Format the text in the specified lines, optionally preserving the caret position
| FormatTextLines of bool

/// Format the text in the specified motion
| FormatTextMotion of bool * MotionData

/// Go to the definition of hte word under the caret.
| GoToDefinition
Expand Down Expand Up @@ -2940,11 +2946,13 @@ type NormalCommand =

member private x.GetMotionDataCore() =
match x with
| NormalCommand.ChangeCaseMotion (changeCharacterKind, motion) -> Some ((fun motion -> NormalCommand.ChangeCaseMotion (changeCharacterKind, motion)), motion)
| NormalCommand.ChangeMotion motion -> Some (NormalCommand.ChangeMotion, motion)
| NormalCommand.DeleteMotion motion -> Some (NormalCommand.DeleteMotion, motion)
| NormalCommand.FilterMotion motion -> Some (NormalCommand.FilterMotion, motion)
| NormalCommand.FoldMotion motion -> Some (NormalCommand.FoldMotion, motion)
| NormalCommand.FormatMotion motion -> Some (NormalCommand.FormatMotion, motion)
| NormalCommand.FormatCodeMotion motion -> Some (NormalCommand.FormatCodeMotion, motion)
| NormalCommand.FormatTextMotion (preserveCaretPosition, motion) -> Some ((fun motion -> NormalCommand.FormatTextMotion (preserveCaretPosition, motion)), motion)
| NormalCommand.ShiftMotionLinesLeft motion -> Some (NormalCommand.ShiftMotionLinesLeft, motion)
| NormalCommand.ShiftMotionLinesRight motion -> Some (NormalCommand.ShiftMotionLinesRight, motion)
| NormalCommand.Yank motion -> Some (NormalCommand.Yank, motion)
Expand All @@ -2953,7 +2961,6 @@ type NormalCommand =
| NormalCommand.AddToWord _ -> None
| NormalCommand.ChangeCaseCaretLine _ -> None
| NormalCommand.ChangeCaseCaretPoint _ -> None
| NormalCommand.ChangeCaseMotion _ -> None
| NormalCommand.ChangeLines -> None
| NormalCommand.ChangeTillEndOfLine -> None
| NormalCommand.CloseAllFolds -> None
Expand All @@ -2972,7 +2979,8 @@ type NormalCommand =
| NormalCommand.DisplayCharacterCodePoint ->None
| NormalCommand.FilterLines -> None
| NormalCommand.FoldLines -> None
| NormalCommand.FormatLines -> None
| NormalCommand.FormatCodeLines -> None
| NormalCommand.FormatTextLines _ -> None
| NormalCommand.GoToDefinition -> None
| NormalCommand.GoToFileUnderCaret _ -> None
| NormalCommand.GoToGlobalDeclaration -> None
Expand Down Expand Up @@ -3081,8 +3089,11 @@ type VisualCommand =
/// Fold the current selected lines
| FoldSelection

/// Format the selected text
| FormatLines
/// Format the selected code lines
| FormatCodeLines

/// Format the selected text lines, optionally preserving the caret position
| FormatTextLines of bool

/// GoTo the file under the cursor in a new window
| GoToFileInSelectionInNewWindow
Expand Down
5 changes: 4 additions & 1 deletion Src/VimCore/MefInterfaces.fs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,10 @@ type ICommonOperations =
abstract FilterLines: SnapshotLineRange -> command: string -> unit

/// Format the specified line range
abstract FormatLines: SnapshotLineRange -> unit
abstract FormatCodeLines: SnapshotLineRange -> unit

/// Format the specified line range
abstract FormatTextLines: SnapshotLineRange -> preserveCaretPosition: bool -> unit

/// Get the new line text which should be used for new lines at the given SnapshotPoint
abstract GetNewLineText: SnapshotPoint -> string
Expand Down
10 changes: 8 additions & 2 deletions Src/VimCore/Modes_Normal_NormalMode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,11 @@ type internal NormalMode
yield (".", CommandFlags.Special, NormalCommand.RepeatLastCommand)
yield ("<lt><lt>", CommandFlags.Repeatable, NormalCommand.ShiftLinesLeft)
yield (">>", CommandFlags.Repeatable, NormalCommand.ShiftLinesRight)
yield ("==", CommandFlags.Repeatable, NormalCommand.FormatLines)
yield ("==", CommandFlags.Repeatable, NormalCommand.FormatCodeLines)
yield ("gqgq", CommandFlags.Repeatable, NormalCommand.FormatTextLines false)
yield ("gqq", CommandFlags.Repeatable, NormalCommand.FormatTextLines false)
yield ("gwgw", CommandFlags.Repeatable, NormalCommand.FormatTextLines true)
yield ("gww", CommandFlags.Repeatable, NormalCommand.FormatTextLines true)
yield ("!!", CommandFlags.Repeatable, NormalCommand.FilterLines)
yield (":", CommandFlags.Special, NormalCommand.SwitchMode (ModeKind.Command, ModeArgument.None))
yield ("<C-^>", CommandFlags.None, NormalCommand.GoToRecentView)
Expand All @@ -187,7 +191,9 @@ type internal NormalMode
yield ("<lt>", CommandFlags.Repeatable ||| CommandFlags.ShiftLeft, NormalCommand.ShiftMotionLinesLeft)
yield (">", CommandFlags.Repeatable ||| CommandFlags.ShiftRight, NormalCommand.ShiftMotionLinesRight)
yield ("!", CommandFlags.Repeatable, NormalCommand.FilterMotion)
yield ("=", CommandFlags.Repeatable, NormalCommand.FormatMotion)
yield ("=", CommandFlags.Repeatable, NormalCommand.FormatCodeMotion)
yield ("gq", CommandFlags.Repeatable, (fun motion -> NormalCommand.FormatTextMotion (false, motion)))
yield ("gw", CommandFlags.Repeatable, (fun motion -> NormalCommand.FormatTextMotion (true, motion)))
} |> Seq.map (fun (str, flags, command) ->
let keyInputSet = KeyNotationUtil.StringToKeyInputSet str
CommandBinding.MotionBinding (keyInputSet, flags, command))
Expand Down
4 changes: 3 additions & 1 deletion Src/VimCore/Modes_Visual_VisualMode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ type internal VisualMode
yield ("<lt>", CommandFlags.Repeatable, VisualCommand.ShiftLinesLeft)
yield (">", CommandFlags.Repeatable, VisualCommand.ShiftLinesRight)
yield ("~", CommandFlags.Repeatable, VisualCommand.ChangeCase ChangeCharacterKind.ToggleCase)
yield ("=", CommandFlags.Repeatable, VisualCommand.FormatLines)
yield ("=", CommandFlags.Repeatable, VisualCommand.FormatCodeLines)
yield ("gq", CommandFlags.Repeatable, VisualCommand.FormatTextLines false)
yield ("gw", CommandFlags.Repeatable, VisualCommand.FormatTextLines true)
yield ("!", CommandFlags.Repeatable, VisualCommand.FilterLines)
} |> Seq.map (fun (str, flags, command) ->
let keyInputSet = KeyNotationUtil.StringToKeyInputSet str
Expand Down
2 changes: 2 additions & 0 deletions Src/VimCore/StringUtil.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ module internal StringUtil =

let Empty = System.String.Empty

let GetLength (input: string) = input.Length

let FindFirst (input:seq<char>) index del =
let found =
input
Expand Down
Loading