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

More VS cleanup #15954

Merged
merged 25 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type internal FSharpClassificationService [<ImportingConstructor>] () =
ClassificationTypeNames.Text

match RoslynHelpers.TryFSharpRangeToTextSpan(text, tok.Range) with
| Some span -> result.Add(ClassifiedSpan(TextSpan(textSpan.Start + span.Start, span.Length), spanKind))
| ValueSome span -> result.Add(ClassifiedSpan(TextSpan(textSpan.Start + span.Start, span.Length), spanKind))
| _ -> ()

let flags =
Expand All @@ -79,8 +79,8 @@ type internal FSharpClassificationService [<ImportingConstructor>] () =
=
for item in items do
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) with
| None -> ()
| Some span ->
| ValueNone -> ()
| ValueSome span ->
let span =
match item.Type with
| SemanticClassificationType.Printf -> span
Expand Down
120 changes: 63 additions & 57 deletions vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ open Microsoft.VisualStudio.TextManager.Interop
open Microsoft.VisualStudio.LanguageServices
open Microsoft.VisualStudio.Utilities
open FSharp.Compiler.EditorServices
open CancellableTasks.CancellableTaskBuilder
open CancellableTasks

type internal XmlDocCommandFilter(wpfTextView: IWpfTextView, filePath: string, workspace: VisualStudioWorkspace) =

Expand Down Expand Up @@ -58,76 +60,80 @@ type internal XmlDocCommandFilter(wpfTextView: IWpfTextView, filePath: string, w

match XmlDocComment.IsBlank lineWithLastCharInserted with
| Some i when i = indexOfCaret ->
asyncMaybe {
cancellableTask {
try
// XmlDocable line #1 are 1-based, editor is 0-based
let curEditorLineNum =
wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber

let! document = getLastDocument ()
let! cancellationToken = Async.CancellationToken |> liftAsync
let! sourceText = document.GetTextAsync(cancellationToken)
let! parseResults = document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter)) |> liftAsync

let xmlDocables =
XmlDocParser.GetXmlDocables(sourceText.ToFSharpSourceText(), parseResults.ParseTree)

let xmlDocablesBelowThisLine =
// +1 because looking below current line for e.g. a 'member' or 'let'
xmlDocables
|> List.filter (fun (XmlDocable (line, _indent, _paramNames)) -> line = curEditorLineNum + 1)

match xmlDocablesBelowThisLine with
| [] -> ()
| XmlDocable (_line, indent, paramNames) :: _xs ->
// delete the slashes the user typed (they may be indented wrong)
let editorLineToDelete =
wpfTextView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(
wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber
)

wpfTextView.TextBuffer.Delete(editorLineToDelete.Extent.Span) |> ignore
// add the new xmldoc comment
let toInsert = new Text.StringBuilder()

toInsert
.Append(' ', indent)
.AppendLine("/// <summary>")
.Append(' ', indent)
.AppendLine("/// ")
.Append(' ', indent)
.Append("/// </summary>")
|> ignore

paramNames
|> List.iter (fun p ->
toInsert
.AppendLine()
.Append(' ', indent)
.Append(sprintf "/// <param name=\"%s\"></param>" p)
|> ignore)
let document = getLastDocument ()

match document with
| None -> ()
| Some document ->
let! cancellationToken = CancellableTask.getCancellationToken ()
let! sourceText = document.GetTextAsync(cancellationToken)
let! parseResults = document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter))

let xmlDocables =
XmlDocParser.GetXmlDocables(sourceText.ToFSharpSourceText(), parseResults.ParseTree)

let _newSS =
wpfTextView.TextBuffer.Insert(
wpfTextView.Caret.Position.BufferPosition.Position,
toInsert.ToString()
)
// move the caret to between the summary tags
let lastLine = wpfTextView.Caret.Position.BufferPosition.GetContainingLine()
let xmlDocablesBelowThisLine =
// +1 because looking below current line for e.g. a 'member' or 'let'
xmlDocables
|> List.filter (fun (XmlDocable (line, _indent, _paramNames)) -> line = curEditorLineNum + 1)

let middleSummaryLine =
wpfTextView.TextSnapshot.GetLineFromLineNumber(lastLine.LineNumber - 1 - paramNames.Length)
match xmlDocablesBelowThisLine with
| [] -> ()
| XmlDocable (_line, indent, paramNames) :: _xs ->
// delete the slashes the user typed (they may be indented wrong)
let editorLineToDelete =
wpfTextView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(
wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber
)

