diff --git a/fcs/FSharp.Compiler.Service.netstandard/FSharp.Compiler.Service.netstandard.fsproj b/fcs/FSharp.Compiler.Service.netstandard/FSharp.Compiler.Service.netstandard.fsproj index eb20755c318..cbf108a68e4 100644 --- a/fcs/FSharp.Compiler.Service.netstandard/FSharp.Compiler.Service.netstandard.fsproj +++ b/fcs/FSharp.Compiler.Service.netstandard/FSharp.Compiler.Service.netstandard.fsproj @@ -607,6 +607,12 @@ Service/ExternalSymbol.fs + + + Service/QuickParse.fsi + + + Service/QuickParse.fs Service/service.fsi diff --git a/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj b/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj index 07d9840cde2..fa10b3664ea 100644 --- a/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj +++ b/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj @@ -583,6 +583,12 @@ Service/ExternalSymbol.fs + + + Service/QuickParse.fsi + + + Service/QuickParse.fs Service/service.fsi diff --git a/src/buildfromsource/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj b/src/buildfromsource/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj index 1cc973ee6b2..aeddec6f538 100644 --- a/src/buildfromsource/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj +++ b/src/buildfromsource/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj @@ -564,6 +564,12 @@ Service/ExternalSymbol.fs + + + Service/QuickParse.fsi + + + Service/QuickParse.fs Service/service.fsi diff --git a/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj b/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj index acfb28e533b..ca673baf854 100644 --- a/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj +++ b/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj @@ -600,6 +600,12 @@ Service/ExternalSymbol.fs + + Service/QuickParse.fsi + + + Service/QuickParse.fs + Service/service.fsi diff --git a/vsintegration/src/FSharp.LanguageService/QuickParse.fs b/src/fsharp/vs/QuickParse.fs similarity index 64% rename from vsintegration/src/FSharp.LanguageService/QuickParse.fs rename to src/fsharp/vs/QuickParse.fs index 3dcb71b968c..1530759307b 100644 --- a/vsintegration/src/FSharp.LanguageService/QuickParse.fs +++ b/src/fsharp/vs/QuickParse.fs @@ -1,11 +1,27 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. -namespace Microsoft.VisualStudio.FSharp.LanguageService +namespace Microsoft.FSharp.Compiler open System -open System.Globalization open Microsoft.FSharp.Compiler.SourceCodeServices +/// Qualified long name. +type PartialLongName = + { /// Qualifying idents, prior to the last dot, not including the last part. + QualifyingIdents: string list + + /// Last part of long ident. + PartialIdent: string + + /// The column number at the end of full partial name. + EndColumn: int + + /// Position of the last dot. + LastDotPos: int option } + + /// Empty patial long name. + static member Empty(endColumn: int) = { QualifyingIdents = []; PartialIdent = ""; EndColumn = endColumn; LastDotPos = None } + /// Methods for cheaply and innacurately parsing F#. /// /// These methods are very old and are mostly to do with extracting "long identifier islands" @@ -22,15 +38,15 @@ open Microsoft.FSharp.Compiler.SourceCodeServices /// It's also surprising how hard even the job of getting long identifier islands can be. For example the code /// below is inaccurate for long identifier chains involving ``...`` identifiers. And there are special cases /// for active pattern names and so on. -module internal QuickParse = - open Microsoft.FSharp.Compiler.SourceCodeServices.PrettyNaming - - let magicalAdjustmentConstant = 1 // 0 puts us immediately *before* the last character; 1 puts us after the last character +module QuickParse = + open PrettyNaming + /// Puts us after the last character. + let MagicalAdjustmentConstant = 1 // Adjusts the token tag for the given identifier // - if we're inside active pattern name (at the bar), correct the token TAG to be an identifier - let CorrectIdentifierToken (s:string) tokenTag = - if s.EndsWith "|" then Microsoft.FSharp.Compiler.Parser.tagOfToken (Microsoft.FSharp.Compiler.Parser.token.IDENT s) + let CorrectIdentifierToken (tokenText: string) (tokenTag: int) = + if tokenText.EndsWith "|" then Microsoft.FSharp.Compiler.Parser.tagOfToken (Microsoft.FSharp.Compiler.Parser.token.IDENT tokenText) else tokenTag let rec isValidStrippedName (name:string) idx = @@ -42,7 +58,7 @@ module internal QuickParse = // Extracts the 'core' part without surrounding bars and checks whether it contains some identifier // (Note, this doesn't have to be precise, because this is checked by backround compiler, // but it has to be good enough to distinguish operators and active pattern names) - let private isValidActivePatternName (name:string) = + let private isValidActivePatternName (name: string) = // Strip the surrounding bars (e.g. from "|xyz|_|") to get "xyz" match name.StartsWith("|", System.StringComparison.Ordinal), @@ -52,40 +68,17 @@ module internal QuickParse = | true, _, true when name.Length > 2 -> isValidStrippedName (name.Substring(1, name.Length - 2)) 0 | _ -> false - /// Given a string and a position in that string, find an identifier as - /// expected by `GotoDefinition`. This will work when the cursor is - /// immediately before the identifier, within the identifier, or immediately - /// after the identifier. - /// - /// 'tolerateJustAfter' indicates that we tolerate being one character after the identifier, used - /// for goto-definition - - /// In general, only identifiers composed from upper/lower letters and '.' are supported, but there - /// are a couple of explicitly handled exceptions to allow some common scenarios: - /// - When the name contains only letters and '|' symbol, it may be an active pattern, so we - /// treat it as a valid identifier - e.g. let ( |Identitiy| ) a = a - /// (but other identifiers that include '|' are not allowed - e.g. '||' operator) - /// - It searches for double tick (``) to see if the identifier could be something like ``a b`` - - /// REVIEW: Also support, e.g., operators, performing the necessary mangling. - /// (i.e., I would like that the name returned here can be passed as-is - /// (post `.`-chopping) to `GetDeclarationLocation.) - - /// In addition, return the position where a `.` would go if we were making - /// a call to `DeclItemsForNamesAtPosition` for intellisense. This will - /// allow us to use find the correct qualified items rather than resorting - /// to the more expensive and less accurate environment lookup. - let GetCompleteIdentifierIslandImpl (s : string) (p : int) : (string*int*bool) option = - if p < 0 || isNull s || p >= s.Length then None + let GetCompleteIdentifierIslandImpl (lineStr: string) (index: int) : (string * int * bool) option = + if index < 0 || isNull lineStr || index >= lineStr.Length then None else let fixup = match () with // at a valid position, on a valid character - | _ when (p < s.Length) && (s.[p] = '|' || IsIdentifierPartCharacter s.[p]) -> Some p + | _ when (index < lineStr.Length) && (lineStr.[index] = '|' || IsIdentifierPartCharacter lineStr.[index]) -> Some index | _ -> None // not on a word or '.' - let (|Char|_|) p = if p >=0 && p < s.Length then Some(s.[p]) else None + let (|Char|_|) p = if p >=0 && p < lineStr.Length then Some(lineStr.[p]) else None let (|IsLongIdentifierPartChar|_|) c = if IsLongIdentifierPartCharacter c then Some () else None let (|IsIdentifierPartChar|_|) c = if IsIdentifierPartCharacter c then Some () else None @@ -105,7 +98,7 @@ module internal QuickParse = let tickColsOpt = let rec walkOutsideBackticks i = - if i >= s.Length then None + if i >= lineStr.Length then None else match i, i + 1 with | Char '`', Char '`' -> @@ -113,19 +106,19 @@ module internal QuickParse = // if pos = i then it will be included in backticked range ($``identifier``) walkInsideBackticks (i + 2) i | _, _ -> - if i >= p then None + if i >= index then None else // we still not reached position p - continue walking walkOutsideBackticks (i + 1) and walkInsideBackticks i start = - if i >= s.Length then None // non-closed backticks + if i >= lineStr.Length then None // non-closed backticks else match i, i + 1 with | Char '`', Char '`' -> // found closing pair of backticks // if target position is between start and current pos + 1 (entire range of escaped identifier including backticks) - return success // else climb outside and continue walking - if p >= start && p < (i + 2) then Some (start, i) + if index >= start && index < (i + 2) then Some (start, i) else walkOutsideBackticks (i + 2) | _, _ -> walkInsideBackticks (i + 1) start @@ -134,43 +127,66 @@ module internal QuickParse = match tickColsOpt with | Some (prevTickTick, idxTickTick) -> // inside ``identifier`` (which can contain any characters!) so we try returning its location - let pos = idxTickTick + 1 + magicalAdjustmentConstant - let ident = s.Substring(prevTickTick, idxTickTick - prevTickTick + 2) + let pos = idxTickTick + 1 + MagicalAdjustmentConstant + let ident = lineStr.Substring(prevTickTick, idxTickTick - prevTickTick + 2) Some(ident, pos, true) | _ -> // find location of an ordinary identifier fixup |> Option.bind (fun p -> let l = searchLeft p let r = searchRight p - let ident = s.Substring (l, r - l + 1) + let ident = lineStr.Substring (l, r - l + 1) if ident.IndexOf('|') <> -1 && not(isValidActivePatternName(ident)) then None else - let pos = r + magicalAdjustmentConstant + let pos = r + MagicalAdjustmentConstant Some(ident, pos, false) ) - let GetCompleteIdentifierIsland (tolerateJustAfter:bool) (s : string) (p : int) : (string*int*bool) option = - if String.IsNullOrEmpty s then None + /// Given a string and a position in that string, find an identifier as + /// expected by `GotoDefinition`. This will work when the cursor is + /// immediately before the identifier, within the identifier, or immediately + /// after the identifier. + /// + /// 'tolerateJustAfter' indicates that we tolerate being one character after the identifier, used + /// for goto-definition + /// + /// In general, only identifiers composed from upper/lower letters and '.' are supported, but there + /// are a couple of explicitly handled exceptions to allow some common scenarios: + /// - When the name contains only letters and '|' symbol, it may be an active pattern, so we + /// treat it as a valid identifier - e.g. let ( |Identitiy| ) a = a + /// (but other identifiers that include '|' are not allowed - e.g. '||' operator) + /// - It searches for double tick (``) to see if the identifier could be something like ``a b`` + /// + /// REVIEW: Also support, e.g., operators, performing the necessary mangling. + /// (i.e., I would like that the name returned here can be passed as-is + /// (post `.`-chopping) to `GetDeclarationLocation.) + /// + /// In addition, return the position where a `.` would go if we were making + /// a call to `DeclItemsForNamesAtPosition` for intellisense. This will + /// allow us to use find the correct qualified items rather than resorting + /// to the more expensive and less accurate environment lookup. + let GetCompleteIdentifierIsland (tolerateJustAfter: bool) (lineStr: string) (index: int) : (string * int * bool) option = + if String.IsNullOrEmpty lineStr then None else - let directResult = GetCompleteIdentifierIslandImpl s p + let directResult = GetCompleteIdentifierIslandImpl lineStr index if tolerateJustAfter && directResult = None then - GetCompleteIdentifierIslandImpl s (p-1) + GetCompleteIdentifierIslandImpl lineStr (index - 1) else directResult - let private defaultName = [],"" + let private defaultName = [], "" /// Get the partial long name of the identifier to the left of index. - let GetPartialLongName(line:string,index) = - if isNull line then defaultName + let GetPartialLongName(lineStr: string, index: int) = + if isNull lineStr then defaultName elif index < 0 then defaultName - elif index >= line.Length then defaultName + elif index >= lineStr.Length then defaultName else - let IsIdentifierPartCharacter pos = IsIdentifierPartCharacter line.[pos] - let IsLongIdentifierPartCharacter pos = IsLongIdentifierPartCharacter line.[pos] - let IsDot pos = line.[pos] = '.' + let IsIdentifierPartCharacter pos = IsIdentifierPartCharacter lineStr.[pos] + let IsLongIdentifierPartCharacter pos = IsLongIdentifierPartCharacter lineStr.[pos] + let IsDot pos = lineStr.[pos] = '.' let rec InLeadingIdentifier(pos,right,(prior,residue)) = - let PushName() = ((line.Substring(pos+1,right-pos-1))::prior),residue + let PushName() = ((lineStr.Substring(pos+1,right-pos-1))::prior),residue if pos < 0 then PushName() elif IsIdentifierPartCharacter pos then InLeadingIdentifier(pos-1,right,(prior,residue)) elif IsDot pos then InLeadingIdentifier(pos-1,pos,PushName()) @@ -178,132 +194,150 @@ module internal QuickParse = let rec InName(pos,startResidue,right) = let NameAndResidue() = - [line.Substring(pos+1,startResidue-pos-1)],(line.Substring(startResidue+1,right-startResidue)) - if pos < 0 then [line.Substring(pos+1,startResidue-pos-1)],(line.Substring(startResidue+1,right-startResidue)) + [lineStr.Substring(pos+1,startResidue-pos-1)],(lineStr.Substring(startResidue+1,right-startResidue)) + if pos < 0 then [lineStr.Substring(pos+1,startResidue-pos-1)],(lineStr.Substring(startResidue+1,right-startResidue)) elif IsIdentifierPartCharacter pos then InName(pos-1,startResidue,right) elif IsDot pos then InLeadingIdentifier(pos-1,pos,NameAndResidue()) else NameAndResidue() let rec InResidue(pos,right) = - if pos < 0 then [],line.Substring(pos+1,right-pos) + if pos < 0 then [],lineStr.Substring(pos+1,right-pos) elif IsDot pos then InName(pos-1,pos,right) elif IsLongIdentifierPartCharacter pos then InResidue(pos-1, right) - else [],line.Substring(pos+1,right-pos) + else [],lineStr.Substring(pos+1,right-pos) let result = InResidue(index,index) result type private EatCommentCallContext = - | SkipWhiteSpaces of string * string list * bool - | StartIdentifier of string list * bool + | SkipWhiteSpaces of ident: string * current: string list * throwAwayNext: bool + | StartIdentifier of current: string list * throwAway: bool /// Get the partial long name of the identifier to the left of index. - /// For example, for `System.DateTime.Now` it returns ([|"System"; "DateTime"|], "Now"). - let GetPartialLongNameEx(line:string,index) : (string list * string) = - if isNull line then defaultName - elif index < 0 then defaultName - elif index >= line.Length then defaultName + /// For example, for `System.DateTime.Now` it returns PartialLongName ([|"System"; "DateTime"|], "Now", Some 32), where "32" pos of the last dot. + let GetPartialLongNameEx(lineStr: string, index: int) : PartialLongName = + if isNull lineStr then PartialLongName.Empty(index) + elif index < 0 then PartialLongName.Empty(index) + elif index >= lineStr.Length then PartialLongName.Empty(index) else - let IsIdentifierPartCharacter pos = IsIdentifierPartCharacter line.[pos] + let IsIdentifierPartCharacter pos = IsIdentifierPartCharacter lineStr.[pos] let IsIdentifierStartCharacter pos = IsIdentifierPartCharacter pos - let IsDot pos = line.[pos] = '.' - let IsTick pos = line.[pos] = '`' - let IsEndOfComment pos = pos < index - 1 && line.[pos] = '*' && line.[pos + 1] = ')' - let IsStartOfComment pos = pos < index - 1 && line.[pos] = '(' && line.[pos + 1] = '*' - let IsWhitespace pos = Char.IsWhiteSpace(line.[pos]) + let IsDot pos = lineStr.[pos] = '.' + let IsTick pos = lineStr.[pos] = '`' + let IsEndOfComment pos = pos < index - 1 && lineStr.[pos] = '*' && lineStr.[pos + 1] = ')' + let IsStartOfComment pos = pos < index - 1 && lineStr.[pos] = '(' && lineStr.[pos + 1] = '*' + let IsWhitespace pos = Char.IsWhiteSpace(lineStr.[pos]) - let rec SkipWhitespaceBeforeDotIdentifier(pos, ident, current,throwAwayNext) = - if pos > index then defaultName // we're in whitespace after an identifier, if this is where the cursor is, there is no PLID here - elif IsWhitespace pos then SkipWhitespaceBeforeDotIdentifier(pos+1,ident,current,throwAwayNext) - elif IsDot pos then AtStartOfIdentifier(pos+1,ident::current,throwAwayNext) - elif IsStartOfComment pos then EatComment(1, pos + 1, EatCommentCallContext.SkipWhiteSpaces(ident, current, throwAwayNext)) - else AtStartOfIdentifier(pos,[],false) // Throw away what we have and start over. + let rec SkipWhitespaceBeforeDotIdentifier(pos, ident, current, throwAwayNext, lastDotPos) = + if pos > index then PartialLongName.Empty(index) // we're in whitespace after an identifier, if this is where the cursor is, there is no PLID here + elif IsWhitespace pos then SkipWhitespaceBeforeDotIdentifier(pos+1,ident,current,throwAwayNext,lastDotPos) + elif IsDot pos then AtStartOfIdentifier(pos+1,ident::current,throwAwayNext, Some pos) + elif IsStartOfComment pos then EatComment(1, pos + 1, EatCommentCallContext.SkipWhiteSpaces(ident, current, throwAwayNext), lastDotPos) + else AtStartOfIdentifier(pos,[],false,None) // Throw away what we have and start over. - and EatComment (nesting, pos, callContext) = - if pos > index then defaultName else + and EatComment (nesting, pos, callContext,lastDotPos) = + if pos > index then PartialLongName.Empty(index) else if IsStartOfComment pos then // track balance of closing '*)' - EatComment(nesting + 1, pos + 2, callContext) + EatComment(nesting + 1, pos + 2, callContext,lastDotPos) else if IsEndOfComment pos then if nesting = 1 then // all right, we are at the end of comment, jump outside match callContext with | EatCommentCallContext.SkipWhiteSpaces(ident, current, throwAway) -> - SkipWhitespaceBeforeDotIdentifier(pos + 2, ident, current, throwAway) + SkipWhitespaceBeforeDotIdentifier(pos + 2, ident, current, throwAway,lastDotPos) | EatCommentCallContext.StartIdentifier(current, throwAway) -> - AtStartOfIdentifier(pos + 2, current, throwAway) + AtStartOfIdentifier(pos + 2, current, throwAway,lastDotPos) else // reduce level of nesting and continue - EatComment(nesting - 1, pos + 2, callContext) + EatComment(nesting - 1, pos + 2, callContext, lastDotPos) else // eat next char - EatComment(nesting, pos + 1, callContext) + EatComment(nesting, pos + 1, callContext, lastDotPos) - and InUnquotedIdentifier(left:int,pos:int,current,throwAwayNext) = + and InUnquotedIdentifier(left:int,pos:int,current,throwAwayNext,lastDotPos) = if pos > index then - if throwAwayNext then defaultName else current,line.Substring(left,pos-left) + if throwAwayNext then + PartialLongName.Empty(index) + else + { QualifyingIdents = current + PartialIdent = lineStr.Substring(left,pos-left) + EndColumn = index + LastDotPos = lastDotPos } else - if IsIdentifierPartCharacter pos then InUnquotedIdentifier(left,pos+1,current,throwAwayNext) + if IsIdentifierPartCharacter pos then InUnquotedIdentifier(left,pos+1,current,throwAwayNext,lastDotPos) elif IsDot pos then - let ident = line.Substring(left,pos-left) - AtStartOfIdentifier(pos+1,ident::current,throwAwayNext) + let ident = lineStr.Substring(left,pos-left) + AtStartOfIdentifier(pos+1,ident::current,throwAwayNext, Some pos) elif IsWhitespace pos || IsStartOfComment pos then - let ident = line.Substring(left,pos-left) - SkipWhitespaceBeforeDotIdentifier(pos, ident, current,throwAwayNext) - else AtStartOfIdentifier(pos,[],false) // Throw away what we have and start over. + let ident = lineStr.Substring(left,pos-left) + SkipWhitespaceBeforeDotIdentifier(pos, ident, current, throwAwayNext, lastDotPos) + else AtStartOfIdentifier(pos,[],false,None) // Throw away what we have and start over. - and InQuotedIdentifier(left:int,pos:int, current,throwAwayNext) = + and InQuotedIdentifier(left:int,pos:int, current,throwAwayNext,lastDotPos) = if pos > index then - if throwAwayNext then defaultName else current,line.Substring(left,pos-left) + if throwAwayNext then + PartialLongName.Empty(index) + else + { QualifyingIdents = current + PartialIdent = lineStr.Substring(left,pos-left) + EndColumn = index + LastDotPos = lastDotPos } else - let remainingLength = line.Length - pos + let remainingLength = lineStr.Length - pos if IsTick pos && remainingLength > 1 && IsTick(pos+1) then - let ident = line.Substring(left, pos-left) - SkipWhitespaceBeforeDotIdentifier(pos+2,ident,current,throwAwayNext) - else InQuotedIdentifier(left,pos+1,current,throwAwayNext) + let ident = lineStr.Substring(left, pos-left) + SkipWhitespaceBeforeDotIdentifier(pos+2,ident,current,throwAwayNext,lastDotPos) + else InQuotedIdentifier(left,pos+1,current,throwAwayNext,lastDotPos) - and AtStartOfIdentifier(pos:int, current, throwAwayNext) = + and AtStartOfIdentifier(pos:int, current, throwAwayNext, lastDotPos: int option) = if pos > index then - if throwAwayNext then defaultName else current,"" + if throwAwayNext then + PartialLongName.Empty(index) + else + { QualifyingIdents = current + PartialIdent = "" + EndColumn = index + LastDotPos = lastDotPos } else - if IsWhitespace pos then AtStartOfIdentifier(pos+1,current,throwAwayNext) + if IsWhitespace pos then AtStartOfIdentifier(pos+1,current,throwAwayNext, lastDotPos) else - let remainingLength = line.Length - pos - if IsTick pos && remainingLength > 1 && IsTick(pos+1) then InQuotedIdentifier(pos+2,pos+2,current,throwAwayNext) - elif IsStartOfComment pos then EatComment(1, pos + 1, EatCommentCallContext.StartIdentifier(current, throwAwayNext)) - elif IsIdentifierStartCharacter pos then InUnquotedIdentifier(pos,pos+1,current,throwAwayNext) + let remainingLength = lineStr.Length - pos + if IsTick pos && remainingLength > 1 && IsTick(pos+1) then InQuotedIdentifier(pos+2,pos+2,current,throwAwayNext,lastDotPos) + elif IsStartOfComment pos then EatComment(1, pos + 1, EatCommentCallContext.StartIdentifier(current, throwAwayNext), lastDotPos) + elif IsIdentifierStartCharacter pos then InUnquotedIdentifier(pos,pos+1,current,throwAwayNext,lastDotPos) elif IsDot pos then if pos = 0 then // dot on first char of line, currently treat it like empty identifier to the left - AtStartOfIdentifier(pos+1,""::current,throwAwayNext) + AtStartOfIdentifier(pos+1,""::current,throwAwayNext, Some pos) elif not (pos > 0 && (IsIdentifierPartCharacter(pos-1) || IsWhitespace(pos-1))) then // it's not dots as part.of.a.long.ident, it's e.g. the range operator (..), or some other multi-char operator ending in dot - if line.[pos-1] = ')' then + if lineStr.[pos-1] = ')' then // one very problematic case is someCall(args).Name // without special logic, we will decide that ). is an operator and parse Name as the plid // but in fact this is an expression tail, and we don't want a plid, rather we need to use expression typings at that location // so be sure not to treat the name here as a plid - AtStartOfIdentifier(pos+1,[],true) // Throw away what we have, and the next apparent plid, and start over. + AtStartOfIdentifier(pos+1,[],true,None) // Throw away what we have, and the next apparent plid, and start over. else - AtStartOfIdentifier(pos+1,[],false) // Throw away what we have and start over. + AtStartOfIdentifier(pos+1,[],false,None) // Throw away what we have and start over. else - AtStartOfIdentifier(pos+1,""::current,throwAwayNext) - else AtStartOfIdentifier(pos+1,[],throwAwayNext) - let plid, residue = AtStartOfIdentifier(0,[],false) - let plid = List.rev plid - match plid with - | s::_rest when s.Length > 0 && Char.IsDigit(s.[0]) -> defaultName // "2.0" is not a longId (this might not be right for ``2.0`` but good enough for common case) - | _ -> plid, residue + AtStartOfIdentifier(pos+1,""::current,throwAwayNext, Some pos) + else AtStartOfIdentifier(pos+1,[],throwAwayNext, None) + let partialLongName = AtStartOfIdentifier(0, [], false, None) + + match List.rev partialLongName.QualifyingIdents with + | s :: _ when s.Length > 0 && Char.IsDigit(s.[0]) -> PartialLongName.Empty(index) // "2.0" is not a longId (this might not be right for ``2.0`` but good enough for common case) + | plid -> { partialLongName with QualifyingIdents = plid } - let TokenNameEquals (tokenInfo : FSharpTokenInfo) token2 = + let TokenNameEquals (tokenInfo: FSharpTokenInfo) (token2: string) = String.Compare(tokenInfo .TokenName, token2, StringComparison.OrdinalIgnoreCase) = 0 // The prefix of the sequence of token names to look for in TestMemberOrOverrideDeclaration, in reverse order let private expected = [ [|"dot"|]; [|"ident"|]; [|"member"; "override"|] ] /// Tests whether the user is typing something like "member x." or "override (*comment*) x." - let internal TestMemberOrOverrideDeclaration (tokens:FSharpTokenInfo[]) = + let TestMemberOrOverrideDeclaration (tokens: FSharpTokenInfo[]) = let filteredReversed = tokens |> Array.filter (fun tok -> diff --git a/src/fsharp/vs/QuickParse.fsi b/src/fsharp/vs/QuickParse.fsi new file mode 100644 index 00000000000..78bfa2b39f3 --- /dev/null +++ b/src/fsharp/vs/QuickParse.fsi @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.FSharp.Compiler + +open System +open Microsoft.FSharp.Compiler.SourceCodeServices + +/// Qualified long name. +#if COMPILER_PUBLIC_API +type PartialLongName = +#else +type internal PartialLongName = +#endif + { /// Qualifying idents, prior to the last dot, not including the last part. + QualifyingIdents: string list + + /// Last part of long ident. + PartialIdent: string + + /// The column number at the end of full partial name. + EndColumn: int + + /// Position of the last dot. + LastDotPos: int option } + + /// Empty patial long name. + static member Empty: endColumn: int -> PartialLongName + +/// Methods for cheaply and innacurately parsing F#. +/// +/// These methods are very old and are mostly to do with extracting "long identifier islands" +/// A.B.C +/// from F# source code, an approach taken from pre-F# VS samples for implementing intelliense. +/// +/// This code should really no longer be needed since the language service has access to +/// parsed F# source code ASTs. However, the long identifiers are still passed back to GetDeclarations and friends in the +/// F# Compiler Service and it's annoyingly hard to remove their use completely. +/// +/// In general it is unlikely much progress will be made by fixing this code - it will be better to +/// extract more information from the F# ASTs. +/// +/// It's also surprising how hard even the job of getting long identifier islands can be. For example the code +/// below is inaccurate for long identifier chains involving ``...`` identifiers. And there are special cases +/// for active pattern names and so on. +#if COMPILER_PUBLIC_API +module QuickParse = +#else +module internal QuickParse = +#endif + /// Puts us after the last character. + val MagicalAdjustmentConstant : int + + // Adjusts the token tag for the given identifier + // - if we're inside active pattern name (at the bar), correct the token TAG to be an identifier + val CorrectIdentifierToken : tokenText: string -> tokenTag: int -> int + + /// Given a string and a position in that string, find an identifier as + /// expected by `GotoDefinition`. This will work when the cursor is + /// immediately before the identifier, within the identifier, or immediately + /// after the identifier. + /// + /// 'tolerateJustAfter' indicates that we tolerate being one character after the identifier, used + /// for goto-definition + /// + /// In general, only identifiers composed from upper/lower letters and '.' are supported, but there + /// are a couple of explicitly handled exceptions to allow some common scenarios: + /// - When the name contains only letters and '|' symbol, it may be an active pattern, so we + /// treat it as a valid identifier - e.g. let ( |Identitiy| ) a = a + /// (but other identifiers that include '|' are not allowed - e.g. '||' operator) + /// - It searches for double tick (``) to see if the identifier could be something like ``a b`` + /// + /// REVIEW: Also support, e.g., operators, performing the necessary mangling. + /// (i.e., I would like that the name returned here can be passed as-is + /// (post `.`-chopping) to `GetDeclarationLocation.) + /// + /// In addition, return the position where a `.` would go if we were making + /// a call to `DeclItemsForNamesAtPosition` for intellisense. This will + /// allow us to use find the correct qualified items rather than resorting + /// to the more expensive and less accurate environment lookup. + val GetCompleteIdentifierIsland : tolerateJustAfter: bool -> tokenText: string -> index: int -> (string * int * bool) option + + /// Get the partial long name of the identifier to the left of index. + val GetPartialLongName : lineStr: string * index: int -> (string list * string) + + /// Get the partial long name of the identifier to the left of index. + /// For example, for `System.DateTime.Now` it returns PartialLongName ([|"System"; "DateTime"|], "Now", Some 32), where "32" pos of the last dot. + val GetPartialLongNameEx : lineStr: string * index: int -> PartialLongName + + /// Tests whether the user is typing something like "member x." or "override (*comment*) x." + val TestMemberOrOverrideDeclaration : tokens: FSharpTokenInfo[] -> bool \ No newline at end of file diff --git a/src/fsharp/vs/service.fs b/src/fsharp/vs/service.fs index 5c2fc8f5445..d387ca9702a 100644 --- a/src/fsharp/vs/service.fs +++ b/src/fsharp/vs/service.fs @@ -140,7 +140,7 @@ type SemanticClassificationType = | TypeArgument | Operator | Disposable - + /// A TypeCheckInfo represents everything we get back from the typecheck of a file. /// It acts like an in-memory database about the file. /// It is effectively immutable and not updated: when we re-typecheck we just drop the previous @@ -216,7 +216,7 @@ type TypeCheckInfo | None -> true if contained then - match !bestAlmostIncludedSoFar with + match !bestAlmostIncludedSoFar with | Some (rightm:range,_,_) -> if posGt possm.End rightm.End || (posEq possm.End rightm.End && posGt possm.Start rightm.Start) then @@ -591,7 +591,7 @@ type TypeCheckInfo let DefaultCompletionItem item = CompletionItem None None item let getItem (x: ItemWithInst) = x.Item - let GetDeclaredItems (parseResultsOpt: FSharpParseFileResults option, lineStr: string, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, line, loc, + let GetDeclaredItems (parseResultsOpt: FSharpParseFileResults option, lineStr: string, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, isInRangeOperator, allSymbols: unit -> AssemblySymbol list) = // Are the last two chars (except whitespaces) = ".." @@ -612,18 +612,12 @@ type TypeCheckInfo // deals with cases when we have spaces between dot and\or identifier, like A . $ // if this is our case - then we need to locate end position of the name skipping whitespaces // this allows us to handle cases like: let x . $ = 1 - - // colAtEndOfNamesAndResidue is 1-based so at first we need to convert it to 0-based - // - // TODO: this code would be a lot simpler if we just passed in colAtEndOfNames in - // the first place. colAtEndOfNamesAndResidue serves no purpose. The cracking below is - // inaccurate and incomplete in any case since it only works on a single line. - match FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1) with + match lastDotPos |> Option.orElseWith (fun _ -> FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1)) with | Some p when lineStr.[p] = '.' -> match FindFirstNonWhitespacePosition lineStr (p - 1) with | Some colAtEndOfNames -> - let colAtEndOfNames = colAtEndOfNames + 1 // convert 0-based to 1-based - GetPreciseItemsFromNameResolution(line, colAtEndOfNames, Some(residue), filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck) + let colAtEndOfNames = colAtEndOfNames + 1 // convert 0-based to 1-based + GetPreciseItemsFromNameResolution(line, colAtEndOfNames, Some(residue), filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck) | None -> NameResResult.Empty | _ -> NameResResult.Empty @@ -647,7 +641,7 @@ type TypeCheckInfo match NameResolution.TryToResolveLongIdentAsType ncenv nenv m plid with | Some x -> Some x | None -> - match FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1) with + match lastDotPos |> Option.orElseWith (fun _ -> FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1)) with | Some p when lineStr.[p] = '.' -> match FindFirstNonWhitespacePosition lineStr (p - 1) with | Some colAtEndOfNames -> @@ -769,7 +763,7 @@ type TypeCheckInfo /// Get the auto-complete items at a particular location. let GetDeclItemsForNamesAtPosition(ctok: CompilationThreadToken, parseResultsOpt: FSharpParseFileResults option, origLongIdentOpt: string list option, - residueOpt:string option, line:int, lineStr:string, colAtEndOfNamesAndResidue, filterCtors, resolveOverloads, + residueOpt:string option, lastDotPos: int option, line:int, lineStr:string, colAtEndOfNamesAndResidue, filterCtors, resolveOverloads, getAllSymbols: unit -> AssemblySymbol list, hasTextChangedSinceLastTypecheck: (obj * range -> bool)) : (CompletionItem list * DisplayEnv * CompletionContext option * range) option = RequireCompilationThread ctok // the operations in this method need the reactor thread @@ -812,7 +806,7 @@ type TypeCheckInfo match GetClassOrRecordFieldsEnvironmentLookupResolutions(mkPos line loc, plid) |> toCompletionItems with | [],_,_ -> // no record fields found, return completion list as if we were outside any computation expression - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, line, loc, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck, false, fun() -> []) + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck, false, fun() -> []) | result -> Some(result) // Completion at ' { XXX = ... with ... } " @@ -835,7 +829,7 @@ type TypeCheckInfo let results = GetNamedParametersAndSettableFields endPos hasTextChangedSinceLastTypecheck let declaredItems = - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, line, loc, filterCtors, resolveOverloads, + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) match results with @@ -858,7 +852,7 @@ type TypeCheckInfo | _ -> declaredItems | Some(CompletionContext.AttributeApplication) -> - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) |> Option.map (fun (items, denv, m) -> items |> List.filter (fun cItem -> @@ -868,14 +862,14 @@ type TypeCheckInfo | _ -> false), denv, m) | Some(CompletionContext.OpenDeclaration) -> - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, hasTextChangedSinceLastTypecheck, false, getAllSymbols) |> Option.map (fun (items, denv, m) -> items |> List.filter (fun x -> match x.Item with Item.ModuleOrNamespaces _ -> true | _ -> false), denv, m) // Other completions | cc -> let isInRangeOperator = (match cc with Some (CompletionContext.RangeOperator) -> true | _ -> false) - GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, line, loc, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck, isInRangeOperator, getAllSymbols) + GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors,resolveOverloads, hasTextChangedSinceLastTypecheck, isInRangeOperator, getAllSymbols) res |> Option.map (fun (items, denv, m) -> items, denv, completionContext, m) @@ -902,11 +896,11 @@ type TypeCheckInfo false) /// Get the auto-complete items at a location - member __.GetDeclarations (ctok, parseResultsOpt, line, lineStr, colAtEndOfNamesAndResidue, qualifyingNames, partialName, getAllSymbols, hasTextChangedSinceLastTypecheck) = + member __.GetDeclarations (ctok, parseResultsOpt, line, lineStr, partialName, getAllSymbols, hasTextChangedSinceLastTypecheck) = let isInterfaceFile = SourceFileImpl.IsInterfaceFile mainInputFileName ErrorScope.Protect Range.range0 - (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, parseResultsOpt, Some qualifyingNames, Some partialName, line, lineStr, colAtEndOfNamesAndResidue, ResolveTypeNamesToCtors, ResolveOverloads.Yes, getAllSymbols, hasTextChangedSinceLastTypecheck) with + (fun () -> + match GetDeclItemsForNamesAtPosition(ctok, parseResultsOpt, Some partialName.QualifyingIdents, Some partialName.PartialIdent, partialName.LastDotPos, line, lineStr, partialName.EndColumn + 1, ResolveTypeNamesToCtors, ResolveOverloads.Yes, getAllSymbols, hasTextChangedSinceLastTypecheck) with | None -> FSharpDeclarationListInfo.Empty | Some (items, denv, ctx, m) -> let items = if isInterfaceFile then items |> List.filter (fun x -> IsValidSignatureFileItem x.Item) else items @@ -922,11 +916,11 @@ type TypeCheckInfo FSharpDeclarationListInfo.Error msg) /// Get the symbols for auto-complete items at a location - member __.GetDeclarationListSymbols (ctok, parseResultsOpt, line, lineStr, colAtEndOfNamesAndResidue, qualifyingNames, partialName, hasTextChangedSinceLastTypecheck) = + member __.GetDeclarationListSymbols (ctok, parseResultsOpt, line, lineStr, partialName, hasTextChangedSinceLastTypecheck) = let isInterfaceFile = SourceFileImpl.IsInterfaceFile mainInputFileName ErrorScope.Protect Range.range0 (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, parseResultsOpt, Some qualifyingNames, Some partialName, line, lineStr, colAtEndOfNamesAndResidue, ResolveTypeNamesToCtors, ResolveOverloads.Yes, (fun () -> []), hasTextChangedSinceLastTypecheck) with + match GetDeclItemsForNamesAtPosition(ctok, parseResultsOpt, Some partialName.QualifyingIdents, Some partialName.PartialIdent, partialName.LastDotPos, line, lineStr, partialName.EndColumn + 1, ResolveTypeNamesToCtors, ResolveOverloads.Yes, (fun () -> []), hasTextChangedSinceLastTypecheck) with | None -> List.Empty | Some (items, denv, _, m) -> let items = if isInterfaceFile then items |> List.filter (fun x -> IsValidSignatureFileItem x.Item) else items @@ -1028,7 +1022,7 @@ type TypeCheckInfo let Compute() = ErrorScope.Protect Range.range0 (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, None,Some(names),None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToCtors,ResolveOverloads.Yes,(fun() -> []),fun _ -> false) with + match GetDeclItemsForNamesAtPosition(ctok, None,Some(names),None,None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToCtors,ResolveOverloads.Yes,(fun() -> []),fun _ -> false) with | None -> FSharpToolTipText [] | Some(items, denv, _, m) -> FSharpToolTipText(items |> List.map (fun x -> FormatStructuredDescriptionOfItem false infoReader m denv x.ItemWithInst))) @@ -1048,7 +1042,7 @@ type TypeCheckInfo member __.GetF1Keyword (ctok, line, lineStr, colAtEndOfNames, names) : string option = ErrorScope.Protect Range.range0 (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, None, Some names, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.No,(fun() -> []), fun _ -> false) with // F1 Keywords do not distinguish between overloads + match GetDeclItemsForNamesAtPosition(ctok, None, Some names, None, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.No,(fun() -> []), fun _ -> false) with // F1 Keywords do not distinguish between overloads | None -> None | Some (items: CompletionItem list, _,_, _) -> match items with @@ -1080,7 +1074,7 @@ type TypeCheckInfo member __.GetMethods (ctok, line, lineStr, colAtEndOfNames, namesOpt) = ErrorScope.Protect Range.range0 (fun () -> - match GetDeclItemsForNamesAtPosition(ctok, None,namesOpt,None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToCtors,ResolveOverloads.No,(fun() -> []),fun _ -> false) with + match GetDeclItemsForNamesAtPosition(ctok, None,namesOpt,None,None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToCtors,ResolveOverloads.No,(fun() -> []),fun _ -> false) with | None -> FSharpMethodGroup("",[| |]) | Some (items, denv, _, m) -> FSharpMethodGroup.Create(infoReader, m, denv, items |> List.map (fun x -> x.ItemWithInst))) (fun msg -> @@ -1090,7 +1084,7 @@ type TypeCheckInfo member __.GetMethodsAsSymbols (ctok, line, lineStr, colAtEndOfNames, names) = ErrorScope.Protect Range.range0 (fun () -> - match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.No,(fun() -> []),fun _ -> false) with + match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, None,line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.No,(fun() -> []),fun _ -> false) with | None | Some ([],_,_,_) -> None | Some (items, denv, _, m) -> let allItems = items |> List.collect (fun item -> SymbolHelpers.FlattenItems g m item.Item) @@ -1104,7 +1098,7 @@ type TypeCheckInfo member scope.GetDeclarationLocation (ctok, line, lineStr, colAtEndOfNames, names, preferFlag) = ErrorScope.Protect Range.range0 (fun () -> - match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors,ResolveOverloads.Yes,(fun() -> []), fun _ -> false) with + match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors,ResolveOverloads.Yes,(fun() -> []), fun _ -> false) with | None | Some ([], _, _, _) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.Unknown "") | Some (item :: _, _, _, _) -> @@ -1209,7 +1203,7 @@ type TypeCheckInfo member scope.GetSymbolUseAtLocation (ctok, line, lineStr, colAtEndOfNames, names) = ErrorScope.Protect Range.range0 (fun () -> - match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.Yes,(fun() -> []), fun _ -> false) with + match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors, ResolveOverloads.Yes,(fun() -> []), fun _ -> false) with | None | Some ([], _, _, _) -> None | Some (item :: _, denv, _, m) -> let symbol = FSharpSymbol.Create(g, thisCcu, tcImports, item.Item) @@ -1885,16 +1879,16 @@ type FSharpCheckFileResults(filename: string, errors: FSharpErrorInfo[], scopeOp member info.HasFullTypeCheckInfo = details.IsSome /// Intellisense autocompletions - member info.GetDeclarationListInfo(parseResultsOpt, line, colAtEndOfNamesAndResidue, lineStr, qualifyingNames, partialName, getAllEntities, ?hasTextChangedSinceLastTypecheck, ?userOpName: string) = + member info.GetDeclarationListInfo(parseResultsOpt, line, lineStr, partialName, getAllEntities, ?hasTextChangedSinceLastTypecheck, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" let hasTextChangedSinceLastTypecheck = defaultArg hasTextChangedSinceLastTypecheck (fun _ -> false) reactorOp userOpName "GetDeclarations" FSharpDeclarationListInfo.Empty (fun ctok scope -> - scope.GetDeclarations(ctok, parseResultsOpt, line, lineStr, colAtEndOfNamesAndResidue, qualifyingNames, partialName, getAllEntities, hasTextChangedSinceLastTypecheck)) + scope.GetDeclarations(ctok, parseResultsOpt, line, lineStr, partialName, getAllEntities, hasTextChangedSinceLastTypecheck)) - member info.GetDeclarationListSymbols(parseResultsOpt, line, colAtEndOfNamesAndResidue, lineStr, qualifyingNames, partialName, ?hasTextChangedSinceLastTypecheck, ?userOpName: string) = + member info.GetDeclarationListSymbols(parseResultsOpt, line, lineStr, partialName, ?hasTextChangedSinceLastTypecheck, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" let hasTextChangedSinceLastTypecheck = defaultArg hasTextChangedSinceLastTypecheck (fun _ -> false) - reactorOp userOpName "GetDeclarationListSymbols" List.empty (fun ctok scope -> scope.GetDeclarationListSymbols(ctok, parseResultsOpt, line, lineStr, colAtEndOfNamesAndResidue, qualifyingNames, partialName, hasTextChangedSinceLastTypecheck)) + reactorOp userOpName "GetDeclarationListSymbols" List.empty (fun ctok scope -> scope.GetDeclarationListSymbols(ctok, parseResultsOpt, line, lineStr, partialName, hasTextChangedSinceLastTypecheck)) /// Resolve the names at the given location to give a data tip member info.GetStructuredToolTipText(line, colAtEndOfNames, lineStr, names, tokenTag, ?userOpName: string) = diff --git a/src/fsharp/vs/service.fsi b/src/fsharp/vs/service.fsi index 986b9a0e4c8..b274dac53fa 100755 --- a/src/fsharp/vs/service.fsi +++ b/src/fsharp/vs/service.fsi @@ -132,21 +132,23 @@ type internal FSharpCheckFileResults = /// 'record field' locations and r.h.s. of 'range' operator a..b /// /// The line number where the completion is happening - /// The column number at the end of the 'names' text - /// The long identifier to the left of the '.' - /// The residue of a partial long identifier to the right of the '.' - /// The residue of a partial long identifier to the right of the '.' + /// + /// Partial long name. QuickParse.GetPartialLongNameEx can be used to get it. + /// /// /// The text of the line where the completion is happening. This is only used to make a couple /// of adhoc corrections to completion accuracy (e.g. checking for "..") /// + /// + /// Function that returns all symbols from current and referenced assemblies. + /// /// /// If text has been used from a captured name resolution from the typecheck, then /// callback to the client to check if the text has changed. If it has, then give up /// and assume that we're going to repeat the operation later on. /// /// An optional string used for tracing compiler operations associated with this request. - member GetDeclarationListInfo : ParsedFileResultsOpt:FSharpParseFileResults option * line: int * colAtEndOfPartialName: int * lineText:string * qualifyingNames: string list * partialName: string * getAllSymbols: (unit -> AssemblySymbol list) * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) * ?userOpName: string -> Async + member GetDeclarationListInfo : ParsedFileResultsOpt:FSharpParseFileResults option * line: int * lineText:string * partialName: PartialLongName * getAllSymbols: (unit -> AssemblySymbol list) * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) * ?userOpName: string -> Async /// Get the items for a declaration list in FSharpSymbol format /// @@ -156,21 +158,23 @@ type internal FSharpCheckFileResults = /// 'record field' locations and r.h.s. of 'range' operator a..b /// /// The line number where the completion is happening - /// The column number at the end of the 'names' text - /// The long identifier to the left of the '.' - /// The residue of a partial long identifier to the right of the '.' - /// The residue of a partial long identifier to the right of the '.' + /// + /// Partial long name. QuickParse.GetPartialLongNameEx can be used to get it. + /// /// /// The text of the line where the completion is happening. This is only used to make a couple /// of adhoc corrections to completion accuracy (e.g. checking for "..") /// + /// + /// Function that returns all symbols from current and referenced assemblies. + /// /// /// If text has been used from a captured name resolution from the typecheck, then /// callback to the client to check if the text has changed. If it has, then give up /// and assume that we're going to repeat the operation later on. /// /// An optional string used for tracing compiler operations associated with this request. - member GetDeclarationListSymbols : ParsedFileResultsOpt:FSharpParseFileResults option * line: int * colAtEndOfPartialName: int * lineText:string * qualifyingNames: string list * partialName: string * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) * ?userOpName: string -> Async + member GetDeclarationListSymbols : ParsedFileResultsOpt:FSharpParseFileResults option * line: int * lineText:string * partialName: PartialLongName * ?hasTextChangedSinceLastTypecheck: (obj * range -> bool) * ?userOpName: string -> Async /// Compute a formatted tooltip for the given location @@ -200,7 +204,7 @@ type internal FSharpCheckFileResults = /// The text of the line where the information is being requested. /// The identifiers at the location where the information is being requested. /// An optional string used for tracing compiler operations associated with this request. - member GetF1Keyword : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?userOpName: string -> Async + member GetF1Keyword : line:int * colAtEndOfNames:int * lineText:string * names:string list * ?userOpName: string -> Async /// Compute a set of method overloads to show in a dialog relevant to the given code location. @@ -210,7 +214,7 @@ type internal FSharpCheckFileResults = /// The text of the line where the information is being requested. /// The identifiers at the location where the information is being requested. /// An optional string used for tracing compiler operations associated with this request. - member GetMethods : line:int * colAtEndOfNames:int * lineText:string * names:string list option * ?userOpName: string -> Async + member GetMethods : line:int * colAtEndOfNames:int * lineText:string * names:string list option * ?userOpName: string -> Async /// Compute a set of method overloads to show in a dialog relevant to the given code location. The resulting method overloads are returned as symbols. /// The line number where the information is being requested. diff --git a/tests/service/EditorTests.fs b/tests/service/EditorTests.fs index 8b70eeda1bc..e935737511e 100644 --- a/tests/service/EditorTests.fs +++ b/tests/service/EditorTests.fs @@ -86,7 +86,8 @@ let ``Intro test`` () = let tip = typeCheckResults.GetToolTipText(4, 7, inputLines.[1], ["foo"], identToken) |> Async.RunSynchronously // (sprintf "%A" tip).Replace("\n","") |> shouldEqual """FSharpToolTipText [Single ("val foo : unit -> unitFull name: Test.foo",None)]""" // Get declarations (autocomplete) for a location - let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 7, 23, inputLines.[6], [], "msg", (fun _ -> []), fun _ -> false)|> Async.RunSynchronously + let partialName = { QualifyingIdents = []; PartialIdent = "msg"; EndColumn = 22; LastDotPos = None } + let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 7, inputLines.[6], partialName, (fun _ -> []), fun _ -> false)|> Async.RunSynchronously CollectionAssert.AreEquivalent(stringMethods,[ for item in decls.Items -> item.Name ]) // Get overloads of the String.Concat method let methods = typeCheckResults.GetMethods(5, 27, inputLines.[4], Some ["String"; "Concat"]) |> Async.RunSynchronously @@ -285,7 +286,7 @@ let ``Expression typing test`` () = // gives the results for the string type. // for col in 42..43 do - let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 2, col, inputLines.[1], [], "", (fun _ -> []), fun _ -> false)|> Async.RunSynchronously + let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 2, inputLines.[1], PartialLongName.Empty(col), (fun _ -> []), fun _ -> false)|> Async.RunSynchronously let autoCompleteSet = set [ for item in decls.Items -> item.Name ] autoCompleteSet |> shouldEqual (set stringMethods) @@ -306,7 +307,7 @@ type Test() = let file = "/home/user/Test.fsx" let parseResult, typeCheckResults = parseAndCheckScript(file, input) - let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 4, 21, inputLines.[3], [], "", (fun _ -> []), fun _ -> false)|> Async.RunSynchronously + let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 4, inputLines.[3], PartialLongName.Empty(20), (fun _ -> []), fun _ -> false)|> Async.RunSynchronously let item = decls.Items |> Array.tryFind (fun d -> d.Name = "abc") decls.Items |> Seq.exists (fun d -> d.Name = "abc") |> shouldEqual true @@ -323,7 +324,7 @@ type Test() = let file = "/home/user/Test.fsx" let parseResult, typeCheckResults = parseAndCheckScript(file, input) - let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 4, 22, inputLines.[3], [], "", (fun _ -> []), fun _ -> false)|> Async.RunSynchronously + let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 4, inputLines.[3], PartialLongName.Empty(21), (fun _ -> []), fun _ -> false)|> Async.RunSynchronously let item = decls.Items |> Array.tryFind (fun d -> d.Name = "abc") decls.Items |> Seq.exists (fun d -> d.Name = "abc") |> shouldEqual true @@ -340,7 +341,7 @@ type Test() = let file = "/home/user/Test.fsx" let parseResult, typeCheckResults = parseAndCheckScript(file, input) - let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 4, 15, inputLines.[3], [], "", (fun _ -> []), fun _ -> false)|> Async.RunSynchronously + let decls = typeCheckResults.GetDeclarationListInfo(Some parseResult, 4, inputLines.[3], PartialLongName.Empty(14), (fun _ -> []), fun _ -> false)|> Async.RunSynchronously decls.Items |> Seq.exists (fun d -> d.Name = "abc") |> shouldEqual true [] @@ -356,7 +357,7 @@ type Test() = let file = "/home/user/Test.fsx" let parseResult, typeCheckResults = parseAndCheckScript(file, input) - let decls = typeCheckResults.GetDeclarationListSymbols(Some parseResult, 4, 21, inputLines.[3], [], "", fun _ -> false)|> Async.RunSynchronously + let decls = typeCheckResults.GetDeclarationListSymbols(Some parseResult, 4, inputLines.[3], PartialLongName.Empty(20), fun _ -> false)|> Async.RunSynchronously //decls |> List.map (fun d -> d.Head.Symbol.DisplayName) |> printfn "---> decls = %A" decls |> Seq.exists (fun d -> d.Head.Symbol.DisplayName = "abc") |> shouldEqual true @@ -373,7 +374,7 @@ type Test() = let file = "/home/user/Test.fsx" let parseResult, typeCheckResults = parseAndCheckScript(file, input) - let decls = typeCheckResults.GetDeclarationListSymbols(Some parseResult, 4, 22, inputLines.[3], [], "", fun _ -> false)|> Async.RunSynchronously + let decls = typeCheckResults.GetDeclarationListSymbols(Some parseResult, 4, inputLines.[3], PartialLongName.Empty(21), fun _ -> false)|> Async.RunSynchronously //decls |> List.map (fun d -> d.Head.Symbol.DisplayName) |> printfn "---> decls = %A" decls |> Seq.exists (fun d -> d.Head.Symbol.DisplayName = "abc") |> shouldEqual true @@ -390,7 +391,7 @@ type Test() = let file = "/home/user/Test.fsx" let parseResult, typeCheckResults = parseAndCheckScript(file, input) - let decls = typeCheckResults.GetDeclarationListSymbols(Some parseResult, 4, 15, inputLines.[3], [], "", fun _ -> false)|> Async.RunSynchronously + let decls = typeCheckResults.GetDeclarationListSymbols(Some parseResult, 4, inputLines.[3], PartialLongName.Empty(14), fun _ -> false)|> Async.RunSynchronously //decls |> List.map (fun d -> d.Head.Symbol.DisplayName) |> printfn "---> decls = %A" decls |> Seq.exists (fun d -> d.Head.Symbol.DisplayName = "abc") |> shouldEqual true diff --git a/vsintegration/Utils/LanguageServiceProfiling/Program.fs b/vsintegration/Utils/LanguageServiceProfiling/Program.fs index bc785b787cd..5413e8b9094 100644 --- a/vsintegration/Utils/LanguageServiceProfiling/Program.fs +++ b/vsintegration/Utils/LanguageServiceProfiling/Program.fs @@ -164,10 +164,11 @@ let main argv = fileResults.GetDeclarationListInfo( Some parseResult, completion.Position.Line, - completion.Position.Column, getLine (completion.Position.Line), - completion.QualifyingNames, - completion.PartialName, + { QualifyingIdents = completion.QualifyingNames + PartialIdent = completion.PartialName + EndColumn = completion.Position.Column - 1 + LastDotPos = None }, fun() -> []) for i in listInfo.Items do diff --git a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs index 99bcd091c91..b8f8ac4c4c9 100644 --- a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs @@ -10,6 +10,7 @@ open Microsoft.CodeAnalysis.Classification open Microsoft.VisualStudio.FSharp.LanguageService open Microsoft.VisualStudio.LanguageServices.Implementation.F1Help open Microsoft.CodeAnalysis.Host.Mef +open Microsoft.FSharp.Compiler open Microsoft.FSharp.Compiler.Range open Microsoft.FSharp.Compiler.SourceCodeServices diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index e30c0322b47..974796d9d20 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -100,14 +100,14 @@ type internal FSharpCompletionProvider let caretLine = textLines.GetLineFromPosition(caretPosition) let fcsCaretLineNumber = Line.fromZ caretLinePos.Line // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based let caretLineColumn = caretLinePos.Character - let qualifyingNames, partialName = QuickParse.GetPartialLongNameEx(caretLine.ToString(), caretLineColumn - 1) + let partialName = QuickParse.GetPartialLongNameEx(caretLine.ToString(), caretLineColumn - 1) let getAllSymbols() = - getAllSymbols() |> List.filter (fun entity -> entity.FullName.Contains "." && not (PrettyNaming.IsOperatorName entity.Symbol.DisplayName)) + getAllSymbols() + |> List.filter (fun entity -> entity.FullName.Contains "." && not (PrettyNaming.IsOperatorName entity.Symbol.DisplayName)) - let! declarations = - checkFileResults.GetDeclarationListInfo(Some(parseResults), fcsCaretLineNumber, caretLineColumn, caretLine.ToString(), qualifyingNames, partialName, getAllSymbols, userOpName=userOpName) |> liftAsync - + let! declarations = checkFileResults.GetDeclarationListInfo(Some(parseResults), fcsCaretLineNumber, caretLine.ToString(), + partialName, getAllSymbols, userOpName=userOpName) |> liftAsync let results = List() let getKindPriority = function @@ -186,7 +186,7 @@ type internal FSharpCompletionProvider declarationItemsCache.Add(completionItem.DisplayText, declItem) results.Add(completionItem)) - if results.Count > 0 && not declarations.IsForType && not declarations.IsError && List.isEmpty qualifyingNames then + if results.Count > 0 && not declarations.IsForType && not declarations.IsError && List.isEmpty partialName.QualifyingIdents then let lineStr = textLines.[caretLinePos.Line].ToString() match UntypedParseImpl.TryGetCompletionContext(Pos.fromZ caretLinePos.Line caretLinePos.Character, Some parseResults, lineStr) with | None -> results.AddRange(keywordCompletionItems) diff --git a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs index 02ccfc1e6e8..432b0431052 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs @@ -14,7 +14,7 @@ open Microsoft.CodeAnalysis.Editor.Implementation.Debugging open Microsoft.CodeAnalysis.Host.Mef open Microsoft.CodeAnalysis.Text -open Microsoft.VisualStudio.FSharp.LanguageService +open Microsoft.FSharp.Compiler [] [, FSharpConstants.FSharpLanguageName)>] diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs index b0b25ca6ead..ace07bf57f2 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs @@ -69,11 +69,11 @@ type internal SimplifyNameDiagnosticAnalyzer() = |> Array.Parallel.map (fun symbolUse -> let lineStr = sourceText.Lines.[Line.toZ symbolUse.RangeAlternate.StartLine].ToString() // for `System.DateTime.Now` it returns ([|"System"; "DateTime"|], "Now") - let plid, name = QuickParse.GetPartialLongNameEx(lineStr, symbolUse.RangeAlternate.EndColumn - 1) + let partialName = QuickParse.GetPartialLongNameEx(lineStr, symbolUse.RangeAlternate.EndColumn - 1) // `symbolUse.RangeAlternate.Start` does not point to the start of plid, it points to start of `name`, // so we have to calculate plid's start ourselves. - let plidStartCol = symbolUse.RangeAlternate.EndColumn - name.Length - (getPlidLength plid) - symbolUse, plid, plidStartCol, name) + let plidStartCol = symbolUse.RangeAlternate.EndColumn - partialName.PartialIdent.Length - (getPlidLength partialName.QualifyingIdents) + symbolUse, partialName.QualifyingIdents, plidStartCol, partialName.PartialIdent) |> Array.filter (fun (_, plid, _, name) -> name <> "" && not (List.isEmpty plid)) |> Array.groupBy (fun (symbolUse, _, plidStartCol, _) -> symbolUse.RangeAlternate.StartLine, plidStartCol) |> Array.map (fun (_, xs) -> xs |> Array.maxBy (fun (symbolUse, _, _, _) -> symbolUse.RangeAlternate.EndColumn)) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs index 2ef32dd1941..840138c00b3 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs @@ -532,7 +532,7 @@ module internal Tokenizer = | _ -> false) |> Option.orElseWith (fun _ -> tokensUnderCursor |> List.tryFind (fun { DraftToken.Kind = k } -> k = LexerSymbolKind.Operator)) |> Option.map (fun token -> - let plid, _ = QuickParse.GetPartialLongNameEx(lineStr, token.RightColumn) + let partialName = QuickParse.GetPartialLongNameEx(lineStr, token.RightColumn) let identStr = lineStr.Substring(token.Token.LeftColumn, token.Token.FullMatchedLength) { Kind = token.Kind Ident = @@ -541,7 +541,7 @@ module internal Tokenizer = fileName (Range.mkPos (linePos.Line + 1) token.Token.LeftColumn) (Range.mkPos (linePos.Line + 1) (token.RightColumn + 1))) - FullIsland = plid @ [identStr] }) + FullIsland = partialName.QualifyingIdents @ [identStr] }) let private getCachedSourceLineData(documentKey: DocumentId, sourceText: SourceText, position: int, fileName: string, defines: string list) = let textLine = sourceText.Lines.GetLineFromPosition(position) diff --git a/vsintegration/src/FSharp.LanguageService/FSharp.LanguageService.fsproj b/vsintegration/src/FSharp.LanguageService/FSharp.LanguageService.fsproj index 96ed9e9404a..7e170f7530b 100644 --- a/vsintegration/src/FSharp.LanguageService/FSharp.LanguageService.fsproj +++ b/vsintegration/src/FSharp.LanguageService/FSharp.LanguageService.fsproj @@ -55,7 +55,6 @@ - @@ -66,7 +65,6 @@ - @@ -83,7 +81,6 @@ - $(FSharpSourcesRoot)\..\packages\Microsoft.VisualFSharp.Msbuild.15.0.1.0.1\lib\net45\Microsoft.Build.Framework.dll @@ -95,7 +92,7 @@ $(FSharpSourcesRoot)\..\packages\EnvDTE80.8.0.1\lib\net10\EnvDTE80.dll True - + $(FSharpSourcesRoot)\..\packages\VSSDK.VSLangProj.7.0.4\lib\net20\VSLangProj.dll True @@ -149,7 +146,7 @@ $(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.Designer.Interfaces.1.1.4322\lib\microsoft.visualstudio.designer.interfaces.dll - + $(FSharpSourcesRoot)\..\packages\VSSDK.VSHelp.7.0.4\lib\net20\Microsoft.VisualStudio.VSHelp.dll True diff --git a/vsintegration/src/FSharp.LanguageService/GotoDefinition.fs b/vsintegration/src/FSharp.LanguageService/GotoDefinition.fs index dd4f2df45be..b027528e689 100644 --- a/vsintegration/src/FSharp.LanguageService/GotoDefinition.fs +++ b/vsintegration/src/FSharp.LanguageService/GotoDefinition.fs @@ -57,7 +57,7 @@ module internal GotoDefinition = | Some (s, colIdent, isQuoted) -> let qualId = if isQuoted then [s] else s.Split '.' |> Array.toList // chop it up (in case it's a qualified ident) // this is a bit irratiting: `GetTokenInfoAt` won't handle just-past-the-end, so we take the just-past-the-end position and adjust it by the `magicalAdjustmentConstant` to just-*at*-the-end - let colIdentAdjusted = colIdent - QuickParse.magicalAdjustmentConstant + let colIdentAdjusted = colIdent - QuickParse.MagicalAdjustmentConstant // Corrrect the identifier (e.g. to correctly handle active pattern names that end with "BAR" token) let tag = colourizer.GetTokenInfoAt(VsTextLines.TextColorState(VsTextView.Buffer textView), line, colIdentAdjusted).Token diff --git a/vsintegration/src/FSharp.LanguageService/Intellisense.fs b/vsintegration/src/FSharp.LanguageService/Intellisense.fs index 1190822644e..60673bb7f47 100644 --- a/vsintegration/src/FSharp.LanguageService/Intellisense.fs +++ b/vsintegration/src/FSharp.LanguageService/Intellisense.fs @@ -526,14 +526,14 @@ type internal FSharpIntellisenseInfo_DEPRECATED else None // TODO don't use QuickParse below, we have parse info available - let plid = QuickParse.GetPartialLongNameEx(lineText, col-1) - ignore plid // for breakpoint + let pname = QuickParse.GetPartialLongNameEx(lineText, col-1) + let _x = 1 // for breakpoint let detectTextChange (oldTextSnapshotInfo: obj, range) = let oldTextSnapshot = oldTextSnapshotInfo :?> ITextSnapshot hasTextChangedSinceLastTypecheck (textSnapshot, oldTextSnapshot, Range.Range.toZ range) - let! decls = typedResults.GetDeclarationListInfo(untypedParseInfoOpt, Range.Line.fromZ line, col, lineText, fst plid, snd plid, (fun() -> []), detectTextChange) + let! decls = typedResults.GetDeclarationListInfo(untypedParseInfoOpt, Range.Line.fromZ line, lineText, pname, (fun() -> []), detectTextChange) return (new FSharpDeclarations_DEPRECATED(documentationBuilder, decls.Items, reason) :> Declarations_DEPRECATED) else // no TypeCheckInfo in ParseResult. diff --git a/vsintegration/tests/Salsa/salsa.fs b/vsintegration/tests/Salsa/salsa.fs index 5e33c81e71c..be29d4eecf2 100644 --- a/vsintegration/tests/Salsa/salsa.fs +++ b/vsintegration/tests/Salsa/salsa.fs @@ -25,7 +25,7 @@ open Microsoft.VisualStudio.FSharp.LanguageService open Microsoft.VisualStudio.TextManager.Interop open UnitTests.TestLib.Utils.FilesystemHelpers open Microsoft.Build.Framework -open Microsoft.FSharp.Compiler.Range +open Microsoft.FSharp.Compiler open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.Build.Evaluation diff --git a/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs b/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs index aca191434a8..88d22ecb293 100644 --- a/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs +++ b/vsintegration/tests/unittests/Tests.LanguageService.Completion.fs @@ -7774,6 +7774,15 @@ let rec f l = let bar = 1""", marker = "(*Marker*)") + [] + member public this.``ExpressionDotting.Regression.Bug3709``() = + this.VerifyCtrlSpaceListContainAllAtStartOfMarker( + fileContents = """ + let foo = "" + let foo = foo.E(*marker*)n "a" """, + marker = "(*marker*)", + list = ["EndsWith"]) + // Context project system [] type UsingProjectSystem() = diff --git a/vsintegration/tests/unittests/Tests.LanguageService.GotoDefinition.fs b/vsintegration/tests/unittests/Tests.LanguageService.GotoDefinition.fs index 8794cc6e688..f72aa5c93d8 100644 --- a/vsintegration/tests/unittests/Tests.LanguageService.GotoDefinition.fs +++ b/vsintegration/tests/unittests/Tests.LanguageService.GotoDefinition.fs @@ -1346,7 +1346,7 @@ type UsingMSBuild() = member this.GetCompleteIdTest tolerate (s : string)(exp : string option) : unit = let n = s.IndexOf '$' let s = s.Remove (n, 1) - match (Microsoft.VisualStudio.FSharp.LanguageService.QuickParse.GetCompleteIdentifierIsland tolerate s n, exp) with + match (Microsoft.FSharp.Compiler.QuickParse.GetCompleteIdentifierIsland tolerate s n, exp) with | (Some (s1, _, _), Some s2) -> printfn "%s" "Received result, as expected." Assert.AreEqual (s1, s2) diff --git a/vsintegration/tests/unittests/Tests.LanguageService.QuickParse.fs b/vsintegration/tests/unittests/Tests.LanguageService.QuickParse.fs index 7c5be4fe7c4..7d08f8e4967 100644 --- a/vsintegration/tests/unittests/Tests.LanguageService.QuickParse.fs +++ b/vsintegration/tests/unittests/Tests.LanguageService.QuickParse.fs @@ -3,9 +3,8 @@ namespace Tests.LanguageService open System -open System.IO open NUnit.Framework -open Microsoft.VisualStudio.FSharp.LanguageService +open Microsoft.FSharp.Compiler [] [] @@ -22,37 +21,37 @@ type QuickParse() = [] member public qp.CheckGetPartialLongName() = - let CheckAt(line,index,expected) = - let actual = QuickParse.GetPartialLongNameEx(line,index) - if actual <> expected then + let CheckAt(line, index, expected) = + let actual = QuickParse.GetPartialLongNameEx(line, index) + if (actual.QualifyingIdents, actual.PartialIdent, actual.LastDotPos) <> expected then failwithf "Expected %A but got %A" expected actual let Check(line,expected) = CheckAt(line, line.Length-1, expected) do Microsoft.FSharp.Compiler.AbstractIL.Diagnostics.setDiagnosticsChannel(Some(Console.Out)); - Check("let y = List.",(["List"], "")) - Check("let y = List.conc",(["List"], "conc")) - Check("let y = S", ([], "S")) - Check("S", ([], "S")) - Check("let y=", ([], "")) - Check("Console.Wr", (["Console"], "Wr")) - Check(" .", ([""], "")) - Check(".", ([""], "")) - Check("System.Console.Wr", (["System";"Console"],"Wr")) - Check("let y=f'", ([], "f'")) - Check("let y=SomeModule.f'", (["SomeModule"], "f'")) - Check("let y=Some.OtherModule.f'", (["Some";"OtherModule"], "f'")) - Check("let y=f'g", ([], "f'g")) - Check("let y=SomeModule.f'g", (["SomeModule"], "f'g")) - Check("let y=Some.OtherModule.f'g", (["Some";"OtherModule"], "f'g")) - Check("let y=FSharp.Data.File.``msft-prices.csv``", ([], "")) - Check("let y=FSharp.Data.File.``msft-prices.csv", (["FSharp";"Data";"File"], "msft-prices.csv")) - Check("let y=SomeModule. f", (["SomeModule"], "f")) - Check("let y=SomeModule .f", (["SomeModule"], "f")) - Check("let y=SomeModule . f", (["SomeModule"], "f")) - Check("let y=SomeModule .", (["SomeModule"], "")) - Check("let y=SomeModule . ", (["SomeModule"], "")) + Check("let y = List.",(["List"], "", Some 12)) + Check("let y = List.conc",(["List"], "conc", Some 12)) + Check("let y = S", ([], "S", None)) + Check("S", ([], "S", None)) + Check("let y=", ([], "", None)) + Check("Console.Wr", (["Console"], "Wr", Some 7)) + Check(" .", ([""], "", Some 1)) + Check(".", ([""], "", Some 0)) + Check("System.Console.Wr", (["System";"Console"],"Wr", Some 14)) + Check("let y=f'", ([], "f'", None)) + Check("let y=SomeModule.f'", (["SomeModule"], "f'", Some 16)) + Check("let y=Some.OtherModule.f'", (["Some";"OtherModule"], "f'", Some 22)) + Check("let y=f'g", ([], "f'g", None)) + Check("let y=SomeModule.f'g", (["SomeModule"], "f'g", Some 16)) + Check("let y=Some.OtherModule.f'g", (["Some";"OtherModule"], "f'g", Some 22)) + Check("let y=FSharp.Data.File.``msft-prices.csv``", ([], "", None)) + Check("let y=FSharp.Data.File.``msft-prices.csv", (["FSharp";"Data";"File"], "msft-prices.csv", Some 22)) + Check("let y=SomeModule. f", (["SomeModule"], "f", Some 16)) + Check("let y=SomeModule .f", (["SomeModule"], "f", Some 18)) + Check("let y=SomeModule . f", (["SomeModule"], "f", Some 18)) + Check("let y=SomeModule .", (["SomeModule"], "", Some 18)) + Check("let y=SomeModule . ", (["SomeModule"], "", Some 18)) []