aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-core/fs.lua
blob: 5b194c4f49feeb76136204f5bb8b99c2b8bdd542 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
local Path = require "mason-core.path"
local a = require "mason-core.async"
local log = require "mason-core.log"
local settings = require "mason.settings"

local function make_module(uv)
    local M = {}

    ---@param path string
    function M.fstat(path)
        log.trace("fs: fstat", path)
        local fd = uv.fs_open(path, "r", 438)
        local fstat = uv.fs_fstat(fd)
        uv.fs_close(fd)
        return fstat
    end

    ---@param path string
    function M.file_exists(path)
        log.trace("fs: file_exists", path)
        local ok, fstat = pcall(M.fstat, path)
        if not ok then
            return false
        end
        return fstat.type == "file"
    end

    ---@param path string
    function M.dir_exists(path)
        log.trace("fs: dir_exists", path)
        local ok, fstat = pcall(M.fstat, path)
        if not ok then
            return false
        end
        return fstat.type == "directory"
    end

    ---@param path string
    function M.rmrf(path)
        assert(
            Path.is_subdirectory(settings.current.install_root_dir, path),
            ("Refusing to rmrf %q which is outside of the allowed boundary %q. Please report this error at https://github.com/mason-org/mason.nvim/issues/new"):format(
                path,
                settings.current.install_root_dir
            )
        )
        log.debug("fs: rmrf", path)
        if vim.in_fast_event() then
            a.scheduler()
        end
        if vim.fn.delete(path, "rf") ~= 0 then
            log.debug "fs: rmrf failed"
            error(("rmrf: Could not remove directory %q."):format(path))
        end
    end

    ---@param path string
    function M.unlink(path)
        log.debug("fs: unlink", path)
        uv.fs_unlink(path)
    end

    ---@param path string
    function M.mkdir(path)
        log.debug("fs: mkdir", path)
        uv.fs_mkdir(path, 493) -- 493(10) == 755(8)
    end

    ---@param path string
    function M.mkdirp(path)
        log.debug("fs: mkdirp", path)
        if vim.in_fast_event() then
            a.scheduler()
        end
        if vim.fn.mkdir(path, "p") ~= 1 then
            log.debug "fs: mkdirp failed"
            error(("mkdirp: Could not create directory %q."):format(path))
        end
    end

    ---@param path string
    ---@param new_path string
    function M.rename(path, new_path)
        log.debug("fs: rename", path, new_path)
        uv.fs_rename(path, new_path)
    end

    ---@param path string
    ---@param new_path string
    ---@param flags table? { excl?: boolean, ficlone?: boolean, ficlone_force?: boolean }
    function M.copy_file(path, new_path, flags)
        log.debug("fs: copy_file", path, new_path, flags)
        uv.fs_copyfile(path, new_path, flags)
    end

    ---@param path string
    ---@param contents string
    ---@param flags string? Defaults to "w".
    function M.write_file(path, contents, flags)
        log.trace("fs: write_file", path)
        local fd = uv.fs_open(path, flags or "w", 438)
        uv.fs_write(fd, contents, -1)
        uv.fs_close(fd)
    end

    ---@param path string
    ---@param contents string
    function M.append_file(path, contents)
        M.write_file(path, contents, "a")
    end

    ---@param path string
    function M.read_file(path)
        log.trace("fs: read_file", path)
        local fd = uv.fs_open(path, "r", 438)
        local fstat = uv.fs_fstat(fd)
        local contents = uv.fs_read(fd, fstat.size, 0)
        uv.fs_close(fd)
        return contents
    end

    ---@alias ReaddirEntry {name: string, type: string}

    ---@param path string: The full path to the directory to read.
    ---@return ReaddirEntry[]
    function M.readdir(path)
        log.trace("fs: fs_opendir", path)
        local dir = assert(vim.loop.fs_opendir(path, nil, 25))
        local all_entries = {}
        local exhausted = false

        repeat
            local entries = uv.fs_readdir(dir)
            log.trace("fs: fs_readdir", path, entries)
            if entries and #entries > 0 then
                for i = 1, #entries do
                    if entries[i].name and not entries[i].type then
                        -- See https://github.com/luvit/luv/issues/660
                        local full_path = Path.concat { path, entries[i].name }
                        log.trace("fs: fs_readdir falling back to fs_stat to find type", full_path)
                        local stat = uv.fs_stat(full_path)
                        entries[i].type = stat.type
                    end
                    all_entries[#all_entries + 1] = entries[i]
                end
            else
                log.trace("fs: fs_readdir exhausted scan", path)
                exhausted = true
            end
        until exhausted

        uv.fs_closedir(dir)

        return all_entries
    end

    ---@param path string
    ---@param new_path string
    function M.symlink(path, new_path)
        log.trace("fs: symlink", path, new_path)
        uv.fs_symlink(path, new_path)
    end

    ---@param path string
    ---@param mode integer
    function M.chmod(path, mode)
        log.trace("fs: chmod", path, mode)
        uv.fs_chmod(path, mode)
    end

    return M
end

return {
    async = make_module(require "mason-core.async.uv"),
    sync = make_module(vim.loop),
}