More F# and .NET stuff (#43)

This commit is contained in:
Patrick Stevens
2024-03-27 20:30:31 +00:00
committed by GitHub
parent e91fb514fe
commit b69b9248f9
4 changed files with 269 additions and 47 deletions

View File

@@ -261,7 +261,7 @@
vimdiffAlias = true;
withPython3 = true;
extraLuaConfig = builtins.readFile ./nvim/build-utils.lua + "\n" + builtins.replaceStrings ["%PYTHONENV%"] ["${pythonEnv}"] (builtins.readFile ./nvim/init.lua);
extraLuaConfig = builtins.readFile ./nvim/build-utils.lua + "\n" + builtins.readFile ./nvim/dotnet.lua + "\n" + builtins.replaceStrings ["%PYTHONENV%"] ["${pythonEnv}"] (builtins.readFile ./nvim/init.lua);
package = nixpkgs.neovim-nightly;
};

View File

@@ -0,0 +1,158 @@
local dotnet_has_set_status_line
function DetachSolution()
vim.g.current_sln_path = nil
-- TODO: unregister key bindings again
end
local function on_line(data, _, context)
-- Keep the window alive if there were warnings
if string.match(data, "%s[1-9]%d* Warning%(s%)") then
context.warn = context.warn + 1
end
end
local function on_complete(context, code, _)
if code ~= 0 then
print("Exit code " .. code)
context.errs = context.errs + 1
end
if context.errs == 0 and context.warn == 0 then
-- Close the temporary floating window (but keep it alive if the
-- cursor is in it)
local cur_win = vim.api.nvim_get_current_win()
local cur_buf = vim.api.nvim_win_get_buf(cur_win)
if cur_buf ~= context.buffer then
vim.api.nvim_win_close(context.window, true)
end
print("All builds successful")
end
end
function GetCurrentSln()
if vim.g.current_sln_path then
return vim.g.current_sln_path
else
return nil
end
end
function BuildDotNetSolution()
if vim.g.current_sln_path then
local context = BuildUtils.create_window()
context.errs = 0
context.warn = 0
BuildUtils.run("dotnet", { "build", vim.g.current_sln_path }, "dotnet build", context, on_line, on_complete)
end
end
function TestDotNetSolution()
if vim.g.current_sln_path then
local context = BuildUtils.create_window()
context.warn = 0
context.errs = 0
BuildUtils.run("dotnet", { "test", vim.g.current_sln_path }, "dotnet test", context, on_line, on_complete)
end
end
function CurrentSlnOrEmpty()
local sln = GetCurrentSln()
if sln then
return sln
else
return ""
end
end
function RegisterSolution(sln_path)
vim.g.current_sln_path = sln_path
if not dotnet_has_set_status_line then
dotnet_has_set_status_line = true
vim.o.statusline = vim.o.statusline .. " %{v:lua.CurrentSlnOrEmpty()}"
end
local status, whichkey = pcall(require, "which-key")
if status then
whichkey.register({
s = {
name = ".NET solution",
b = { BuildDotNetSolution, "Build .NET solution" },
t = { TestDotNetSolution, "Test .NET solution" },
},
}, { prefix = vim.api.nvim_get_var("maplocalleader"), buffer = vim.api.nvim_get_current_buf() })
else
vim.api.nvim_set_keymap("n", "<localleader>sb", ":call BuildDotNetSolution", { noremap = true })
end
end
local function find_nearest_slns()
local path = vim.fn.expand("%:p:h") -- Get the full path of the current buffer's directory
while path and path ~= "/" do
local sln_paths = vim.fn.glob(path .. "/*.sln", nil, true)
if #sln_paths > 0 then
return sln_paths
end
path = vim.fn.fnamemodify(path, ":h") -- Move up one directory
end
return {}
end
local function FindAndRegisterSolution()
local solutions = find_nearest_slns()
if not solutions or #solutions == 0 then
print("No .sln file found in any parent directory.")
return
elseif #solutions == 1 then
-- Exactly one solution found; register it directly
RegisterSolution(solutions[1])
elseif #solutions > 1 then
-- Multiple solutions found; use Telescope to pick one
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
local conf = require("telescope.config").values
pickers
.new({}, {
prompt_title = "Select a Solution File",
finder = finders.new_table({
results = solutions,
entry_maker = function(entry)
return {
value = entry,
display = entry,
ordinal = entry,
}
end,
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_bufnr, _)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
actions.close(prompt_bufnr)
RegisterSolution(selection.value)
end)
return true
end,
})
:find()
end
end
vim.api.nvim_create_autocmd({ "BufReadPost", "BufNewFile" }, {
pattern = "*.sln",
callback = function()
RegisterSolution(vim.fn.expand("%:p"))
end,
})
vim.api.nvim_create_autocmd("FileType", {
pattern = { "fsharp", "cs" },
callback = function()
FindAndRegisterSolution()
end,
})

