Skip to content
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
8 changes: 8 additions & 0 deletions doc/tags
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
projections-table-of-contents projections.txt /*projections-table-of-contents*
projections-🛸-projections.nvim projections.txt /*projections-🛸-projections.nvim*
projections-🛸-projections.nvim-❓-further-queries projections.txt /*projections-🛸-projections.nvim-❓-further-queries*
projections-🛸-projections.nvim-💻-api projections.txt /*projections-🛸-projections.nvim-💻-api*
projections-🛸-projections.nvim-🔌-installation projections.txt /*projections-🛸-projections.nvim-🔌-installation*
projections-🛸-projections.nvim-🔭-about-telescope projections.txt /*projections-🛸-projections.nvim-🔭-about-telescope*
projections-🛸-projections.nvim-🗺️-quick-guide projections.txt /*projections-🛸-projections.nvim-🗺️-quick-guide*
projections.txt projections.txt /*projections.txt*
11 changes: 11 additions & 0 deletions lua/projections/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ local Path = require("projections.path")
---@field workspaces table
---@field patterns Patterns
---@field workspaces_file Path
---@field projects_file Path
---@field sessions_directory Path
---@field skip_session_check boolean
local Config = {}
Config.__index = Config

Expand All @@ -20,7 +22,10 @@ function Config.new()
config.workspaces = {}
config.patterns = { '.git', '.svn', '.hg' }
config.workspaces_file = Path.new(vim.fn.stdpath("data")) .. "projections_workspaces.json"
config.projects_file = Path.new(vim.fn.stdpath("data")) .. "projections_projects.json"
config.sessions_directory = Path.new(vim.fn.stdpath("cache")) .. "projections_sessions"
config.skip_session_check = false

return config
end

Expand All @@ -35,14 +40,20 @@ M.merge = function(conf)
M.config.workspaces_file = Path.new(conf.workspaces_file)
conf.workspaces_file = nil
end
if conf.projects_file ~= nil then
M.config.projects_file = Path.new(conf.projects_file)
conf.projects_file = nil
end
if conf.sessions_directory ~= nil then
M.config.sessions_directory = Path.new(conf.sessions_directory)
conf.sessions_directory = nil
end
M.config = vim.tbl_deep_extend("force", M.config, conf)
-- tbl_deep_extend doesn't copy metatables reliably
M.config.workspaces_file = setmetatable(M.config.workspaces_file, Path)
M.config.projects_file = setmetatable(M.config.projects_file, Path)
M.config.sessions_directory = setmetatable(M.config.sessions_directory, Path)

return M.config
end

Expand Down
2 changes: 2 additions & 0 deletions lua/projections/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ local M = {}
---@field workspaces table|nil
---@field patterns Patterns|nil
---@field workspaces_file string|nil
---@field projects_file string|nil
---@field sessions_directory string|nil
---@field skip_session_check boolean

-- Setup projections
---@param conf ConfigUser
Expand Down
97 changes: 97 additions & 0 deletions lua/projections/project.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
local Path = require("projections.path")
local utils = require("projections.utils")
local config = require("projections.config").config
local validators = require("projections.validators")

---@class Project
---@field name string Name of the project
---@field workspace Workspace Workspace project is from
Expand All @@ -23,4 +28,96 @@ function Project:path()
return self.workspace.path .. self.name
end

---@alias ProjectJSON { name: string, workspace: string }

-- Deserialize project from table
---@param tbl ProjectJSON string or table of values
---@return Project
---@nodiscard
function Project.deserialize(tbl)
local Workspace = require("projections.workspace")

validators.assert_type(
{ "table" },
tbl,
"Please consult README/wiki for a spec for the format. projects -"
)
return Project.new(tbl.name, Workspace.new(Path.new(tbl.workspace), {}))
end

-- Ensure that persistent projects file is present
-- @return boolean
function Project._ensure_persistent_file()
local file = io.open(tostring(config.projects_file), "a")
if file == nil then
vim.notify("projections: cannot access projects file", vim.log.levels.ERROR)
return false
end
file:close()
return true
end

function Project.get_projects()
local projects = {}
if vim.fn.filereadable(tostring(config.projects_file)) == 1 then
local lines = vim.fn.readfile(tostring(config.projects_file))
if next(lines) == nil then
return {}
end

local projects_json = vim.fn.json_decode(lines)
validators.validate_projects_json(projects_json)

for _, proj in ipairs(projects_json) do
table.insert(projects, Project.deserialize(proj))
end
end

return projects
end

function Project.add(spath)
local Workspace = require("projections.workspace")

local path = Path.new(spath)
if vim.fn.isdirectory(tostring(path)) == 0 then
vim.notify(
"projections: can't add project, not a directory",
vim.log.levels.ERROR
)
return false
end

Project._ensure_persistent_file()

local projects = Project.get_projects()
table.insert(
projects,
Project.new(
path:basename(),
Workspace.new(
Path.new(tostring(path:parent())), {}
)
)
)
projects = utils._unique_projects(projects)

local projects_serialized = {}
for _, project in ipairs(projects) do
table.insert(
projects_serialized,
{ name = tostring(project.name), workspace = tostring(project.workspace.path) }
)
end

local file = io.open(tostring(config.projects_file), "w")
if file == nil then
vim.notify("projections: cannot open projects file", vim.log.levels.ERROR)
return false
end
file:write(vim.json.encode(projects_serialized))
file:close()
return true
end

return Project
51 changes: 40 additions & 11 deletions lua/projections/session.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ function Session.info(spath)
local path = Path.new(spath)
local project_name = path:basename()
local workspace_path = path:parent()

-- allow skipping checks whether the provided path is a workspace or a project
if config.skip_session_check then
local filename = Session.session_filename(tostring(workspace_path), project_name)
return {
path = config.sessions_directory .. filename,
project = Project.new(project_name, Workspace.new(workspace_path, {}))
}
end

local all_workspaces = Workspace.get_workspaces()
local workspace = nil
for _, ws in ipairs(all_workspaces) do
Expand All @@ -27,13 +37,31 @@ function Session.info(spath)
break
end
end
if workspace == nil or not workspace:is_project(project_name) then return nil end

local filename = Session.session_filename(tostring(workspace_path), project_name)
return {
path = config.sessions_directory .. filename,
project = Project.new(project_name, workspace)
}
-- check if the path is part of a workspace
local is_workspace = workspace ~= nil and workspace:is_project(project_name)
if is_workspace then
local filename = Session.session_filename(tostring(workspace_path), project_name)
return {
path = config.sessions_directory .. filename,
project = Project.new(project_name, workspace)
}
-- check if the path was manually added as a project
else
local projects = Project.get_projects()
for _, proj in ipairs(projects) do
-- check if the path of this project is the path of some workspace
if path:parent() == proj.workspace.path and project_name == proj.name then
local filename = Session.session_filename(tostring(proj.workspace.path), proj.name)
return {
path = config.sessions_directory .. filename,
project = proj
}
end
end
end

return nil
end

-- Returns the session filename for project
Expand All @@ -43,7 +71,8 @@ end
---@nodiscard
function Session.session_filename(workspace_path, project_name)
local path_hash = utils._fnv1a(workspace_path)
return string.format("%s_%u.vim", project_name, path_hash)
--return string.format("%s_%u.vim", project_name, path_hash)
return string.format("%s__%s", path_hash, project_name)
end

-- Ensures sessions directory is available
Expand All @@ -66,10 +95,10 @@ end
---@param spath string Path to the session file
---@returns boolean
function Session.store_to_session_file(spath)
if config.store_hooks.pre ~= nil then config.store_hooks.pre() end
if config.store_hooks.pre ~= nil then config.store_hooks.pre(spath) end
-- TODO: correctly indicate errors here!
vim.cmd("mksession! " .. spath)
if config.store_hooks.post ~= nil then config.store_hooks.post() end
if config.store_hooks.post ~= nil then config.store_hooks.post(spath) end
return true
end

Expand All @@ -87,10 +116,10 @@ end
---@param spath string Path to session file
---@return boolean
function Session.restore_from_session_file(spath)
if config.restore_hooks.pre ~= nil then config.restore_hooks.pre() end
if config.restore_hooks.pre ~= nil then config.restore_hooks.pre(spath) end
-- TODO: correctly indicate errors here!
vim.cmd("silent! source " .. spath)
if config.restore_hooks.post ~= nil then config.restore_hooks.post() end
if config.restore_hooks.post ~= nil then config.restore_hooks.post(spath) end
return true
end

Expand Down
24 changes: 23 additions & 1 deletion lua/projections/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ M._fnv1a = function(s)
hash = bit.bxor(hash, s:byte(i))
hash = hash * prime
end
return tonumber(hash)


s = s:gsub("/", "__")

-- return tonumber(hash)
return s
end

-- Returns unique workspaces in list
Expand All @@ -44,4 +49,21 @@ M._unique_workspaces = function(workspaces)
return result
end

-- Returns unique projects in list
---@param projects Project[] List of projects
---@return Project[]
---@nodiscard
M._unique_projects = function(projects)
local hashmap = {}
local result = {}
for _, p in ipairs(projects) do
local hash = tostring(p:path())
if not hashmap[hash] then
result[#result + 1] = p
hashmap[hash] = true
end
end
return result
end

return M
11 changes: 11 additions & 0 deletions lua/projections/validators.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ M.validate_workspaces_json = function(workspaces)
end
end

-- Validate projects table from JSON. Throws error on failure.
---@param projects table Sequential table of projects
M.validate_projects_json = function(projects)
M.assert_type({ "table" }, projects, "projects")
for project_index, proj in ipairs(projects) do
M.assert_type({ "table" }, proj, "projects[%d]", project_index)
M.assert_type({ "string" }, proj.name, "projects[%d].name", project_index)
M.assert_type({ "string" }, proj.workspace, "projects[%d].workspace", project_index)
end
end

-- Validate projections config passed to setup. Throws error on failure.
---@param config ConfigUser Config passed to projections by user
M.validate_user_config = function(config)
Expand Down
8 changes: 8 additions & 0 deletions lua/telescope/_extensions/projections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local finders = require("telescope.finders")
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
local conf = require("telescope.config").values
local utils = require("projections.utils")

local function project_finder(opts)
local workspaces = require("projections.workspace").get_workspaces()
Expand All @@ -15,6 +16,13 @@ local function project_finder(opts)
end
end

local manual_projects = require("projections.project").get_projects()
for _, project in ipairs(manual_projects) do
table.insert(projects, project)
end

projects = utils._unique_projects(projects)

return finders.new_table({
results = projects,
entry_maker = opts.entry_maker or function(project)
Expand Down