Skip to content

Commit 3b9a170

Browse files
[Repo Assist] fix: show hover documentation for FSI hash directives (#r, #load, #nowarn, etc.) (#1504)
* fix: show hover documentation for FSI hash directives (#r, #load, #nowarn, etc.) Resolves #1225 ## Root Cause Hovering over a hash directive like `#r`, `#load`, `#nowarn` returned `None` for two reasons: 1. `Lexer.findLongIdents` returns `None` for multi-character directives like `#nowarn` because the F# tokenizer emits them as directive tokens, not plain identifier tokens. 2. Even for short directives like `#r` where an identifier IS found, there was no lookup in the hash-directive description table. ## Fix - Added `hashDirectiveTooltips` to `KeywordList.fs`: a dictionary mapping each hash directive key (e.g. `"nowarn"`) to a synthetic `ToolTipText` built from the existing `hashDirectives` descriptions already used for completion. - In `TryGetToolTipEnhanced` (`ParseAndCheckResults.fs`): when `findLongIdents` returns `None`, check whether the line starts with a `#` and the cursor is within the directive keyword; if so, return the tooltip from `hashDirectiveTooltips`. - Also added a fallback in the `| _ ->` branch (when `findLongIdents` finds an ident but it is not a keyword) to handle short directives like `#r` and `#I`. ## Test Added `#nowarn "40"` to the tooltip test script and a `verifyDescription` assertion that hovering on column 3 of that line returns the expected `"Disables a compiler warning or warnings"` description. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b405c41 commit 3b9a170

4 files changed

Lines changed: 78 additions & 7 deletions

File tree

src/FsAutoComplete.Core/KeywordList.fs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ module KeywordList =
4242
"line", "Indicates the original source code line" ]
4343
|> dict
4444

45+
let hashDirectiveTooltips =
46+
hashDirectives
47+
|> Seq.map (fun kv ->
48+
let lines = kv.Value.Replace("\r\n", "\n").Split('\n')
49+
let allLines = Array.concat [| [| "<summary>" |]; lines; [| "</summary>" |] |]
50+
51+
let tip =
52+
ToolTipText
53+
[ ToolTipElement.Single(
54+
[| TaggedText.tagText ("#" + kv.Key) |],
55+
FSharpXmlDoc.FromXmlText(FSharp.Compiler.Xml.XmlDoc(allLines, Range.Zero))
56+
) ]
57+
58+
kv.Key, tip)
59+
|> dict
60+
4561
let hashSymbolCompletionItems =
4662
hashDirectives
4763
|> Seq.map (fun kv ->

src/FsAutoComplete.Core/ParseAndCheckResults.fs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,38 @@ type ParseAndCheckResults
357357
| Completion.Context.Unknown ->
358358
match Lexer.findLongIdents (uint32 pos.Column, lineStr) with
359359
| None ->
360-
logger.info (Log.setMessageI $"Cannot find ident for tooltip: {pos.Column:column} in {lineStr:lineString}")
361-
None
360+
// Check if we're hovering over a hash directive (e.g. #r, #load, #nowarn).
361+
// Lexer.findLongIdents returns None for multi-character directives like #nowarn because
362+
// the F# tokenizer emits them as directive tokens, not plain identifiers.
363+
let trimmedLine = lineStr.TrimStart()
364+
365+
if trimmedLine.StartsWith("#") then
366+
let leadingSpaces = lineStr.Length - trimmedLine.Length
367+
let rest = trimmedLine.Substring(1)
368+
let wordEnd = rest.IndexOfAny([| ' '; '\t'; '"'; '\r'; '\n' |])
369+
let directiveWord = if wordEnd > 0 then rest.[.. wordEnd - 1] else rest
370+
let directiveEndCol = leadingSpaces + 1 + directiveWord.Length
371+
372+
if pos.Column < directiveEndCol then
373+
match KeywordList.hashDirectiveTooltips.TryGetValue directiveWord with
374+
| true, tip ->
375+
{ ToolTipText = tip
376+
Signature = "#" + directiveWord
377+
Footer = ""
378+
SymbolInfo = TryGetToolTipEnhancedResult.Keyword directiveWord }
379+
|> Some
380+
| _ ->
381+
logger.info (
382+
Log.setMessageI $"Cannot find ident for tooltip: {pos.Column:column} in {lineStr:lineString}"
383+
)
384+
385+
None
386+
else
387+
logger.info (Log.setMessageI $"Cannot find ident for tooltip: {pos.Column:column} in {lineStr:lineString}")
388+
None
389+
else
390+
logger.info (Log.setMessageI $"Cannot find ident for tooltip: {pos.Column:column} in {lineStr:lineString}")
391+
None
362392
| Some(col, identIsland) ->
363393
let identIsland = Array.toList identIsland
364394
// TODO: Display other tooltip types, for example for strings or comments where appropriate
@@ -380,11 +410,22 @@ type ParseAndCheckResults
380410
SymbolInfo = TryGetToolTipEnhancedResult.Keyword ident }
381411
|> Some
382412
| _ ->
383-
logger.info (
384-
Log.setMessageI $"Cannot find ident for tooltip: {pos.Column:column} in {lineStr:lineString}"
385-
)
413+
// Check if we're hovering over a hash directive (e.g., #r, #load, #nowarn)
414+
let trimmedLine = lineStr.TrimStart()
415+
416+
match KeywordList.hashDirectiveTooltips.TryGetValue ident with
417+
| true, tip when trimmedLine.StartsWith("#" + ident) ->
418+
{ ToolTipText = tip
419+
Signature = "#" + ident
420+
Footer = ""
421+
SymbolInfo = TryGetToolTipEnhancedResult.Keyword ident }
422+
|> Some
423+
| _ ->
424+
logger.info (
425+
Log.setMessageI $"Cannot find ident for tooltip: {pos.Column:column} in {lineStr:lineString}"
426+
)
386427

387-
None
428+
None
388429
| _ ->
389430
logger.info (Log.setMessageI $"Cannot find ident for tooltip: {pos.Column:column} in {lineStr:lineString}")
390431
None

test/FsAutoComplete.Tests.Lsp/CoreTests.fs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,18 @@ let tooltipTests state =
624624
5u
625625
(concatLines [ "type MultiArgDelegate ="; " delegate of a: string * b: int -> bool" ])
626626

627-
verifySignature 120u 5u (concatLines [ "type UnannotatedDelegate ="; " delegate of string -> int" ]) ] ]
627+
verifySignature 120u 5u (concatLines [ "type UnannotatedDelegate ="; " delegate of string -> int" ])
628+
629+
// FSI hash directive hover — regression for issue #1225.
630+
// Hovering on a hash directive (e.g. #nowarn, #r, #load) should show documentation.
631+
verifyDescription
632+
123u
633+
3u
634+
[ "**Description**"
635+
""
636+
""
637+
"Disables a compiler warning or warnings"
638+
"" ] ] ]
628639

629640
let closeTests state =
630641
// Note: clear diagnostics also implies clear caches (-> remove file & project options from State).

test/FsAutoComplete.Tests.Lsp/TestCases/Tooltips/Script.fsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,6 @@ type NoArgDelegate = delegate of unit -> unit
119119
type SingleArgDelegate = delegate of arg: string -> int
120120
type MultiArgDelegate = delegate of a: string * b: int -> bool
121121
type UnannotatedDelegate = delegate of string -> int
122+
123+
// FSI hash directives — hover tooltip for issue #1225
124+
#nowarn "40"

0 commit comments

Comments
 (0)