View File

@@ -99,7 +99,7 @@ function HasPaste()
return ""
end
vim.o.statusline = vim.o.statusline .. "%{v:lua.HasPaste()}%F%m%r%h %w CWD: %r%{getcwd()}%h Line: %l Column: %c"
vim.o.statusline = vim.o.statusline .. "%{v:lua.HasPaste()}%F%m%r%h %w Line: %l Column: %c"
--------------------------------------------------------------
@@ -186,7 +186,6 @@ if status then
print("-----")
end
end
-- TODO: If a command is a prefix of an existing command, prepend its description to those commands' descriptions, and append a '...' to the parent's description.
end
end)
end

View File

@@ -65,7 +65,7 @@ local function BuildFSharpProjects(projects)
-- cursor is in it)
local cur_win = vim.api.nvim_get_current_win()
local cur_buf = vim.api.nvim_win_get_buf(cur_win)
if cur_buf ~= context.buf then
if cur_buf ~= context.buffer then
vim.api.nvim_win_close(context.window, true)
end
print("All builds successful")
@@ -94,45 +94,6 @@ local function BuildFSharpProjects(projects)
end
end
-- local function fsprojAndDirCompletion(ArgLead, _, _)
-- local results = {}
-- local loc = ArgLead
-- if not loc then
-- loc = "."
-- end
-- local command = string.format(
-- "find "
-- .. vim.fn.shellescape(loc)
-- .. " -maxdepth 2 \\( -type f -name '*.fsproj' -o -type d \\) -print0 2> /dev/null"
-- )
-- local handle = io.popen(command)
-- if handle then
-- local stdout = handle:read("*all")
-- handle:close()
--
-- local allResults = {}
-- for match in string.gmatch(stdout, "([^%z]+)") do
-- table.insert(allResults, match)
-- end
-- table.sort(allResults, function(a, b)
-- local aEndsWithProj = a:match("proj$")
-- local bEndsWithProj = b:match("proj$")
-- if aEndsWithProj and not bEndsWithProj then
-- return true
-- elseif not aEndsWithProj and bEndsWithProj then
-- return false
-- else
-- return a < b -- If both or neither end with 'proj', sort alphabetically
-- end
-- end)
--
-- for _, line in ipairs(allResults) do
-- table.insert(results, line)
-- end
-- end
-- return results
-- end
vim.api.nvim_create_user_command("BuildFSharpProject", function(opts)
if opts.fargs and opts.fargs[1] then
BuildFSharpProjects(opts.fargs)
@@ -144,7 +105,7 @@ vim.api.nvim_create_user_command("BuildFSharpProject", function(opts)
local actions = require("telescope.actions")
pickers
.new({}, {
prompt_title = "Actions",
prompt_title = "Projects",
finder = finders.new_table({
results = captureLoadedProjects(),
}),
@@ -162,6 +123,101 @@ vim.api.nvim_create_user_command("BuildFSharpProject", function(opts)
end
end, { nargs = "?", complete = "file" })
local function TableConcat(tables)
local result = {}
for _, tab in ipairs(tables) do
for _, v in ipairs(tab) do
table.insert(result, v)
end
end
return result
end
-- args is a table that will be splatted into the command line and will be immediately
-- followed by the project.
local function RunDotnet(command, args, project, configuration)
local function on_line(data, _, context) end
local function on_complete(context, code, signal) end
local context = BuildUtils.create_window()
BuildUtils.run(
"dotnet",
TableConcat({ { command }, args, { project, "--configuration", configuration } }),
"dotnet",
context,
on_line,
on_complete
)
end
-- Call this as:
-- RunFSharpProject path/to/fsproj
-- RunFSharpProject Debug path/to/fsproj
vim.api.nvim_create_user_command("RunFSharpProject", function(opts)
local configuration = "Release"
if opts.fargs and opts.fargs[1] and opts.fargs[1]:match("sproj$") then
RunDotnet("run", { "--project" }, opts.fargs[1], configuration)
elseif opts.fargs and opts.fargs[1] and opts.fargs[2] then
configuration = opts.fargs[1]
RunDotnet("run", { "--project" }, opts.fargs[2], configuration)
else
configuration = opts.fargs[1]
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
local action_state = require("telescope.actions.state")
local actions = require("telescope.actions")
pickers
.new({}, {
prompt_title = "Projects",
finder = finders.new_table({
results = captureLoadedProjects(),
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_buf, _)
actions.select_default:replace(function()
actions.close(prompt_buf)
local selection = action_state.get_selected_entry()
RunDotnet("run", { "--project" }, selection.value, configuration)
end)
return true
end,
})
:find()
end
end, { nargs = "*", complete = "file" })
vim.api.nvim_create_user_command("PublishFSharpProject", function(opts)
if opts.fargs and opts.fargs[1] then
RunDotnet("publish", {}, opts.fargs[1], "Release")
else
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
local action_state = require("telescope.actions.state")
local actions = require("telescope.actions")
pickers
.new({}, {
prompt_title = "Projects",
finder = finders.new_table({
results = captureLoadedProjects(),
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_buf, _)
actions.select_default:replace(function()
actions.close(prompt_buf)
local selection = action_state.get_selected_entry()
RunDotnet("publish", {}, selection.value, "Release")
end)
return true
end,
})
:find()
end
end, { nargs = "*", complete = "file" })
vim.api.nvim_create_autocmd("FileType", {
pattern = "fsharp",
callback = function()
@@ -169,12 +225,21 @@ vim.api.nvim_create_autocmd("FileType", {
if status then
whichkey.register({
f = {
name = "F#",
t = { ":call fsharp#showTooltip()<CR>", "Show F# Tooltip" },
["si"] = { ":call fsharp#toggleFsi()<CR>", "Toggle FSI (F# Interactive)" },
["sl"] = { ":call fsharp#sendLineToFsi()<cr>", "Send line to FSI (F# Interactive)" },
},
b = {
r = {
name = "Run F# project",
d = { ":RunFSharpProject Debug", "Run F# project in debug configuration" },
r = { ":RunFSharpProject Release", "Run F# project in release configuration" },
},
p = {
":PublishFSharpProject",
"Publish F# project",
},
b = {
"Build F# project",
a = { BuildFSharpProjects, "Build all projects" },
s = { ":BuildFSharpProject", "Build specified project" },
},
@@ -184,7 +249,7 @@ vim.api.nvim_create_autocmd("FileType", {
vim.api.nvim_set_keymap("n", "<localleader>ft", ":call fsharp#showTooltip()<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>fsi", ":call fsharp#toggleFsi()<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>fsl", ":call fsharp#sendLineToFsi()<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>bpa", BuildFSharpProjects, { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>bpa", ":lua BuildFSharpProjects()", { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>bps", ":BuildFSharpProject", { noremap = true })
end
end,