F# support for Neovim ONLY
An unofficial part of the Ionide plugin suite.
-
A (now disconnected) fork of Ionide-Vim, which is a fork of fsharp/vim-fsharp.
-
Uses FsAutoComplete as a backend.
-
Uses Neovim's built-in LSP client (requires Neovim 0.5+)
Consider this to be beta since it's lacking features compared to Ionide-VSCode and not as battle-tested as that.
That being said, I use this plugin daily so it will someday become feature-rich and stable for sure.
Feel free to request features and/or file bug reports!
-
Neovim - this one will only run on Neovim. I'm simply unable and unwilling to support regular vim.
-
- Required to install and run FsAutoComplete.
- Very useful for command-line development.
- Syntax highlighting
- Auto completions
- Error highlighting, error list, and quick fixes based on errors
- Tooltips
- Codelens
- Go to Definition
- Find all references
- Highlighting usages
- Rename
- Show symbols in file
- Find symbol in workspace
- Show signature in status line
- Integration with F# Interactive
- Integration with FSharpLint (additional hints and quick fixes)
- Integration with Fantomas (the best formatter available for F#)
If you're not relying on Mason, install FsAutoComplete with dotnet tool install.
If you want to install it as a "global" tool, run dotnet tool install -g fsautocomplete.
If you want to install it as a project-local tool, run dotnet tool install fsautocomplete
at the root directory of your F# project, and configure ionide.cmd
No LSP client plugin is required.
If you are using neovim/nvim-lspconfig, set up fsautocomplete as follows:
-- lazy.nvim
{
-- very recommended to use nvim-lspconfig, as it takes care of much of the management of starting Ionide,
"neovim/nvim-lspconfig",
version = false, -- last release is way too old
dependencies = {
"WillEhrendreich/Ionide-Nvim",
},
opts = {
servers = {
fsautocomplete = require("ionide").setup({
-- set things here if you want to override defaults otherwise just send in {} like require("ionide").setup({})
}),
},
},
}
Clone Ionide-Nvim to some runtimepath, I guess... but, honestly just don't. Too messy this way.
Opening either *.fs, *.fsi or *.fsx files should trigger syntax highlighting and other depending runtime files as well.
Will's fork feature: opening a *.fsproj file will trigger Ionide to load. Yeah, It's awesome.
Ionide-Nvim has an integration with F# Interactive.
FSI is displayed using the builtin :terminal feature in Neovim and can be used like in VSCode.
- "Send Current line's text to FSharp Interactive"
- "Send Current buffer's text to FSharp Interactive"
- "Toggle FSharp Interactive"
- "Quit FSharp Interactive"
Here are all the defaults that end up in a final merged config
ionide ={
--- Settings specific to neovim's built-in LSP client
IonideNvimSettings = {
AutocmdEvents = { "LspAttach"},
AutomaticReloadWorkspace = true,
AutomaticWorkspaceInit = true,
FsautocompleteCommand = { "fsautocomplete" },
--- refer to fsac documentation for other possible commands.
FsiCommand = "dotnet fsi",
FsiFocusOnSend = false,
FsiKeymap = "vscode",
-- #### `Alt-Enter`
-- - When in normal mode, sends the current line to FSI.
-- - When in visual mode, sends the selection to FSI.
-- - Sending code to FSI opens FSI window but the cursor does not focus to it.
FsiKeymapSend = "<M-cr>",
-- #### `Alt-@`
-- - Toggles FSI window. FSI windows shown in different tabpages share the same FSI session.
-- - When opened, the cursor automatically focuses to the FSI window (unlike in `Alt-Enter` by default).
FsiKeymapToggle = "<M-@>",
FsiVscodeKeymaps = true,
--##### Customize how FSI window is opened
FsiWindowCommand = "botright 10new",
LspAutoSetup = false,
LspCodelens = true,
--- Enable/disable the default colorscheme for diagnostics
--- *Default:* enabled
--- Neovim's LSP client comes with no default colorscheme, so Ionide-Nvim sets a VSCode-like one for LSP diagnostics by default.
LspRecommendedColorScheme = true,
ShowSignatureOnCursorMove = true,
Statusline = "Ionide",
UseRecommendedServerConfig = false
},
---path to fsautocomplete. if it's installed globally, this should be fine.
--- the project local version would be {"dotnet", "fsautocomplete" }
--- and the mason version would be
--- Windows: vim.fs.normalize(vim.fn.stdpath("data").."/mason/bin/fsautocomplete.cmd"),
--- Non - Windows(I think..): vim.fs.normalize(vim.fn.stdpath("data").."/mason/bin/fsautocomplete"),
cmd = { "fsautocomplete" },
filetypes = { "fsharp", "fsharp_project" },
handlers = {
-- Ionide registers handlers for these lsp requests by default.
-- do not copy paste this portion, just showing that there is some handling of these here
-- ["fsharp/compilerLocation"] = function(error,result,context,config) end,
-- ["fsharp/documentationSymbol"] = function(error,result,context,config) end,
-- ["fsharp/notifyWorkspace"] = function(error,result,context,config) end,
-- ["fsharp/signature"] = function(error,result,context,config) end,
-- ["fsharp/workspaceLoad"] = function(error,result,context,config) end,
-- ["fsharp/workspacePeek"] = function(error,result,context,config) end,
-- ["textDocument/documentHighlight"] = function(error,result,context,config) end,
-- ["textDocument/hover"] = function(error,result,context,config) end,
},
init_options = {
AutomaticWorkspaceInit = true
},
log_level = 2,
message_level = 2,
--- this is what will run on every buffer that the client attaches itself to.
--- it's a function that takes in (LspClient , bufferNumber) and does whatever you want after that
--- it's a good place to set some keymaps for specific lsp things, though in my experience there's a lot
--- that you want set regardless, so it's only conditional stuff that you want to set here exactly.
-- on_attach = your on attach func here.
-- on_init = -- ionide's init function goes here. I can't imagine why you'd want to change this, but feel free to open pr or ask about it.
-- this is the function used to find a root directory to pass to the Lsp.
-- for root dir, it should be fine to leave it as is, but you can change it if you need to.
-- for reference, this is how ionide has implemented this:
-- function M.GetRoot(n)
-- local root
-- root = root or util.root_pattern("*.slnx")(n)
-- if root then
-- return root
-- end
-- root = root or util.root_pattern("*.sln")(n)
-- if root then
-- return root
-- end
-- root = root or util.root_pattern("*.fsproj")(n)
-- if root then
-- return root
-- end
-- root = root or util.root_pattern("*.fsx")(n)
-- if root then
-- return root
-- end
-- root = util.find_git_ancestor(n)
-- if root then
-- return root
-- end
-- return root
-- end
-- root_dir = function(bufnr, on_dir)
-- local bufnr = vim.api.nvim_get_current_buf()
-- local bufname = vim.fs.normalize(vim.api.nvim_buf_get_name(bufnr))
-- local root = M.GetRoot(bufname)
--
-- return on_dir(root)
-- end,
--- this is the settings table. it's what is passed to fsautocomplete on the UpdateSeverConfiguration function call,
--- most importantly, Initialize calls the UpdateSeverConfiguration function with this data,
--- though if you change anything in here at runtime,
--- you're going to have to call the IonideUpdateServerConfiguration user command to to do that.
--- in all likelihood, it's going to be easier to just make the setting change in your setup to ionide, and reload Neovim.
settings = {
FSharp =
{
-- `addFsiWatcher`,
addFsiWatcher = false,
-- `addPrivateAccessModifier`,
addPrivateAccessModifier = false,
-- `autoRevealInExplorer`,
autoRevealInExplorer = "sameAsFileExplorer",
-- `disableFailedProjectNotifications`,
disableFailedProjectNotifications = false,
-- `enableMSBuildProjectGraph`,
enableMSBuildProjectGraph = true,
-- `enableReferenceCodeLens`,
enableReferenceCodeLens = true,
-- `enableTouchBar`,
enableTouchBar = true,
-- `enableTreeView`,
enableTreeView = true,
-- `fsiSdkFilePath`,
fsiSdkFilePath = "",
-- `infoPanelReplaceHover`,
-- Not relevant to Neovim, currently
-- if there's a big demand I'll consider making one.
infoPanelReplaceHover = false,
-- `infoPanelShowOnStartup`,
infoPanelShowOnStartup = false,
-- `infoPanelStartLocked`,
infoPanelStartLocked = false,
-- `infoPanelUpdate`,
infoPanelUpdate = "onCursorMove",
-- `inlineValues`,
inlineValues = { enabled = true, prefix = " // " },
-- `msbuildAutoshow`,
-- Not relevant to Neovim, currently
msbuildAutoshow = false,
-- `notifications`,
notifications = { trace = true, traceNamespaces = { "BoundModel.TypeCheck", "BackgroundCompiler." } },
-- `openTelemetry`,
openTelemetry = { enabled = false },
-- `pipelineHints`,
pipelineHints = { enabled = true, prefix = " // " },
-- `saveOnSendLastSelection`,
saveOnSendLastSelection = false,
-- `showExplorerOnStartup`,
-- Not relevant to Neovim, currently
showExplorerOnStartup = false,
-- `showProjectExplorerIn`,
-- Not relevant to Neovim, currently
showProjectExplorerIn = "fsharp",
-- `simplifyNameAnalyzerExclusions`,
-- Not relevant to Neovim, currently
simplifyNameAnalyzerExclusions = { ".*\\.g\\.fs", ".*\\.cg\\.fs" },
-- `smartIndent`,
-- Not relevant to Neovim, currently
smartIndent = true,
-- `suggestGitignore`,
suggestGitignore = true,
-- `trace`,
trace = { server = "off" },
-- `unusedDeclarationsAnalyzerExclusions`,
unusedDeclarationsAnalyzerExclusions = { ".*\\.g\\.fs", ".*\\.cg\\.fs" },
-- `unusedOpensAnalyzerExclusions`,
unusedOpensAnalyzerExclusions = { ".*\\.g\\.fs", ".*\\.cg\\.fs" },
-- `verboseLogging`,
verboseLogging = false,
-- `workspacePath`
workspacePath = "",
-- `TestExplorer` = "",
-- Not relevant to Neovim, currently
TestExplorer = { AutoDiscoverTestsOnLoad = true },
-- { AutomaticWorkspaceInit: bool option AutomaticWorkspaceInit = false
-- WorkspaceModePeekDeepLevel: int option WorkspaceModePeekDeepLevel = 2
workspaceModePeekDeepLevel = 4,
fsac = {
attachDebugger = false,
cachedTypeCheckCount = 200,
conserveMemory = true,
silencedLogs = {},
parallelReferenceResolution = true,
-- "FSharp.fsac.sourceTextImplementation": {
-- "default": "NamedText",
-- "description": "EXPERIMENTAL. Enables the use of a new source text implementation. This may have better memory characteristics. Requires restart.",
-- "enum": [
-- "NamedText",
-- "RoslynSourceText"
-- ]
-- },
sourceTextImplementation = "RoslynSourceText",
dotnetArgs = {},
netCoreDllPath = "",
gc = {
conserveMemory = 0,
heapCount = 2,
noAffinitize = true,
server = true,
},
},
enableAdaptiveLspServer = true,
-- ExcludeProjectDirectories: string[] option = [||]
excludeProjectDirectories = { "paket-files", ".fable", "packages", "node_modules" },
-- KeywordsAutocomplete: bool option false
keywordsAutocomplete = true,
-- ExternalAutocomplete: bool option false
externalAutocomplete = false,
-- Linter: bool option false
linter = true,
-- IndentationSize: int option 4
indentationSize = 2,
-- UnionCaseStubGeneration: bool option false
unionCaseStubGeneration = true,
-- UnionCaseStubGenerationBody: string option """failwith "Not Implemented" """
unionCaseStubGenerationBody = 'failwith "Not Implemented"',
-- RecordStubGeneration: bool option false
recordStubGeneration = true,
-- RecordStubGenerationBody: string option "failwith \"Not Implemented\""
recordStubGenerationBody = 'failwith "Not Implemented"',
-- InterfaceStubGeneration: bool option false
interfaceStubGeneration = true,
-- InterfaceStubGenerationObjectIdentifier: string option "this"
interfaceStubGenerationObjectIdentifier = "this",
-- InterfaceStubGenerationMethodBody: string option "failwith \"Not Implemented\""
interfaceStubGenerationMethodBody = 'failwith "Not Implemented"',
-- UnusedOpensAnalyzer: bool option false
unusedOpensAnalyzer = true,
-- UnusedDeclarationsAnalyzer: bool option false
unusedDeclarationsAnalyzer = true,
-- SimplifyNameAnalyzer: bool option false
simplifyNameAnalyzer = true,
-- ResolveNamespaces: bool option false
resolveNamespaces = true,
-- EnableAnalyzers: bool option false
enableAnalyzers = true,
-- AnalyzersPath: string[] option
analyzersPath = { "packages/Analyzers", "analyzers" },
-- DisableInMemoryProjectReferences: bool option false|
-- disableInMemoryProjectReferences = false,
-- LineLens: LineLensConfig option
lineLens = { enabled = "always", prefix = "ll//" },
-- enables the use of .Net Core SDKs for script file type-checking and evaluation,
-- otherwise the .Net Framework reference lists will be used.
-- Recommended default value: `true`.
--
useSdkScripts = true,
suggestSdkScripts = true,
-- DotNetRoot - the path to the dotnet sdk. usually best left alone, the compiler searches for this on it's own,
dotnetRoot = "",
-- FSIExtraParameters: string[]
-- an array of additional runtime arguments that are passed to FSI.
-- These are used when typechecking scripts to ensure that typechecking has the same context as your FSI instances.
-- An example would be to set the following parameters to enable Preview features (like opening static classes) for typechecking.
-- defaults to {}
fsiExtraParameters = {},
-- FSICompilerToolLocations: string[]|nil
-- passes along this list of locations to compiler tools for FSI to the FSharpCompilerServiceChecker
-- to this function in fsautocomplete
-- https://github.com/fsharp/FsAutoComplete/blob/main/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs#L99
-- which effectively just prepends "--compilertool:" to each entry and tells the FSharpCompilerServiceChecker about it and the fsiExtraParameters
fsiCompilerToolLocations = {},
-- TooltipMode: string option
-- TooltipMode can be one of the following:
-- "full" -> this provides the most verbose output
-- "summary" -> this is a slimmed down version of the tooltip
-- "" or nil -> this is the old or default way, and calls TipFormatter.FormatCommentStyle.Legacy on the lsp server... *shrug*
tooltipMode = "full",
-- GenerateBinlog
-- if true, binary logs will be generated and placed in the directory specified. They will have names of the form `{directory}/{project_name}.binlog`
-- defaults to false
generateBinlog = false,
abstractClassStubGeneration = true,
abstractClassStubGenerationObjectIdentifier = "this",
abstractClassStubGenerationMethodBody = 'failwith "Not Implemented"',
-- configures which parts of the CodeLens are enabled, if any
-- defaults to both signature and references being true
codeLenses = {
signature = { enabled = true },
references = { enabled = true },
},
inlayHints = {
--do these really annoy anyone? why not have em on?
enabled = true,
typeAnnotations = true,
-- Defaults to false, the more info the better, right?
disableLongTooltip = false,
parameterNames = true,
},
debug = {
dontCheckRelatedFiles = false,
checkFileDebouncerTimeout = 250,
logDurationBetweenCheckFiles = false,
logCheckFileDuration = false,
},
}
}Default: not set
Ionide-Nvim does not provide default keybindings for various LSP features, so you will have to set them yourself.
- If you are using neovim's built-in LSP client, see here.
Default: enabled
By default, Ionide-Nvim creates an AutoCommand so that CodeLens gets refreshed automatically.
You can disable this by setting the below option:
ionide={
IonideNvimSettings={
---defaults to true
AutomaticCodeLensRefresh = false,
},
settings={
FSharp={
codeLenses={
references= {enabled = true},
signature= {enabled = true},
},
},
},
}- Some of the settings may not work in Ionide-Nvim as it is lacking the corresponding feature of Ionide-VSCode.
Default: enabled
ionide={
IonideNvimSettings={
---defaults to true
AutomaticWorkspaceInit = false,
},
},Default: `4'
ionide={
settings={
FSharp={
workspaceModePeekDeepLevel = 4,
},
},
}Default: empty
ionide={
settings={
FSharp={
excludeProjectDirectories = { "paket-files", ".fable", "packages", "node_modules" },
},
},
}Linting (other than the basic ones described above) and formatting is powered by independent tools, FSharpLint and Fantomas respectively.
Both uses their own JSON file for configuration and Ionide-Nvim does not control them. See their docs about configuration: FSharpLint and Fantomas.
- The primary maintainer for this repository is @WillEhrendreich.