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

Enabled debugging services in Roslyn branch #1428

Merged
merged 6 commits into from Aug 16, 2016
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
133 changes: 29 additions & 104 deletions vsintegration/src/FSharp.Editor/BraceMatchingService.fs
Expand Up @@ -3,117 +3,42 @@
namespace Microsoft.VisualStudio.FSharp.Editor

open System
open System.Composition
open System.Collections.Concurrent
open System.Collections.Generic
open System.Threading
open System.Threading.Tasks
open System.Linq

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Classification
open Microsoft.CodeAnalysis.Editor
open Microsoft.CodeAnalysis.Editor.Implementation.BraceMatching
open Microsoft.CodeAnalysis.Editor.Shared.Utilities
open Microsoft.CodeAnalysis.Host.Mef
open Microsoft.CodeAnalysis.Text

open Microsoft.VisualStudio.FSharp.LanguageService
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Text.Tagging

open Microsoft.FSharp.Compiler.Parser
open Microsoft.FSharp.Compiler.SourceCodeServices

// FSROSLYNTODO: add defines flags if available from project sites and files

[<ExportBraceMatcher(FSharpCommonConstants.FSharpLanguageName)>]
type internal FSharpBraceMatchingService() =

static let supportedBraceTypes = [
('(', ')');
('<', '>');
('[', ']');
('{', '}');
]

static let ignoredClassificationTypes = [
ClassificationTypeNames.Comment;
ClassificationTypeNames.StringLiteral;
ClassificationTypeNames.ExcludedCode;
]

static let getBraceMatchingResult(sourceText: SourceText, fileName: Option<string>, defines: string list, position: int, cancellationToken: CancellationToken) : Option<BraceMatchingResult> =
if position < 0 || position >= sourceText.Length then
None
else
let shouldBeIgnored(characterPosition) =
let textSpan = TextSpan(characterPosition, 1)
let classifiedSpans = FSharpColorizationService.GetColorizationData(sourceText, textSpan, fileName, defines, cancellationToken)
if classifiedSpans.Any() then
ignoredClassificationTypes |> Seq.contains (classifiedSpans.First().ClassificationType)
else
false

if shouldBeIgnored(position) then
None
else
let currentCharacter = sourceText.[position]

let proceedToStartOfString(i) = i - 1
let proceedToEndOfString(i) = i + 1

let afterEndOfString(i) = i >= sourceText.Length
let beforeStartOfString(i) = i < 0

let pickBraceType(leftBrace, rightBrace) =
if currentCharacter = leftBrace then Some(proceedToEndOfString, afterEndOfString, leftBrace, rightBrace)
else if currentCharacter = rightBrace then Some(proceedToStartOfString, beforeStartOfString, rightBrace, leftBrace)
else None

match supportedBraceTypes |> List.tryPick(pickBraceType) with
| None -> None
| Some(proceedFunc, stoppingCondition, matchedBrace, nonMatchedBrace) ->
let mutable currentPosition = proceedFunc position
let mutable result = None
let mutable braceDepth = 0

while result.IsSome = false && stoppingCondition(currentPosition) = false do
cancellationToken.ThrowIfCancellationRequested()
if shouldBeIgnored(currentPosition) = false then
if sourceText.[currentPosition] = matchedBrace then
braceDepth <- braceDepth + 1
else if sourceText.[currentPosition] = nonMatchedBrace then
if braceDepth = 0 then
result <- Some(BraceMatchingResult(TextSpan(min position currentPosition, 1), TextSpan(max position currentPosition, 1)))
else
braceDepth <- braceDepth - 1
currentPosition <- proceedFunc currentPosition
result
static member GetBraceMatchingResult(sourceText, fileName, options, position) = async {
let isPositionInRange(range) =
let span = CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, range)
span.Start <= position && position <= span.End
let! matchedBraces = FSharpChecker.Instance.MatchBracesAlternate(fileName, sourceText.ToString(), options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FSharpChecker.Instance [](start = 28, length = 23)

Is FSharpChecker an immutable type? If not and it contains mutable state consider passing this as a parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static helper methods in service.fsi for parsing/type checking.


return matchedBraces |> Seq.tryFind(fun(left, right) -> isPositionInRange(left) || isPositionInRange(right))
}

interface IBraceMatcher with
member this.FindBracesAsync(document: Document, position: int, cancellationToken: CancellationToken): Task<Nullable<BraceMatchingResult>> =
document.GetTextAsync(cancellationToken).ContinueWith(
fun (sourceTextTask: Task<SourceText>) ->
if sourceTextTask.Status = TaskStatus.RanToCompletion then
try match getBraceMatchingResult(sourceTextTask.Result, Some(document.Name), [], position, cancellationToken) with
| None -> Nullable()
| Some(braceMatchingResult) -> Nullable(braceMatchingResult)
with ex ->
Assert.Exception(ex)
reraise()
else
raise(sourceTextTask.Exception.GetBaseException())
, cancellationToken)

