tangled
alpha
login
or
join now
robinwobin.dev
/
artio.nvim
3
fork
atom
minimal extui fuzzy finder for neovim
3
fork
atom
overview
issues
pulls
pipelines
feat: init
robinwobin.dev
3 months ago
a8875ab4
+1000
9 changed files
expand all
collapse all
unified
split
LICENSE
lua
artio
config.lua
init.lua
picker.lua
view.lua
plugin
artio.lua
selene.toml
stylua.toml
vim.toml
+287
LICENSE
···
1
1
+
EUROPEAN UNION PUBLIC LICENCE v. 1.2
2
2
+
EUPL © the European Union 2007, 2016
3
3
+
4
4
+
This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined
5
5
+
below) which is provided under the terms of this Licence. Any use of the Work,
6
6
+
other than as authorised under this Licence is prohibited (to the extent such
7
7
+
use is covered by a right of the copyright holder of the Work).
8
8
+
9
9
+
The Work is provided under the terms of this Licence when the Licensor (as
10
10
+
defined below) has placed the following notice immediately following the
11
11
+
copyright notice for the Work:
12
12
+
13
13
+
Licensed under the EUPL
14
14
+
15
15
+
or has expressed by any other means his willingness to license under the EUPL.
16
16
+
17
17
+
1. Definitions
18
18
+
19
19
+
In this Licence, the following terms have the following meaning:
20
20
+
21
21
+
- ‘The Licence’: this Licence.
22
22
+
23
23
+
- ‘The Original Work’: the work or software distributed or communicated by the
24
24
+
Licensor under this Licence, available as Source Code and also as Executable
25
25
+
Code as the case may be.
26
26
+
27
27
+
- ‘Derivative Works’: the works or software that could be created by the
28
28
+
Licensee, based upon the Original Work or modifications thereof. This Licence
29
29
+
does not define the extent of modification or dependence on the Original Work
30
30
+
required in order to classify a work as a Derivative Work; this extent is
31
31
+
determined by copyright law applicable in the country mentioned in Article 15.
32
32
+
33
33
+
- ‘The Work’: the Original Work or its Derivative Works.
34
34
+
35
35
+
- ‘The Source Code’: the human-readable form of the Work which is the most
36
36
+
convenient for people to study and modify.
37
37
+
38
38
+
- ‘The Executable Code’: any code which has generally been compiled and which is
39
39
+
meant to be interpreted by a computer as a program.
40
40
+
41
41
+
- ‘The Licensor’: the natural or legal person that distributes or communicates
42
42
+
the Work under the Licence.
43
43
+
44
44
+
- ‘Contributor(s)’: any natural or legal person who modifies the Work under the
45
45
+
Licence, or otherwise contributes to the creation of a Derivative Work.
46
46
+
47
47
+
- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
48
48
+
the Work under the terms of the Licence.
49
49
+
50
50
+
- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
51
51
+
renting, distributing, communicating, transmitting, or otherwise making
52
52
+
available, online or offline, copies of the Work or providing access to its
53
53
+
essential functionalities at the disposal of any other natural or legal
54
54
+
person.
55
55
+
56
56
+
2. Scope of the rights granted by the Licence
57
57
+
58
58
+
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
59
59
+
sublicensable licence to do the following, for the duration of copyright vested
60
60
+
in the Original Work:
61
61
+
62
62
+
- use the Work in any circumstance and for all usage,
63
63
+
- reproduce the Work,
64
64
+
- modify the Work, and make Derivative Works based upon the Work,
65
65
+
- communicate to the public, including the right to make available or display
66
66
+
the Work or copies thereof to the public and perform publicly, as the case may
67
67
+
be, the Work,
68
68
+
- distribute the Work or copies thereof,
69
69
+
- lend and rent the Work or copies thereof,
70
70
+
- sublicense rights in the Work or copies thereof.
71
71
+
72
72
+
Those rights can be exercised on any media, supports and formats, whether now
73
73
+
known or later invented, as far as the applicable law permits so.
74
74
+
75
75
+
In the countries where moral rights apply, the Licensor waives his right to
76
76
+
exercise his moral right to the extent allowed by law in order to make effective
77
77
+
the licence of the economic rights here above listed.
78
78
+
79
79
+
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
80
80
+
any patents held by the Licensor, to the extent necessary to make use of the
81
81
+
rights granted on the Work under this Licence.
82
82
+
83
83
+
3. Communication of the Source Code
84
84
+
85
85
+
The Licensor may provide the Work either in its Source Code form, or as
86
86
+
Executable Code. If the Work is provided as Executable Code, the Licensor
87
87
+
provides in addition a machine-readable copy of the Source Code of the Work
88
88
+
along with each copy of the Work that the Licensor distributes or indicates, in
89
89
+
a notice following the copyright notice attached to the Work, a repository where
90
90
+
the Source Code is easily and freely accessible for as long as the Licensor
91
91
+
continues to distribute or communicate the Work.
92
92
+
93
93
+
4. Limitations on copyright
94
94
+
95
95
+
Nothing in this Licence is intended to deprive the Licensee of the benefits from
96
96
+
any exception or limitation to the exclusive rights of the rights owners in the
97
97
+
Work, of the exhaustion of those rights or of other applicable limitations
98
98
+
thereto.
99
99
+
100
100
+
5. Obligations of the Licensee
101
101
+
102
102
+
The grant of the rights mentioned above is subject to some restrictions and
103
103
+
obligations imposed on the Licensee. Those obligations are the following:
104
104
+
105
105
+
Attribution right: The Licensee shall keep intact all copyright, patent or
106
106
+
trademarks notices and all notices that refer to the Licence and to the
107
107
+
disclaimer of warranties. The Licensee must include a copy of such notices and a
108
108
+
copy of the Licence with every copy of the Work he/she distributes or
109
109
+
communicates. The Licensee must cause any Derivative Work to carry prominent
110
110
+
notices stating that the Work has been modified and the date of modification.
111
111
+
112
112
+
Copyleft clause: If the Licensee distributes or communicates copies of the
113
113
+
Original Works or Derivative Works, this Distribution or Communication will be
114
114
+
done under the terms of this Licence or of a later version of this Licence
115
115
+
unless the Original Work is expressly distributed only under this version of the
116
116
+
Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
117
117
+
(becoming Licensor) cannot offer or impose any additional terms or conditions on
118
118
+
the Work or Derivative Work that alter or restrict the terms of the Licence.
119
119
+
120
120
+
Compatibility clause: If the Licensee Distributes or Communicates Derivative
121
121
+
Works or copies thereof based upon both the Work and another work licensed under
122
122
+
a Compatible Licence, this Distribution or Communication can be done under the
123
123
+
terms of this Compatible Licence. For the sake of this clause, ‘Compatible
124
124
+
Licence’ refers to the licences listed in the appendix attached to this Licence.
125
125
+
Should the Licensee's obligations under the Compatible Licence conflict with
126
126
+
his/her obligations under this Licence, the obligations of the Compatible
127
127
+
Licence shall prevail.
128
128
+
129
129
+
Provision of Source Code: When distributing or communicating copies of the Work,
130
130
+
the Licensee will provide a machine-readable copy of the Source Code or indicate
131
131
+
a repository where this Source will be easily and freely available for as long
132
132
+
as the Licensee continues to distribute or communicate the Work.
133
133
+
134
134
+
Legal Protection: This Licence does not grant permission to use the trade names,
135
135
+
trademarks, service marks, or names of the Licensor, except as required for
136
136
+
reasonable and customary use in describing the origin of the Work and
137
137
+
reproducing the content of the copyright notice.
138
138
+
139
139
+
6. Chain of Authorship
140
140
+
141
141
+
The original Licensor warrants that the copyright in the Original Work granted
142
142
+
hereunder is owned by him/her or licensed to him/her and that he/she has the
143
143
+
power and authority to grant the Licence.
144
144
+
145
145
+
Each Contributor warrants that the copyright in the modifications he/she brings
146
146
+
to the Work are owned by him/her or licensed to him/her and that he/she has the
147
147
+
power and authority to grant the Licence.
148
148
+
149
149
+
Each time You accept the Licence, the original Licensor and subsequent
150
150
+
Contributors grant You a licence to their contributions to the Work, under the
151
151
+
terms of this Licence.
152
152
+
153
153
+
7. Disclaimer of Warranty
154
154
+
155
155
+
The Work is a work in progress, which is continuously improved by numerous
156
156
+
Contributors. It is not a finished work and may therefore contain defects or
157
157
+
‘bugs’ inherent to this type of development.
158
158
+
159
159
+
For the above reason, the Work is provided under the Licence on an ‘as is’ basis
160
160
+
and without warranties of any kind concerning the Work, including without
161
161
+
limitation merchantability, fitness for a particular purpose, absence of defects
162
162
+
or errors, accuracy, non-infringement of intellectual property rights other than
163
163
+
copyright as stated in Article 6 of this Licence.
164
164
+
165
165
+
This disclaimer of warranty is an essential part of the Licence and a condition
166
166
+
for the grant of any rights to the Work.
167
167
+
168
168
+
8. Disclaimer of Liability
169
169
+
170
170
+
Except in the cases of wilful misconduct or damages directly caused to natural
171
171
+
persons, the Licensor will in no event be liable for any direct or indirect,
172
172
+
material or moral, damages of any kind, arising out of the Licence or of the use
173
173
+
of the Work, including without limitation, damages for loss of goodwill, work
174
174
+
stoppage, computer failure or malfunction, loss of data or any commercial
175
175
+
damage, even if the Licensor has been advised of the possibility of such damage.
176
176
+
However, the Licensor will be liable under statutory product liability laws as
177
177
+
far such laws apply to the Work.
178
178
+
179
179
+
9. Additional agreements
180
180
+
181
181
+
While distributing the Work, You may choose to conclude an additional agreement,
182
182
+
defining obligations or services consistent with this Licence. However, if
183
183
+
accepting obligations, You may act only on your own behalf and on your sole
184
184
+
responsibility, not on behalf of the original Licensor or any other Contributor,
185
185
+
and only if You agree to indemnify, defend, and hold each Contributor harmless
186
186
+
for any liability incurred by, or claims asserted against such Contributor by
187
187
+
the fact You have accepted any warranty or additional liability.
188
188
+
189
189
+
10. Acceptance of the Licence
190
190
+
191
191
+
The provisions of this Licence can be accepted by clicking on an icon ‘I agree’
192
192
+
placed under the bottom of a window displaying the text of this Licence or by
193
193
+
affirming consent in any other similar way, in accordance with the rules of
194
194
+
applicable law. Clicking on that icon indicates your clear and irrevocable
195
195
+
acceptance of this Licence and all of its terms and conditions.
196
196
+
197
197
+
Similarly, you irrevocably accept this Licence and all of its terms and
198
198
+
conditions by exercising any rights granted to You by Article 2 of this Licence,
199
199
+
such as the use of the Work, the creation by You of a Derivative Work or the
200
200
+
Distribution or Communication by You of the Work or copies thereof.
201
201
+
202
202
+
11. Information to the public
203
203
+
204
204
+
In case of any Distribution or Communication of the Work by means of electronic
205
205
+
communication by You (for example, by offering to download the Work from a
206
206
+
remote location) the distribution channel or media (for example, a website) must
207
207
+
at least provide to the public the information requested by the applicable law
208
208
+
regarding the Licensor, the Licence and the way it may be accessible, concluded,
209
209
+
stored and reproduced by the Licensee.
210
210
+
211
211
+
12. Termination of the Licence
212
212
+
213
213
+
The Licence and the rights granted hereunder will terminate automatically upon
214
214
+
any breach by the Licensee of the terms of the Licence.
215
215
+
216
216
+
Such a termination will not terminate the licences of any person who has
217
217
+
received the Work from the Licensee under the Licence, provided such persons
218
218
+
remain in full compliance with the Licence.
219
219
+
220
220
+
13. Miscellaneous
221
221
+
222
222
+
Without prejudice of Article 9 above, the Licence represents the complete
223
223
+
agreement between the Parties as to the Work.
224
224
+
225
225
+
If any provision of the Licence is invalid or unenforceable under applicable
226
226
+
law, this will not affect the validity or enforceability of the Licence as a
227
227
+
whole. Such provision will be construed or reformed so as necessary to make it
228
228
+
valid and enforceable.
229
229
+
230
230
+
The European Commission may publish other linguistic versions or new versions of
231
231
+
this Licence or updated versions of the Appendix, so far this is required and
232
232
+
reasonable, without reducing the scope of the rights granted by the Licence. New
233
233
+
versions of the Licence will be published with a unique version number.
234
234
+
235
235
+
All linguistic versions of this Licence, approved by the European Commission,
236
236
+
have identical value. Parties can take advantage of the linguistic version of
237
237
+
their choice.
238
238
+
239
239
+
14. Jurisdiction
240
240
+
241
241
+
Without prejudice to specific agreement between parties,
242
242
+
243
243
+
- any litigation resulting from the interpretation of this License, arising
244
244
+
between the European Union institutions, bodies, offices or agencies, as a
245
245
+
Licensor, and any Licensee, will be subject to the jurisdiction of the Court
246
246
+
of Justice of the European Union, as laid down in article 272 of the Treaty on
247
247
+
the Functioning of the European Union,
248
248
+
249
249
+
- any litigation arising between other parties and resulting from the
250
250
+
interpretation of this License, will be subject to the exclusive jurisdiction
251
251
+
of the competent court where the Licensor resides or conducts its primary
252
252
+
business.
253
253
+
254
254
+
15. Applicable Law
255
255
+
256
256
+
Without prejudice to specific agreement between parties,
257
257
+
258
258
+
- this Licence shall be governed by the law of the European Union Member State
259
259
+
where the Licensor has his seat, resides or has his registered office,
260
260
+
261
261
+
- this licence shall be governed by Belgian law if the Licensor has no seat,
262
262
+
residence or registered office inside a European Union Member State.
263
263
+
264
264
+
Appendix
265
265
+
266
266
+
‘Compatible Licences’ according to Article 5 EUPL are:
267
267
+
268
268
+
- GNU General Public License (GPL) v. 2, v. 3
269
269
+
- GNU Affero General Public License (AGPL) v. 3
270
270
+
- Open Software License (OSL) v. 2.1, v. 3.0
271
271
+
- Eclipse Public License (EPL) v. 1.0
272
272
+
- CeCILL v. 2.0, v. 2.1
273
273
+
- Mozilla Public Licence (MPL) v. 2
274
274
+
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
275
275
+
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
276
276
+
works other than software
277
277
+
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
278
278
+
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
279
279
+
Reciprocity (LiLiQ-R+).
280
280
+
281
281
+
The European Commission may update this Appendix to later versions of the above
282
282
+
licences without producing a new version of the EUPL, as long as they provide
283
283
+
the rights granted in Article 2 of this Licence and protect the covered Source
284
284
+
Code from exclusive appropriation.
285
285
+
286
286
+
All other changes or additions to this Appendix require the production of a new
287
287
+
EUPL version.
+98
lua/artio/config.lua
···
1
1
+
---@module 'artio.config'
2
2
+
3
3
+
---@class artio.config
4
4
+
---@field opts artio.config.opts
5
5
+
---@field win artio.config.win
6
6
+
7
7
+
---@class artio.config.opts
8
8
+
---@field preselect boolean
9
9
+
---@field bottom boolean
10
10
+
---@field promptprefix string
11
11
+
---@field pointer string
12
12
+
13
13
+
---@class artio.config.win
14
14
+
---@field height? integer|number
15
15
+
---@field hidestatusline? boolean
16
16
+
17
17
+
local M = {}
18
18
+
19
19
+
---@type artio.config
20
20
+
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
21
21
+
---@text # Default ~
22
22
+
M.default = {
23
23
+
opts = {
24
24
+
preselect = true,
25
25
+
bottom = true,
26
26
+
promptprefix = "",
27
27
+
pointer = "",
28
28
+
},
29
29
+
win = {
30
30
+
height = 12,
31
31
+
hidestatusline = false, -- works best with laststatus=3
32
32
+
},
33
33
+
}
34
34
+
35
35
+
---@type artio.config
36
36
+
---@diagnostic disable-next-line: missing-fields
37
37
+
M.config = {}
38
38
+
39
39
+
---@private
40
40
+
---@generic T: table|any[]
41
41
+
---@param tdefault T
42
42
+
---@param toverride T
43
43
+
---@return T
44
44
+
local function tmerge(tdefault, toverride)
45
45
+
if toverride == nil then
46
46
+
return tdefault
47
47
+
end
48
48
+
49
49
+
if vim.islist(tdefault) then
50
50
+
return toverride
51
51
+
end
52
52
+
if vim.tbl_isempty(tdefault) then
53
53
+
return toverride
54
54
+
end
55
55
+
56
56
+
return vim.iter(pairs(tdefault)):fold({}, function(tnew, k, v)
57
57
+
if toverride[k] == nil or type(v) ~= type(toverride[k]) then
58
58
+
tnew[k] = v
59
59
+
return tnew
60
60
+
end
61
61
+
if type(v) == "table" then
62
62
+
tnew[k] = tmerge(v, toverride[k])
63
63
+
return tnew
64
64
+
end
65
65
+
66
66
+
tnew[k] = toverride[k]
67
67
+
return tnew
68
68
+
end)
69
69
+
end
70
70
+
71
71
+
---@param tdefault artio.config
72
72
+
---@param toverride artio.config
73
73
+
---@return artio.config
74
74
+
function M.merge(tdefault, toverride)
75
75
+
if vim.fn.has("nvim-0.11.0") == 1 then
76
76
+
toverride =
77
77
+
vim.tbl_deep_extend("keep", toverride, { editor = { float = { solid_border = vim.o.winborder == "solid" } } })
78
78
+
end
79
79
+
return tmerge(tdefault, toverride)
80
80
+
end
81
81
+
82
82
+
---@return artio.config
83
83
+
function M.get()
84
84
+
return M.merge(M.default, M.config)
85
85
+
end
86
86
+
87
87
+
---@param cfg artio.config
88
88
+
---@return artio.config
89
89
+
function M.override(cfg)
90
90
+
return M.merge(M.default, cfg)
91
91
+
end
92
92
+
93
93
+
---@param cfg artio.config
94
94
+
function M.set(cfg)
95
95
+
M.config = cfg
96
96
+
end
97
97
+
98
98
+
return M
+69
lua/artio/init.lua
···
1
1
+
local artio = {}
2
2
+
3
3
+
local function lzrq(modname)
4
4
+
return setmetatable({}, {
5
5
+
__index = function(_, key)
6
6
+
return require(modname)[key]
7
7
+
end,
8
8
+
})
9
9
+
end
10
10
+
11
11
+
local Picker = lzrq("artio.picker")
12
12
+
13
13
+
local findprg = "fd -p -t f --color=never"
14
14
+
15
15
+
local function find_files(match)
16
16
+
if not findprg then
17
17
+
return {}
18
18
+
end
19
19
+
local farg = string.format("'%s'", match)
20
20
+
local findcmd, n = findprg:gsub("%$%*", farg)
21
21
+
if n == 0 then
22
22
+
findcmd = findcmd .. " " .. farg
23
23
+
end
24
24
+
local fn = function(o)
25
25
+
local src = o.stderr
26
26
+
if o.code == 0 then
27
27
+
src = o.stdout
28
28
+
end
29
29
+
src = src
30
30
+
local lines = vim.split(src, "\n", { trimempty = true })
31
31
+
return lines
32
32
+
end
33
33
+
return fn(vim
34
34
+
.system({ vim.o.shell, "-c", findcmd }, {
35
35
+
text = true,
36
36
+
})
37
37
+
:wait())
38
38
+
end
39
39
+
40
40
+
artio.files = function()
41
41
+
return artio.pick({
42
42
+
prompt = "files",
43
43
+
fn = function(input)
44
44
+
local lst = find_files(input)
45
45
+
if not lst or #lst == 0 then
46
46
+
return {}
47
47
+
end
48
48
+
49
49
+
local matches = vim.fn.matchfuzzypos(lst, input)
50
50
+
return vim
51
51
+
.iter(ipairs(matches[1]))
52
52
+
:map(function(index, v)
53
53
+
return { v, matches[2][index] }
54
54
+
end)
55
55
+
:totable()
56
56
+
end,
57
57
+
on_close = function(text, _)
58
58
+
vim.schedule(function()
59
59
+
vim.cmd.edit(text)
60
60
+
end)
61
61
+
end,
62
62
+
})
63
63
+
end
64
64
+
65
65
+
artio.pick = function(...)
66
66
+
return Picker:new(...):open()
67
67
+
end
68
68
+
69
69
+
return artio
+108
lua/artio/picker.lua
···
1
1
+
local View = require("artio.view")
2
2
+
3
3
+
---@class artio.Picker.proto
4
4
+
---@field idx? integer 1-indexed
5
5
+
---@field fn? fun(input: string): [string, integer][]
6
6
+
---@field on_close? fun(text: string, idx: integer)
7
7
+
---@field opts? artio.config.opts
8
8
+
---@field win? artio.config.win
9
9
+
---@field prompt? string
10
10
+
---@field defaulttext? string
11
11
+
---@field prompttext? string
12
12
+
13
13
+
---@class artio.Picker : artio.Picker.proto
14
14
+
---@field idx integer
15
15
+
local Picker = {}
16
16
+
Picker.__index = Picker
17
17
+
18
18
+
---@param props artio.Picker.proto
19
19
+
function Picker:new(props)
20
20
+
vim.validate("fn", props.fn, "function")
21
21
+
vim.validate("on_close", props.on_close, "function")
22
22
+
23
23
+
local t = vim.tbl_deep_extend("force", {
24
24
+
prompt = "",
25
25
+
idx = 1,
26
26
+
items = {},
27
27
+
}, require("artio.config").get(), props)
28
28
+
29
29
+
t.prompttext = t.prompttext or ("%s %s"):format(t.prompt, t.opts.promptprefix)
30
30
+
31
31
+
return setmetatable(t, Picker)
32
32
+
end
33
33
+
34
34
+
function Picker:open()
35
35
+
if not self.fn or not self.on_close then
36
36
+
vim.notify("Picker must have `fn` and `on_close`", vim.log.levels.ERROR)
37
37
+
return
38
38
+
end
39
39
+
40
40
+
local accepted
41
41
+
local cancelled
42
42
+
43
43
+
local view = View:new()
44
44
+
view.picker = self
45
45
+
46
46
+
coroutine.wrap(function()
47
47
+
view:open()
48
48
+
49
49
+
local co, ismain = coroutine.running()
50
50
+
assert(not ismain, "must be called from a coroutine")
51
51
+
52
52
+
local key_ns = vim.on_key(function(_, typed)
53
53
+
if view.closed then
54
54
+
coroutine.resume(co)
55
55
+
return
56
56
+
end
57
57
+
58
58
+
typed = string.lower(vim.fn.keytrans(typed))
59
59
+
if typed == "<down>" then
60
60
+
self.idx = self.idx + 1
61
61
+
self:fix()
62
62
+
view:hlselect()
63
63
+
return ""
64
64
+
elseif typed == "<up>" then
65
65
+
self.idx = self.idx - 1
66
66
+
self:fix()
67
67
+
view:hlselect()
68
68
+
return ""
69
69
+
elseif typed == "<cr>" then
70
70
+
accepted = true
71
71
+
coroutine.resume(co)
72
72
+
return ""
73
73
+
elseif typed == "<esc>" then
74
74
+
cancelled = true
75
75
+
coroutine.resume(co)
76
76
+
return ""
77
77
+
end
78
78
+
end)
79
79
+
80
80
+
coroutine.yield()
81
81
+
82
82
+
vim.on_key(nil, key_ns)
83
83
+
view:close()
84
84
+
85
85
+
if cancelled or not accepted then
86
86
+
return
87
87
+
end
88
88
+
89
89
+
local current = self.items[self.idx]
90
90
+
if not current then
91
91
+
return
92
92
+
end
93
93
+
94
94
+
self.on_close(current[1], self.idx)
95
95
+
end)()
96
96
+
end
97
97
+
98
98
+
function Picker:fix()
99
99
+
self.idx = math.max(self.idx, self.opts.preselect and 1 or 0)
100
100
+
self.idx = math.min(self.idx, self.win.height - 1, #self.items)
101
101
+
end
102
102
+
103
103
+
function Picker:getitems(input)
104
104
+
self.items = self.fn(input)
105
105
+
return self.items
106
106
+
end
107
107
+
108
108
+
return Picker
+333
lua/artio/view.lua
···
1
1
+
local cmdline = require("vim._extui.cmdline")
2
2
+
local ext = require("vim._extui.shared")
3
3
+
4
4
+
local prompt_hl_id = vim.api.nvim_get_hl_id_by_name("ArtioPrompt")
5
5
+
6
6
+
--- Set the 'cmdheight' and cmdline window height. Reposition message windows.
7
7
+
---
8
8
+
---@param win integer Cmdline window in the current tabpage.
9
9
+
---@param hide boolean Whether to hide or show the window.
10
10
+
---@param height integer (Text)height of the cmdline window.
11
11
+
local function win_config(win, hide, height)
12
12
+
if ext.cmdheight == 0 and vim.api.nvim_win_get_config(win).hide ~= hide then
13
13
+
vim.api.nvim_win_set_config(win, { hide = hide, height = not hide and height or nil })
14
14
+
elseif vim.api.nvim_win_get_height(win) ~= height then
15
15
+
vim.api.nvim_win_set_height(win, height)
16
16
+
end
17
17
+
if vim.o.cmdheight ~= height then
18
18
+
-- Avoid moving the cursor with 'splitkeep' = "screen", and altering the user
19
19
+
-- configured value with noautocmd.
20
20
+
vim._with({ noautocmd = true, o = { splitkeep = "screen" } }, function()
21
21
+
vim.o.cmdheight = height
22
22
+
end)
23
23
+
ext.msg.set_pos()
24
24
+
end
25
25
+
end
26
26
+
27
27
+
local cmdbuff = "" ---@type string Stored cmdline used to calculate translation offset.
28
28
+
local promptlen = 0 -- Current length of the last line in the prompt.
29
29
+
local promptwidth = 0 -- Current width of the prompt in the cmdline buffer.
30
30
+
local promptidx = 0
31
31
+
--- Concatenate content chunks and set the text for the current row in the cmdline buffer.
32
32
+
---
33
33
+
---@param content CmdContent
34
34
+
---@param prompt string
35
35
+
local function set_text(content, prompt)
36
36
+
local lines = {} ---@type string[]
37
37
+
for line in (prompt .. "\n"):gmatch("(.-)\n") do
38
38
+
lines[#lines + 1] = vim.fn.strtrans(line)
39
39
+
end
40
40
+
41
41
+
promptlen = #lines[#lines]
42
42
+
promptwidth = vim.fn.strdisplaywidth(lines[#lines])
43
43
+
44
44
+
cmdbuff = ""
45
45
+
for _, chunk in ipairs(content) do
46
46
+
cmdbuff = cmdbuff .. chunk[2]
47
47
+
end
48
48
+
lines[#lines] = ("%s%s"):format(lines[#lines], vim.fn.strtrans(cmdbuff))
49
49
+
vim.api.nvim_buf_set_lines(ext.bufs.cmd, promptidx, promptidx + 1, false, lines)
50
50
+
end
51
51
+
52
52
+
---@class artio.View
53
53
+
---@field picker artio.Picker
54
54
+
---@field closed boolean
55
55
+
---@field win artio.View.win
56
56
+
local View = {}
57
57
+
View.__index = View
58
58
+
59
59
+
function View:new()
60
60
+
return setmetatable({
61
61
+
closed = false,
62
62
+
win = {
63
63
+
height = 0,
64
64
+
},
65
65
+
}, View)
66
66
+
end
67
67
+
68
68
+
---@class artio.View.win
69
69
+
---@field height integer
70
70
+
71
71
+
--- Set the cmdline buffer text and cursor position.
72
72
+
---
73
73
+
---@param content CmdContent
74
74
+
---@param pos? integer
75
75
+
---@param firstc string
76
76
+
---@param prompt string
77
77
+
---@param indent integer
78
78
+
---@param level integer
79
79
+
---@param hl_id integer
80
80
+
function View:show(content, pos, firstc, prompt, indent, level, hl_id)
81
81
+
cmdline.level, cmdline.indent, cmdline.prompt = level, indent, cmdline.prompt or #prompt > 0
82
82
+
if cmdline.highlighter and cmdline.highlighter.active then
83
83
+
cmdline.highlighter.active[ext.bufs.cmd] = nil
84
84
+
end
85
85
+
if ext.msg.cmd.msg_row ~= -1 then
86
86
+
ext.msg.msg_clear()
87
87
+
end
88
88
+
ext.msg.virt.last = { {}, {}, {}, {} }
89
89
+
90
90
+
self:clear()
91
91
+
92
92
+
local cmd_text = ""
93
93
+
for _, chunk in ipairs(content) do
94
94
+
cmd_text = cmd_text .. chunk[2]
95
95
+
end
96
96
+
97
97
+
self.picker:getitems(cmd_text)
98
98
+
self:showitems()
99
99
+
100
100
+
self:promptpos()
101
101
+
set_text(content, ("%s%s%s"):format(firstc, prompt, (" "):rep(indent)))
102
102
+
103
103
+
local height = math.max(1, vim.api.nvim_win_text_height(ext.wins.cmd, {}).all)
104
104
+
height = math.min(height, self.win.height)
105
105
+
win_config(ext.wins.cmd, false, height)
106
106
+
107
107
+
self:updatecursor(pos)
108
108
+
109
109
+
if promptlen > 0 and hl_id > 0 then
110
110
+
vim.api.nvim_buf_set_extmark(ext.bufs.cmd, ext.ns, promptidx, 0, { hl_group = hl_id, end_col = promptlen })
111
111
+
end
112
112
+
self:hlselect()
113
113
+
end
114
114
+
115
115
+
function View:saveview()
116
116
+
self.save = vim.fn.winsaveview()
117
117
+
self.prevwin = vim.api.nvim_get_current_win()
118
118
+
end
119
119
+
120
120
+
function View:restoreview()
121
121
+
vim.api.nvim_set_current_win(self.prevwin)
122
122
+
vim.fn.winrestview(self.save)
123
123
+
end
124
124
+
125
125
+
local ext_winhl = "Search:MsgArea,CurSearch:MsgArea,IncSearch:MsgArea"
126
126
+
127
127
+
function View:setopts()
128
128
+
local opts = {
129
129
+
eventignorewin = "all,-FileType,-TextChangedI,-CursorMovedI",
130
130
+
winhighlight = "Normal:ArtioNormal," .. ext_winhl,
131
131
+
laststatus = self.picker.win.hidestatusline and 0 or nil,
132
132
+
}
133
133
+
134
134
+
self.opts = {}
135
135
+
136
136
+
for name, value in pairs(opts) do
137
137
+
self.opts[name] = vim.api.nvim_get_option_value(name, { scope = "local" })
138
138
+
vim.api.nvim_set_option_value(name, value, { scope = "local" })
139
139
+
end
140
140
+
end
141
141
+
142
142
+
function View:revertopts()
143
143
+
for name, value in pairs(self.opts) do
144
144
+
vim.api.nvim_set_option_value(name, value, { scope = "local" })
145
145
+
end
146
146
+
end
147
147
+
148
148
+
function View:on_resized()
149
149
+
if self.picker.win.height > 0 then
150
150
+
self.win.height = self.picker.win.height
151
151
+
else
152
152
+
self.win.height = vim.o.lines * self.picker.win.height
153
153
+
end
154
154
+
self.win.height = math.max(math.ceil(self.win.height), 1)
155
155
+
end
156
156
+
157
157
+
function View:open()
158
158
+
if not self.picker then
159
159
+
return
160
160
+
end
161
161
+
162
162
+
ext.check_targets()
163
163
+
164
164
+
self.prev_show = cmdline.cmdline_show
165
165
+
166
166
+
self.augroup = vim.api.nvim_create_augroup("artio:view", {})
167
167
+
168
168
+
vim.schedule(function()
169
169
+
vim.api.nvim_create_autocmd({ "CmdlineLeave", "ModeChanged" }, {
170
170
+
group = self.augroup,
171
171
+
once = true,
172
172
+
callback = function()
173
173
+
self:close()
174
174
+
end,
175
175
+
})
176
176
+
177
177
+
vim.api.nvim_create_autocmd("VimResized", {
178
178
+
group = self.augroup,
179
179
+
callback = function()
180
180
+
self:on_resized()
181
181
+
end,
182
182
+
})
183
183
+
184
184
+
vim.api.nvim_create_autocmd("TextChangedI", {
185
185
+
group = self.augroup,
186
186
+
callback = function()
187
187
+
self:update()
188
188
+
end,
189
189
+
})
190
190
+
191
191
+
vim.api.nvim_create_autocmd("CursorMovedI", {
192
192
+
group = self.augroup,
193
193
+
callback = function()
194
194
+
self:updatecursor()
195
195
+
end,
196
196
+
})
197
197
+
end)
198
198
+
199
199
+
self:on_resized()
200
200
+
201
201
+
cmdline.cmdline_show = function(...)
202
202
+
return self:show(...)
203
203
+
end
204
204
+
205
205
+
self:saveview()
206
206
+
207
207
+
cmdline.cmdline_show(
208
208
+
{ self.picker.defaulttext and { 0, self.picker.defaulttext } or nil },
209
209
+
nil,
210
210
+
"",
211
211
+
self.picker.prompttext,
212
212
+
1,
213
213
+
0,
214
214
+
prompt_hl_id
215
215
+
)
216
216
+
217
217
+
vim._with({ noautocmd = true }, function()
218
218
+
vim.api.nvim_set_current_win(ext.wins.cmd)
219
219
+
end)
220
220
+
221
221
+
self:setopts()
222
222
+
223
223
+
vim._with({ noautocmd = true }, function()
224
224
+
vim.cmd.startinsert()
225
225
+
end)
226
226
+
227
227
+
vim.schedule(function()
228
228
+
self:clear()
229
229
+
self:updatecursor()
230
230
+
end)
231
231
+
232
232
+
vim._with({ win = ext.wins.cmd, wo = { eventignorewin = "" } }, function()
233
233
+
vim.api.nvim_exec_autocmds("WinEnter", {})
234
234
+
end)
235
235
+
end
236
236
+
237
237
+
function View:close()
238
238
+
if self.closed then
239
239
+
return
240
240
+
end
241
241
+
self.closed = true
242
242
+
cmdline.cmdline_show = self.prev_show
243
243
+
vim.schedule(function()
244
244
+
vim.cmd.stopinsert()
245
245
+
self:revertopts()
246
246
+
self:clear()
247
247
+
cmdline.srow = 0
248
248
+
cmdline.erow = 0
249
249
+
win_config(ext.wins.cmd, true, ext.cmdheight)
250
250
+
self:restoreview()
251
251
+
cmdline.cmdline_block_hide()
252
252
+
pcall(vim.api.nvim_del_augroup_by_id, self.augroup)
253
253
+
end)
254
254
+
end
255
255
+
256
256
+
function View:update()
257
257
+
local text = vim.api.nvim_get_current_line()
258
258
+
text = text:sub(promptlen + 1)
259
259
+
260
260
+
cmdline.cmdline_show({ { 0, text } }, nil, "", self.picker.prompttext, cmdline.indent, cmdline.level, prompt_hl_id)
261
261
+
end
262
262
+
263
263
+
local curpos = { 0, 0 } -- Last drawn cursor position. absolute
264
264
+
---@param pos? integer relative to prompt
265
265
+
function View:updatecursor(pos)
266
266
+
self:promptpos()
267
267
+
268
268
+
if not pos then
269
269
+
local cursorpos = vim.api.nvim_win_get_cursor(ext.wins.cmd)
270
270
+
pos = cursorpos[2] - promptlen
271
271
+
end
272
272
+
273
273
+
curpos[2] = math.max(curpos[2], promptlen)
274
274
+
275
275
+
if curpos[1] == promptidx + 1 and curpos[2] == promptlen + pos then
276
276
+
return
277
277
+
end
278
278
+
279
279
+
if pos < 0 then
280
280
+
-- reset to last known position
281
281
+
pos = curpos[2] - promptlen
282
282
+
end
283
283
+
284
284
+
curpos[1], curpos[2] = promptidx + 1, promptlen + pos
285
285
+
286
286
+
vim._with({ noautocmd = true }, function()
287
287
+
vim.api.nvim_win_set_cursor(ext.wins.cmd, curpos)
288
288
+
end)
289
289
+
end
290
290
+
291
291
+
function View:clear()
292
292
+
cmdline.srow = self.picker.opts.bottom and 0 or 1
293
293
+
cmdline.erow = 0
294
294
+
vim.api.nvim_buf_set_lines(ext.bufs.cmd, 0, -1, false, {})
295
295
+
end
296
296
+
297
297
+
function View:promptpos()
298
298
+
promptidx = self.picker.opts.bottom and cmdline.erow or 0
299
299
+
end
300
300
+
301
301
+
function View:showitems()
302
302
+
local prefix = (" "):rep(vim.fn.strdisplaywidth(self.picker.opts.pointer) + 1)
303
303
+
304
304
+
local lines = {} ---@type string[]
305
305
+
for i = 1, math.min(#self.picker.items, self.win.height - 1) do
306
306
+
lines[#lines + 1] = ("%s%s"):format(prefix, self.picker.items[i][1])
307
307
+
end
308
308
+
cmdline.erow = cmdline.srow + #lines
309
309
+
vim.api.nvim_buf_set_lines(ext.bufs.cmd, cmdline.srow, cmdline.erow, false, lines)
310
310
+
end
311
311
+
312
312
+
local view_ns = vim.api.nvim_create_namespace("artio:view:ns")
313
313
+
314
314
+
function View:hlselect()
315
315
+
if self.select_ext then
316
316
+
vim.api.nvim_buf_del_extmark(ext.bufs.cmd, view_ns, self.select_ext)
317
317
+
end
318
318
+
319
319
+
self.picker:fix()
320
320
+
local idx = self.picker.idx
321
321
+
if idx == 0 then
322
322
+
return
323
323
+
end
324
324
+
325
325
+
self.select_ext = vim.api.nvim_buf_set_extmark(ext.bufs.cmd, view_ns, cmdline.srow + idx - 1, 0, {
326
326
+
virt_text = { { self.picker.opts.pointer, "ArtioPointer" } },
327
327
+
hl_mode = "combine",
328
328
+
virt_text_pos = "overlay",
329
329
+
line_hl_group = "ArtioSel",
330
330
+
})
331
331
+
end
332
332
+
333
333
+
return View
+37
plugin/artio.lua
···
1
1
+
if vim.g.loaded_artio then
2
2
+
return
3
3
+
end
4
4
+
5
5
+
vim.g.loaded_artio = true
6
6
+
7
7
+
local augroup = vim.api.nvim_create_augroup("artio:hl", {})
8
8
+
9
9
+
vim.api.nvim_create_autocmd("ColorScheme", {
10
10
+
group = augroup,
11
11
+
callback = function()
12
12
+
local normal_hl = vim.api.nvim_get_hl(0, { name = "Normal" })
13
13
+
local msgarea_hl = vim.api.nvim_get_hl(0, { name = "MsgArea" })
14
14
+
15
15
+
vim.api.nvim_set_hl(0, "ArtioNormal", { fg = normal_hl.fg, bg = msgarea_hl.bg, default = true })
16
16
+
vim.api.nvim_set_hl(0, "ArtioPrompt", { link = "Title", default = true })
17
17
+
18
18
+
local cursor_hl = vim.api.nvim_get_hl(0, { name = "Cursor" })
19
19
+
local cursorline_hl = vim.api.nvim_get_hl(0, { name = "CursorLine" })
20
20
+
vim.api.nvim_set_hl(0, "ArtioSel", { fg = cursor_hl.bg, bg = cursorline_hl.bg, default = true })
21
21
+
vim.api.nvim_set_hl(0, "ArtioPointer", { fg = cursor_hl.bg, default = true })
22
22
+
end,
23
23
+
})
24
24
+
25
25
+
vim.api.nvim_create_autocmd("ColorSchemePre", {
26
26
+
group = augroup,
27
27
+
callback = function()
28
28
+
vim.api.nvim_set_hl(0, "ArtioNormal", {})
29
29
+
vim.api.nvim_set_hl(0, "ArtioPrompt", {})
30
30
+
vim.api.nvim_set_hl(0, "ArtioSel", {})
31
31
+
vim.api.nvim_set_hl(0, "ArtioPointer", {})
32
32
+
end,
33
33
+
})
34
34
+
35
35
+
vim.keymap.set("n", "<Plug>(picker-find)", function()
36
36
+
return require("artio").files()
37
37
+
end)
+4
selene.toml
···
1
1
+
std="vim"
2
2
+
3
3
+
[rules]
4
4
+
mixed_table = "allow"
+9
stylua.toml
···
1
1
+
indent_type = "Spaces"
2
2
+
indent_width = 2
3
3
+
column_width = 120
4
4
+
quote_style = "AutoPreferDouble"
5
5
+
call_parentheses = "Always"
6
6
+
line_endings = "Unix"
7
7
+
8
8
+
[sort_requires]
9
9
+
enabled = true
+55
vim.toml
···
1
1
+
[selene]
2
2
+
base = "lua51"
3
3
+
name = "vim"
4
4
+
5
5
+
[vim]
6
6
+
any = true
7
7
+
8
8
+
[[describe.args]]
9
9
+
type = "string"
10
10
+
[[describe.args]]
11
11
+
type = "function"
12
12
+
13
13
+
[[it.args]]
14
14
+
type = "string"
15
15
+
[[it.args]]
16
16
+
type = "function"
17
17
+
18
18
+
[[before_each.args]]
19
19
+
type = "function"
20
20
+
[[after_each.args]]
21
21
+
type = "function"
22
22
+
23
23
+
[assert.is_not]
24
24
+
any = true
25
25
+
26
26
+
[assert.matches]
27
27
+
any = true
28
28
+
29
29
+
[assert.has_error]
30
30
+
any = true
31
31
+
32
32
+
[[assert.equals.args]]
33
33
+
type = "any"
34
34
+
[[assert.equals.args]]
35
35
+
type = "any"
36
36
+
[[assert.equals.args]]
37
37
+
type = "any"
38
38
+
required = false
39
39
+
40
40
+
[[assert.same.args]]
41
41
+
type = "any"
42
42
+
[[assert.same.args]]
43
43
+
type = "any"
44
44
+
45
45
+
[[assert.truthy.args]]
46
46
+
type = "any"
47
47
+
48
48
+
[[assert.falsy.args]]
49
49
+
type = "any"
50
50
+
51
51
+
[[assert.spy.args]]
52
52
+
type = "any"
53
53
+
54
54
+
[[assert.stub.args]]
55
55
+
type = "any"