Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep track of current project #30

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ use({
local switcher = require("projections.switcher")
vim.api.nvim_create_autocmd({ "VimEnter" }, {
callback = function()
if vim.fn.argc() == 0 then switcher.switch(vim.loop.cwd()) end
if vim.fn.argc() == 0 then switcher:switch(vim.loop.cwd()) end
end,
})
end
Expand All @@ -140,13 +140,14 @@ If you are using the recommended configuration, make sure to remove the
-- If in some project's root, attempt to restore that project's session
-- If not, restore last session
-- If no sessions, do nothing
local Switcher = require("projections.switcher")
local Session = require("projections.session")
vim.api.nvim_create_autocmd({ "VimEnter" }, {
callback = function()
if vim.fn.argc() ~= 0 then return end
local session_info = Session.info(vim.loop.cwd())
if session_info == nil then
Session.restore_latest()
Switcher.last()
else
Session.restore(vim.loop.cwd())
end
Expand Down
1 change: 1 addition & 0 deletions lua/projections/project.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ end
---@return Path
---@nodiscard
function Project:path()
if not self.workspace or not self.name then return Path.new("") end
return self.workspace.path .. self.name
end

Expand Down
23 changes: 6 additions & 17 deletions lua/projections/session.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@ function Session.info(spath)
local path = Path.new(spath)
local project_name = path:basename()
local workspace_path = path:parent()
local all_workspaces = Workspace.get_workspaces()
local workspace = nil
for _, ws in ipairs(all_workspaces) do
if workspace_path == ws.path then
workspace = ws
break
end
end
local workspace = Workspace.find(workspace_path)
if workspace == nil or not workspace:is_project(project_name) then return nil end

local filename = Session.session_filename(tostring(workspace_path), project_name)
Expand Down Expand Up @@ -58,7 +51,11 @@ end
function Session.store(spath)
Session._ensure_sessions_directory()
local session_info = Session.info(spath)
if session_info == nil then return false end
local Switcher = require("projections.switcher")
-- Don't store session if no session info or currently not in a project
if session_info == nil or not Switcher:in_project() then
return false
end
return Session.store_to_session_file(tostring(session_info.path))
end

Expand Down Expand Up @@ -112,12 +109,4 @@ function Session.latest()
return latest_session
end

-- Restore latest session
---@return boolean
function Session.restore_latest()
local latest_session = Session.latest()
if latest_session == nil then return false end
return Session.restore_from_session_file(tostring(latest_session))
end

return Session
111 changes: 96 additions & 15 deletions lua/projections/switcher.lua
Original file line number Diff line number Diff line change
@@ -1,28 +1,109 @@
local Session = require("projections.session")
local Project = require("projections.project")
local utils = require("projections.utils")

local M = {}

M._current = Project;

-- Attempts to return the current active project
----@return ProjectInfo
function M:get_current()
return self._current;
end

-- Returns whether or not we are currently in a project
function M:in_project()
return (#tostring(self._current:path()) > 0)
end

-- Attempts to set the current active project, with no args passed, unsets current project
-- @param project_info ProjectInfo table of information about the project to set as the current one
----@return boolean
function M:set_current(project_info)
if project_info == nil then
self._current = Project
else
self._current = project_info
end
return true
end

-- Attempts to switch to the last loaded project
---@return boolean
function M:last()
local latest_session = Session.latest()
if latest_session ~= nil then
local project_dir = utils.project_dir_from_session_file(tostring(latest_session))
-- "expand" for OS compatiblity (Windows)
project_dir = vim.fn.expand(project_dir)
return self:switch(project_dir)
end
return false
end


-- Attempts to switch projects and load the session file.
---@param spath string Path to project root
---@param new_project Project table describing project to switch to
---@return boolean
M.switch = function(spath)
if utils._unsaved_buffers_present() then
vim.notify("projections: Unsaved buffers. Unable to switch projects", vim.log.levels.WARN)
return false
end
function M:switch_project(new_project)
local new_path = vim.fn.expand(tostring(new_project:path()))
local current_path = vim.fn.expand(tostring(self._current and self._current:path() or Path.new("")))
if #new_path == 0 or new_path == current_path then return false end

if utils._unsaved_buffers_present() then
vim.notify("projections: Unsaved buffers. Unable to switch projects", vim.log.levels.WARN)
return false
end

local session_info = Session.info(spath)
if session_info == nil then return false end
-- Store current session before moving on to the new project
if new_path ~= vim.loop.cwd() then
Session.store(vim.loop.cwd())
end

if vim.loop.cwd() ~= spath then Session.store(vim.loop.cwd()) end
vim.cmd("noautocmd cd " .. spath)
-- Close any existing buffers
if utils._num_valid_buffers() > 0 then
vim.cmd [[
silent! %bdelete
clearjumps
]]
Session.restore(spath)
return true
silent! %bdelete
clearjumps
]]
end

-- If there's a session for the project we're switching to, attempt to restore it
local session_info = Session.info(new_path)
if session_info ~= nil then
if Session.restore(new_path) then
vim.schedule(function() vim.notify("[projections.nvim]: Restored session for project - " .. new_project.name, vim.log.levels.INFO) end)
else
-- Something went wrong restoring session
return false
end
else
-- Otherwise, project with no existing session
-- Update current dir to new project dir
vim.cmd("noautocmd cd " .. new_path)
end

-- Formally set the current project
self._current = new_project

return true
end

-- Attempts to switch projects using only a path to the desired project dir
---@param spath string Path to project root
---@return boolean
function M:switch(spath)
local Workspace = require("projections.workspace")
-- check if path is some project's root
local path = Path.new(spath)
local project_name = path:basename()
local workspace_path = path:parent()
local workspace = Workspace.find(workspace_path)
if workspace == nil or not workspace:is_project(project_name) then return nil end

return self:switch_project(Project.new(project_name, workspace))
end


return M
34 changes: 34 additions & 0 deletions lua/projections/utils.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
local M = {}

-- Splits a string at all instances of provided 'separator'
---@param input_string string String to separate
---@param sep string Separator to split on
---@return Table
local split = function(input_string, sep)
local fields = {}
local pattern = string.format("([^%s]+)", sep)
local _ = string.gsub(input_string, pattern, function(c)
fields[#fields + 1] = c
end)

return fields
end

-- Gets a project directory from a session file
---@param filepath string Filepath for session file
---@return string
M.project_dir_from_session_file = function(filepath)
local session = vim.fn.readfile(filepath)
-- Directory for session is found on line 6. It is preceded by "cd ", so we take a substring
local project_dir = string.sub(session[6], 4, -1)
return project_dir
end

-- Checks if unsaved buffers are present
---@return boolean
---@nodiscard
Expand All @@ -12,6 +36,16 @@ M._unsaved_buffers_present = function()
return false
end

-- Gets number of valid buffers currently open
---@return integer
M._num_valid_buffers = function()
local get_ls = vim.tbl_filter(function(buf)
return vim.api.nvim_buf_is_valid(buf)
and vim.api.nvim_buf_get_option(buf, 'buflisted')
end, vim.api.nvim_list_bufs())
return #get_ls
end

-- Calculate fnv1a hash
---@param s string String to hash
---@return integer
Expand Down
16 changes: 16 additions & 0 deletions lua/projections/workspace.lua
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@ function Workspace.get_workspaces()
return utils._unique_workspaces(workspaces)
end

-- Returns the workspace for a given workspace path
---@param workspace_path Path Workspace path of workspace to search for
---@return Workspace[]
---@nodiscard
function Workspace.find(workspace_path)
local all_workspaces = Workspace.get_workspaces()
local workspace = nil
for _, ws in ipairs(all_workspaces) do
if workspace_path == ws.path then
workspace = ws
break
end
end
return workspace
end

-- Add workspace to workspaces file
---@param spath string String representation of path. Can be unnormalized
---@param patterns Patterns The patterns for workspace
Expand Down
2 changes: 1 addition & 1 deletion lua/telescope/_extensions/projections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ local find_projects = function(opts)
if opts.action == nil then
opts.action = function(selected)
if selected ~= nil and selected.value ~= vim.loop.cwd() then
switcher.switch(selected.value)
switcher:switch(selected.value)
end
end
end
Expand Down