F# Interactive window: IntelliSense and semantic colouring in the input buffer
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Active
- Domain
- developer-experience, tooling
Research direction
Start with #20565 and the submission service interface in FSharp.Interactive.Window, then inspect the corresponding FSharp.Editor workspace and FSharpProjectOptionsManager entry points. Run or extend the listed FSharp.Editor.Tests cases for chained submissions, shadowing, failures, and reset. Done means the input buffer provides completion and semantic colouring while preserving the documented session and reset behavior.
Written by the indexing model from the issue text.
Description
Implementation plan for #2161, on top of the new F# Interactive window in #20565.
#20565 hosts F# Interactive in Visual Studio's Interactive Window (the engine C# Interactive uses) and drives dotnet fsi over the JSON-RPC mode from #20396. Its input buffer already has the F# content type, but no Roslyn document behind it, so it only gets lexical colour. This issue covers the next step: completion, quick info, signature help and semantic colouring in the input buffer, checked against everything the session has already run.
How the pieces fit
The window stays free of the compiler, as it is in #20565: it talks to the session over JSON-RPC and asks FSharp.Editor for everything language-related through small interfaces it declares itself. FSharp.Editor already runs on the compiler Visual Studio ships and already provides every IDE feature for .fsx files, so the plan is to make each input buffer look to it like one more open script.
flowchart LR
subgraph VS["devenv.exe"]
IW["Interactive Window package<br/>(buffers, prompts, history)"]
subgraph Window["FSharp.Interactive.Window"]
Eval["FSharpInteractiveEvaluator"]
Host["InteractiveHostClient"]
end
subgraph Editor["FSharp.Editor"]
Svc["Interactive submission service<br/>(new)"]
WS["Interactive workspace<br/>(new, kind 'Interactive')"]
Opts["FSharpProjectOptionsManager"]
Checker["FSharpChecker"]
Features["Completion, QuickInfo,<br/>SignatureHelp, Classification"]
end
Roslyn["Roslyn editor features"]
end
subgraph FSI["dotnet fsi --fsi-server-jsonrpc"]
Server["JSON-RPC server"]
Session["FsiEvaluationSession"]
end
Disk[("per-session temp dir<br/>Submission1.fsx … SubmissionN.fsx")]
IW <--> Eval
Eval --> Host
Host <-->|"named pipe: JSON-RPC<br/>execute / interrupt / reset"| Server
Server --> Session
Session -.->|"stdout / stderr"| Host
Eval -->|"buffer added / submission succeeded / reset"| Svc
Svc --> WS
Svc --> Disk
Roslyn -->|"document for buffer"| WS
Roslyn --> Features
Features --> Opts
Opts -->|"#load earlier submissions"| Disk
Features --> Checker
Two processes, two channels, as in #20565: evaluation happens in dotnet fsi (the SDK the solution's global.json selects), while IntelliSense happens in Visual Studio with the IDE's own checker. They never share state directly. What the IDE knows about the session comes from the submissions that succeeded, kept on disk.
Chaining submissions: #load of earlier ones
fsi compiles every interaction into its own module and opens it for the next. The IDE can reproduce that with plain script features: each successful submission is written to SubmissionN.fsx with a one-line header, and the current input is checked as a script that loads all of them.
// Submission2.fsx, written after the second submission ran
[<AutoOpen>]
module Submission2
let x = x + 1
Verified with dotnet fsi: with the header, a loaded submission's bindings are visible unqualified and later ones shadow earlier ones (let x = 41 then let x = x + 1 reads 42); #r "nuget: …" inside a loaded submission works. Without the header, #loaded script bindings are not visible unqualified (FS0039).
flowchart TB
S1["Submission1.fsx<br/>AutoOpen module Submission1<br/>let x = 41"]
S2["Submission2.fsx<br/>AutoOpen module Submission2<br/>let x = x + 1"]
F["failed submission<br/>(never written)"]
Cur["input buffer = Submission3.fsx<br/>x. ← completion sees x = 42"]
S1 --> S2 --> Cur
F -.-x Cur
FSharpProjectOptionsManager builds options for a script with GetProjectOptionsFromScript(path, text), where the text is used only to find #load/#r. For a document of the interactive workspace it passes #load "Submission1.fsx" "Submission2.fsx" in front of the buffer's text. The loaded files become earlier source files of the check, and the input document itself is unchanged, so positions in it map 1:1. That replaces the "concatenated prelude with offset mapping" the design doc (docs/ide/FSI-Modern-Interactive-Window-Plan.md §2.3) proposed.
What happens when
sequenceDiagram
actor User
participant IW as Interactive Window
participant Eval as FSharpInteractiveEvaluator
participant Svc as Submission service (FSharp.Editor)
participant WS as Interactive workspace
participant Roslyn as Roslyn editor features
participant FS as FSharp.Editor language services
participant FSI as dotnet fsi (JSON-RPC)
IW->>Eval: new input buffer
Eval->>Svc: BufferAdded(buffer)
Svc->>WS: add SubmissionN.fsx, open on buffer.AsTextContainer()
User->>IW: types "x."
IW->>Roslyn: completion requested
Roslyn->>WS: document for buffer
Roslyn->>FS: GetCompletions(document)
FS->>FS: options = GetProjectOptionsFromScript(<br/>"#load Submission1.fsx … N-1" + buffer text)
FS-->>Roslyn: items incl. bindings from earlier submissions
User->>IW: Enter
IW->>Eval: CanExecuteCode / ExecuteCodeAsync
Eval->>FSI: fsi/execute
FSI-->>Eval: ExecutionResult { success }
alt success
Eval->>Svc: SubmissionSucceeded(text)
Svc->>Svc: write SubmissionN.fsx with AutoOpen header
else failure
Eval->>Svc: SubmissionFailed
Note over Svc: nothing written, the chain is unchanged
end
IW->>Eval: next input buffer (SubmissionN+1)
#reset restarts the session and clears the chain:
sequenceDiagram
participant IW as Interactive Window
participant Eval as FSharpInteractiveEvaluator
participant Svc as Submission service
participant WS as Interactive workspace
participant FSI as dotnet fsi
IW->>Eval: ResetAsync
Eval->>Svc: Reset
Svc->>Svc: snapshot classification of executed buffers<br/>(scrollback keeps its colours)
Svc->>WS: close and remove submission documents
Svc->>Svc: delete the session's SubmissionN.fsx files
Eval->>FSI: shut down, start a new session
Because the IDE's view is built from files rather than from the running session, a buffer created before dotnet fsi has finished starting needs no special handling: it simply loads no earlier submissions yet.
Work items
- Submission service interface in
FSharp.Interactive.Window, implemented and exported byFSharp.Editor(the same pattern asILexicalScannerFactoryin #20565), so the window keeps no reference to the compiler:BufferAdded,SubmissionSucceeded,SubmissionFailed,Reset. - Interactive workspace in
FSharp.Editor: a RoslynWorkspaceof kindInteractivecreated with the Visual Studio host services, one project named as the F# miscellaneous-files project (so the options manager takes the script path), oneSubmissionN.fsxdocument per input buffer, opened on the buffer's text container. - Session directory: a per-session temp directory for
SubmissionN.fsx, written on success with the[<AutoOpen>] module SubmissionNheader, deleted on reset and when the window closes. - Options: in
FSharpProjectOptionsManager, prefix the text passed toGetProjectOptionsFromScriptwith a#loadof the earlier submissions for documents of the interactive workspace. - Colouring: retire the window's lexical classifier for the input buffer once the buffer has a document (F# syntactic and semantic classification take over); keep the lexical one for output.
- Scrollback after reset: snapshot the classification of executed buffers before their documents close and replay it (Roslyn's inert-classifier approach).
- One checker:
FSharpWorkspaceServiceFactorycreates anFSharpCheckerper workspace; the interactive workspace should share the host workspace's instead of loading a second copy of every referenced assembly. - Tests in
FSharp.Editor.Tests: options for a submission document include the earlier submissions; completion sees a binding from an earlier submission; shadowing resolves to the latest; a failed submission is not part of the chain; reset empties it.
Open questions
- Whether Roslyn's diagnostic tagger runs for a non-host workspace. If it does not, squiggles in the input buffer need their own tagger; completion and colouring do not depend on it.
- Go to Definition into an earlier submission opens its temp file, two lines off because of the header. Navigating into the window's own scrollback instead, and disabling rename and code fixes there, belong to a follow-up (the "interactive workspace-kind overrides" item of the design doc).
- The longer-term alternative is an FCS API that checks an interaction against fsi's own accumulated type-checking state (design doc §2.3, Phase B). The
#loadchain needs no compiler changes, so it does not wait on that.
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 877
- Avg merge
- 6d 5h
- Merged PRs (30d)
- 163
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/fsharp
-
Needs-Triage
Difficulty 1/5 Under an hour Newbie friendliness 90/100
-
Needs-Triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Bug Needs-Triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Needs-Triage
Difficulty 3/5 1-2 days Newbie friendliness 68/100
-
Needs-Triage
Difficulty 5/5 Over a week Newbie friendliness 48/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
use-agent-os/agent-os#3314 ·
-
documentation low-priority templates
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
jesseray718/openroot#87 ·
-
factory-active factory-automatic task-bug-reproduction-cannot-reproduce task-identify-harness-labels-done task-identify-issue-type-done
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
danielmiessler/LifeOS#2218 ·
-
in-progress proposal
Difficulty 1/5 Under an hour Newbie friendliness 88/100
uibcdf/molsyssuite#36 ·