// Helper function to proxy Roslyn types to tests
static member FindMatchingBrace(sourceText: SourceText, fileName: Option<string>, defines: string list, position: int, cancellationToken: CancellationToken) : Option<int> =
match getBraceMatchingResult(sourceText, fileName, defines, position, cancellationToken) with
| None -> None
| Some(braceMatchingResult) ->
if braceMatchingResult.LeftSpan.Start = position then
Some(braceMatchingResult.RightSpan.Start)
else if braceMatchingResult.RightSpan.Start = position then
Some(braceMatchingResult.LeftSpan.Start)
else
None
member this.FindBracesAsync(document, position, cancellationToken) =
let computation = async {
let options = CommonRoslynHelpers.GetFSharpProjectOptionsForRoslynProject(document.Project)
let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask
let! result = FSharpBraceMatchingService.GetBraceMatchingResult(sourceText, document.Name, options, position)

return match result with
| None -> Nullable()
| Some(left, right) ->
Nullable(BraceMatchingResult(
CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, left),
CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, right)))
}

Async.StartAsTask(computation, TaskCreationOptions.None, cancellationToken).ContinueWith(fun(task: Task<Nullable<BraceMatchingResult>>) ->
if task.Status = TaskStatus.RanToCompletion then
task.Result
else
Assert.Exception(task.Exception.GetBaseException())
raise(task.Exception.GetBaseException())
, cancellationToken)
66 changes: 66 additions & 0 deletions vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.VisualStudio.FSharp.Editor

open System
open System.Composition
open System.Collections.Concurrent
open System.Collections.Generic
open System.Threading
open System.Threading.Tasks
open System.Linq

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Classification
open Microsoft.CodeAnalysis.Editor
open Microsoft.CodeAnalysis.Editor.Implementation.Debugging
open Microsoft.CodeAnalysis.Editor.Shared.Utilities
open Microsoft.CodeAnalysis.Formatting
open Microsoft.CodeAnalysis.Host.Mef
open Microsoft.CodeAnalysis.Text

open Microsoft.VisualStudio.FSharp.LanguageService
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Text.Tagging

open Microsoft.FSharp.Compiler.Parser
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Range

[<Shared>]
[<ExportLanguageService(typeof<IBreakpointResolutionService>, FSharpCommonConstants.FSharpLanguageName)>]
type internal FSharpBreakpointResolutionService() =

static member GetBreakpointLocation(sourceText: SourceText, fileName: string, textSpan: TextSpan, options: FSharpProjectOptions) = async {
let! parseResults = FSharpChecker.Instance.ParseFileInProject(fileName, sourceText.ToString(), options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let! parseResults = FSharpChecker.Instance.ParseFileInProject(fileName, sourceText.ToString(), options) [](start = 6, length = 105)

This is going to cause a large allocation every time due to the ToString. Are the results of parsing cached anywhere based on a SourceText value? For example if parsing a file with the same checksum just return previous parse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I don't see it being done on SourceText, just a memory allocation. I'll need to look into caching on our side if there was a performance issue in UX.

let textLine = sourceText.Lines.GetLineFromPosition(textSpan.Start)

let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based
let textColumnNumber = textSpan.Start - textLine.Start

return parseResults.ValidateBreakpointLocation(mkPos textLineNumber textColumnNumber)
}

interface IBreakpointResolutionService with
member this.ResolveBreakpointAsync(document: Document, textSpan: TextSpan, cancellationToken: CancellationToken): Task<BreakpointResolutionResult> =
let computation = async {
let options = CommonRoslynHelpers.GetFSharpProjectOptionsForRoslynProject(document.Project)
let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask
let! location = FSharpBreakpointResolutionService.GetBreakpointLocation(sourceText, document.Name, textSpan, options)

return match location with
| None -> null
| Some(range) -> BreakpointResolutionResult.CreateSpanResult(document, CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, range))
}

Async.StartAsTask(computation, TaskCreationOptions.None, cancellationToken).ContinueWith(fun(task: Task<BreakpointResolutionResult>) ->
if task.Status = TaskStatus.RanToCompletion then
task.Result
else
Assert.Exception(task.Exception.GetBaseException())
raise(task.Exception.GetBaseException())
, cancellationToken)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper seems to be used a lot. Factor it out into a separate function?


// FSROSLYNTODO: enable placing breakpoints by when user suplies fully-qualified function names
member this.ResolveBreakpointsAsync(_, _, _): Task<IEnumerable<BreakpointResolutionResult>> =
Task.FromResult(Enumerable.Empty<BreakpointResolutionResult>())