wpfTextView.Caret.MoveTo(wpfTextView.GetTextViewLineContainingBufferPosition(middleSummaryLine.Start))
|> ignore
wpfTextView.TextBuffer.Delete(editorLineToDelete.Extent.Span) |> ignore
// add the new xmldoc comment
let toInsert = new Text.StringBuilder()

shouldCommitCharacter <- false
toInsert
.Append(' ', indent)
.AppendLine("/// <summary>")
.Append(' ', indent)
.AppendLine("/// ")
.Append(' ', indent)
.Append("/// </summary>")
|> ignore

paramNames
|> List.iter (fun p ->
toInsert
.AppendLine()
.Append(' ', indent)
.Append(sprintf "/// <param name=\"%s\"></param>" p)
|> ignore)

let _newSS =
wpfTextView.TextBuffer.Insert(
wpfTextView.Caret.Position.BufferPosition.Position,
toInsert.ToString()
)
// move the caret to between the summary tags
let lastLine = wpfTextView.Caret.Position.BufferPosition.GetContainingLine()

let middleSummaryLine =
wpfTextView.TextSnapshot.GetLineFromLineNumber(lastLine.LineNumber - 1 - paramNames.Length)

wpfTextView.Caret.MoveTo(wpfTextView.GetTextViewLineContainingBufferPosition(middleSummaryLine.Start))
|> ignore

shouldCommitCharacter <- false
with ex ->
Assert.Exception ex
()
}
|> Async.Ignore
|> Async.StartImmediate
|> CancellableTask.startAsTaskWithoutCancellation // We don't have a cancellation token here at the moment, maybe there's a better way of handling it in modern VS?
|> ignore
| Some _
| None -> ()
| _ -> ()
Expand Down
7 changes: 3 additions & 4 deletions vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,13 @@ module CancellableTasks =

/// <summary>
/// Builds a cancellableTask using computation expression syntax.
/// Default behaviour when binding (v)options is to return a cacnelled task.
/// Default behaviour when binding (v)options is to return a cancelled task.
/// </summary>
let foregroundCancellableTask = CancellableTaskBuilder(false)

/// <summary>
/// Builds a cancellableTask using computation expression syntax which switches to execute on a background thread if not already doing so.
/// Default behaviour when binding (v)options is to return a cacnelled task.
/// Default behaviour when binding (v)options is to return a cancelled task.
/// </summary>
let cancellableTask = CancellableTaskBuilder(true)

Expand Down Expand Up @@ -1105,8 +1105,7 @@ module CancellableTasks =
return! Task.WhenAll (seq { for task in tasks do yield startTask ct task })
}

let inline ignore (ctask: CancellableTask<_>) =
ctask |> toUnit
let inline ignore ([<InlineIfLambda>] ctask: CancellableTask<_>) = toUnit ctask

/// <exclude />
[<AutoOpen>]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,8 @@ type Solution with
// It's crucial to normalize file path here (specificaly, remove relative parts),
// otherwise Roslyn does not find documents.
self.GetDocumentIdsWithFilePath(Path.GetFullPath filePath)
|> Seq.tryHead
|> Option.map (fun docId -> self.GetDocument docId)

/// Try to find the document corresponding to the provided filepath and ProjectId within this solution
member self.TryGetDocumentFromPath(filePath, projId: ProjectId) =
// It's crucial to normalize file path here (specificaly, remove relative parts),
// otherwise Roslyn does not find documents.
self.GetDocumentIdsWithFilePath(Path.GetFullPath filePath)
|> Seq.filter (fun x -> x.ProjectId = projId)
|> Seq.tryHead
|> Option.map (fun docId -> self.GetDocument docId)
|> ImmutableArray.tryHeadV
|> ValueOption.map (fun docId -> self.GetDocument docId)

/// Try to get a project inside the solution using the project's id
member self.TryGetProject(projId: ProjectId) =
Expand Down
149 changes: 149 additions & 0 deletions vsintegration/src/FSharp.Editor/Common/Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,66 @@ module ValueOption =
| _ -> None

