Skip to content

Commit

Permalink
Merge pull request #1428 from otawfik-ms/roslyn-debugging
Browse files Browse the repository at this point in the history
Enabled debugging services in Roslyn branch
  • Loading branch information
Omar Tawfik committed Aug 16, 2016
2 parents 21d2b38 + e209336 commit 910611c
Show file tree
Hide file tree
Showing 14 changed files with 516 additions and 202 deletions.
128 changes: 24 additions & 104 deletions vsintegration/src/FSharp.Editor/BraceMatchingService.fs
Expand Up @@ -3,117 +3,37 @@
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)

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(CommonRoslynHelpers.GetCompletedTaskResult, cancellationToken)
61 changes: 61 additions & 0 deletions vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs
@@ -0,0 +1,61 @@
// 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)
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(CommonRoslynHelpers.GetCompletedTaskResult, cancellationToken)

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

0 comments on commit 910611c

Please sign in to comment.