[<RequireQualifiedAccess>]
module IEnumerator =
let chooseV f (e: IEnumerator<'T>) =
let mutable started = false
let mutable curr = None

let get () =
if not started then
raise(InvalidOperationException("Not started"))

match curr with
| None ->
raise(InvalidOperationException("Already finished"))
| Some x -> x


{ new IEnumerator<'U> with
member _.Current = get ()
interface System.Collections.IEnumerator with
member _.Current = box (get ())

member _.MoveNext() =
if not started then
started <- true

curr <- None

while (curr.IsNone && e.MoveNext()) do
curr <- f e.Current

Option.isSome curr

member _.Reset() =
raise(NotSupportedException("Reset is not supported"))
interface System.IDisposable with
member _.Dispose() =
e.Dispose()
}
[<RequireQualifiedAccess>]
module Seq =

let mkSeq f =
{ new IEnumerable<'U> with
member _.GetEnumerator() = f()

interface System.Collections.IEnumerable with
member _.GetEnumerator() = (f() :> System.Collections.IEnumerator) }

let inline revamp f (ie: seq<_>) =
mkSeq (fun () -> f (ie.GetEnumerator()))

let toImmutableArray (xs: seq<'a>) : ImmutableArray<'a> = xs.ToImmutableArray()

let inline tryHeadV (source: seq<_>) =
use e = source.GetEnumerator()

if (e.MoveNext()) then
ValueSome e.Current
else
ValueNone

let inline tryFindV ([<InlineIfLambda>] predicate) (source: seq<'T>) =
use e = source.GetEnumerator()
let mutable res = ValueNone
Expand All @@ -326,6 +382,18 @@ module Seq =

loop 0

let inline tryPickV ([<InlineIfLambda>] chooser) (source: seq<'T>) =
use e = source.GetEnumerator()
let mutable res = ValueNone

while (ValueOption.isNone res && e.MoveNext()) do
res <- chooser e.Current

res

let chooseV chooser source =
revamp (IEnumerator.chooseV chooser) source

[<RequireQualifiedAccess>]
module Array =
let inline foldi ([<InlineIfLambda>] folder: 'State -> int -> 'T -> 'State) (state: 'State) (xs: 'T[]) =
Expand All @@ -340,6 +408,12 @@ module Array =

let toImmutableArray (xs: 'T[]) = xs.ToImmutableArray()

let inline tryHeadV (array: _[]) =
if array.Length = 0 then
ValueNone
else
ValueSome array[0]

let inline tryFindV ([<InlineIfLambda>] predicate) (array: _[]) =

let rec loop i =
Expand All @@ -349,6 +423,81 @@ module Array =

loop 0

let inline chooseV ([<InlineIfLambda>] chooser: 'T -> 'U voption) (array: 'T[]) =

let mutable i = 0
let mutable first = Unchecked.defaultof<'U>
let mutable found = false

while i < array.Length && not found do
let element = array.[i]

match chooser element with
| ValueNone -> i <- i + 1
| ValueSome b ->
first <- b
found <- true

if i <> array.Length then

let chunk1: 'U[] =
Array.zeroCreate ((array.Length >>> 2) + 1)

chunk1.[0] <- first
let mutable count = 1
i <- i + 1

while count < chunk1.Length && i < array.Length do
let element = array.[i]

match chooser element with
| ValueNone -> ()
| ValueSome b ->
chunk1.[count] <- b
count <- count + 1

i <- i + 1

if i < array.Length then
let chunk2: 'U[] =
Array.zeroCreate (array.Length - i)

count <- 0

while i < array.Length do
let element = array.[i]

match chooser element with
| ValueNone -> ()
| ValueSome b ->
chunk2.[count] <- b
count <- count + 1

i <- i + 1

let res: 'U[] =
Array.zeroCreate (chunk1.Length + count)

Array.Copy(chunk1, res, chunk1.Length)
Array.Copy(chunk2, 0, res, chunk1.Length, count)
res
else
Array.sub chunk1 0 count
else
Array.empty

[<RequireQualifiedAccess>]
module ImmutableArray =
let inline tryHeadV (xs: ImmutableArray<'T>) : 'T voption =
if xs.Length = 0 then
ValueNone
else
ValueSome xs[0]

let inline empty<'T> = ImmutableArray<'T>.Empty

let inline create<'T> (x: 'T) = ImmutableArray.Create<'T>(x)

[<RequireQualifiedAccess>]
module List =
let rec tryFindV predicate list =
Expand Down
Loading