-
Notifications
You must be signed in to change notification settings - Fork 7
/
show_tips.lua
357 lines (320 loc) · 12.4 KB
/
show_tips.lua
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
--------------------------------------------------------------------------------
settings.add('tips.enable', true, 'Show a tip when Clink starts',
'When true, a random tip is printed when Clink starts.')
if not settings.get('tips.enable') then
return
end
--------------------------------------------------------------------------------
-- A script can register to add its own tips.
-- See modules/external_tips.lua for details.
local external_tips = require("external_tips")
--------------------------------------------------------------------------------
local function rl_testvar(name, test)
if rl.getvariable then
local value = rl.getvariable(name)
if value then
return (test == value)
end
end
end
--------------------------------------------------------------------------------
local function open_seen_tips_file(mode, deny)
if not io.sopen then
return
end
local profile_dir = os.getenv("=clink.profile")
if not profile_dir or profile_dir == "" then
return
end
local name = path.join(profile_dir, ".seen_tips")
if not name then
return
end
-- Retry opening the file until there is no sharing violation.
-- Try for up to 2 seconds, and then give up.
local f
local start_clock = os.clock()
repeat
f = io.sopen(name, mode, deny)
until f or os.clock() - start_clock > 2
return f
end
local function clear_seen_file()
local f = open_seen_tips_file("w", "rw")
if f then
f:close()
end
return {}
end
local function load_seen_tips()
local seen = {}
-- Opening for "a" then "r" ensures the file exists (without clearing it)
-- before trying to read it, otherwise it will retry for 2 seconds when the
-- file doesn't exist.
local f = open_seen_tips_file("a", "w")
if f then
f:close()
f = open_seen_tips_file("r", "w")
for line in f:lines() do
seen[line] = true
end
f:close()
end
return seen
end
local function add_seen_tip(id)
local f = open_seen_tips_file("a", "rw")
if f then
f:write(id.."\n")
f:close()
end
end
--------------------------------------------------------------------------------
-- luacheck: no max line length
local function collect_tips(external, seen)
local tips = {early={}}
local any_seen
local function insert_tip(id, condition, tip)
if seen[id] then
any_seen = true
else
if condition then
tip.id = id
tips[id] = tip
table.insert(tips, id)
if tip.early then
table.insert(tips.early, id)
end
else
add_seen_tip(id)
end
end
end
-- REVIEW: Tips about concepts?
-- TODO: Killing and Yanking.
-- TODO: Numeric Arguments.
-- TODO: Readline Init File (config variables and key bindings).
-- Collect key bindings.
local bindings = rl.getkeybindings()
if bindings and #bindings > 0 then
local early_commands = {
["reverse-search-history"] = true,
["history-search-backward"] = true,
["yank-last-arg"] = true,
["operate-and-get-next"] = true,
["clink-select-complete"] = true,
["undo"] = true,
["add-history"] = true,
["remove-history"] = true,
["clink-expand-line"] = true,
["clink-popup-history"] = true,
["clink-show-help"] = true,
["clink-what-is"] = true,
["clink-up-directory"] = true,
["cua-backward-char"] = true,
["cua-forward-char"] = true,
["cua-beg-of-line"] = true,
["cua-end-of-line"] = true,
}
for _, b in ipairs(bindings) do
if b.key and b.binding and b.desc and b.desc ~= "" then
local k = b.key:gsub(" +$", "")
local id = "key:"..k..":"..b.binding
if seen[id] then
any_seen = true
else
if early_commands[b.binding] then
b.early = true
end
tips[id] = b
table.insert(tips, id)
end
end
end
end
-- Collect other kinds of tips.
insert_tip("set:colored-stats", rl_testvar("colored-stats", "off") and rl_testvar("colored-completion-prefix", "off"),
{early=true, text="Completions can be displayed with color by setting 'colored-stats' in the .inputrc init file.\nSee https://chrisant996.github.io/clink/clink.html#init-file for more info."})
insert_tip("set:clink.colorize_input", not settings.get("clink.colorize_input"),
{early=true, text="The input line can be colorized by running 'clink set clink.colorize_input true'.\nSee https://chrisant996.github.io/clink/clink.html#classifywords for more info."})
insert_tip("set:customized_prompt", true,
{early=true, text="You can customize the prompt by downloading or writing your own Lua scripts.\nSee https://chrisant996.github.io/clink/clink.html#gettingstarted_customprompt for more info."})
insert_tip("set:history.time_stamp", not settings.get("history.time_stamp"),
{text="The saved command history can include time stamps by running 'clink set history.time_stamp true'.\nSee https://chrisant996.github.io/clink/clink.html#history-timestamps for more info."})
insert_tip("set:history.dupe_mode", true,
{text="The 'history.dupe_mode' setting controls how duplicate entries are saved in the command history.\nSee https://chrisant996.github.io/clink/clink.html#history_dupe_mode for more info."})
insert_tip("set:matchicons.enable", not settings.get("matchicons.enable"),
{text="If you're using a Nerd Font (https://nerdfonts.com) the 'matchicons.enable' setting can show file icons next to match completions.\nSee https://github.com/chrisant996/clink-gizmos/#matchicons for more info."})
insert_tip("usage:popup_search_mode", true,
{early=true, category="usage", text="In popup lists you can filter by pressing F4, or you can make it the default by running 'clink set clink.popup_search_mode filter'.\nSee https://chrisant996.github.io/clink/clink.html#popupwindow for more info."})
-- TODO: history expansion.
-- TODO: history.shared setting.
-- TODO: match.wild setting.
-- REVIEW: Not sure how/when/whether to present these tips:
-- TODO: autosuggest feature.
-- TODO: startup cmd script.
-- TODO: clink.auto_answer setting.
-- TODO: match.sort_dirs setting.
-- TODO: terminal.differentiate_keys setting.
-- TODO: clink.logo setting.
-- TODO: exec.enabled and related settings.
-- TODO: doskey.enhanced setting.
-- TODO: autoupdate settings and etc?
-- Some tips have prerequisites.
if rl_testvar("colored-stats", "on") then
insert_tip("set:LS_COLORS", not os.getenv("LS_COLORS"),
{early=true, text="Completion colors can be customized by setting the LS_COLORS environment variable.\nSee https://chrisant996.github.io/clink/clink.html#completioncolors for more info."})
end
if (clink.version_encoded or 0) >= 10070000 then -- luacheck: ignore 542
insert_tip("usage:clinktheme", true,
{early=true, text="You can use 'clink config theme list' to list available color themes, or use 'clink config theme use' to use a color theme.\nSee https://chrisant996.github.io/clink/clink.html#color-themes for more info."})
insert_tip("usage:clinkprompt", true,
{early=true, text="You can use 'clink config prompt list' to list available custom prompts, or use 'clink config prompt use' to use a custom prompt.\nSee https://chrisant996.github.io/clink/clink.html#custom-prompts for more info."})
insert_tip("usage:hexcolors", true,
{early=true, text="You can use hex RGB color codes when setting Clink colors.\nFor example 'clink set color.input #A763FF'."})
end
-- Allow external tips.
if external then
for _, t in ipairs(external) do
if t.id and t.text then
insert_tip(t.id, (t.condition == nil or t.condition), {
early = t.early,
text = t.text,
category = t.category,
})
end
end
end
return tips, any_seen
end
--------------------------------------------------------------------------------
local function print_with_wrap(text)
if unicode.iter then
local width = console.getwidth() - 1
local columns = 0
local line = ""
local word = ""
local non_spaces = false
local function flush_line()
if columns > 0 then
clink.print(line)
columns = 0
line = ""
end
end
local function print_word()
local len = console.cellcount(word)
if len > 0 then
if columns > 0 and columns + len >= width then
flush_line()
word = word:gsub("^ +", "")
len = console.cellcount(word)
end
columns = columns + len
line = line..word
word = ""
non_spaces = false
end
end
for s in unicode.iter(text) do
if s == "\n" then
print_word()
flush_line()
elseif s == " " then
if non_spaces then
print_word()
end
word = word..s
else
word = word..s
non_spaces = true
end
end
print_word()
flush_line()
else
clink.print(text)
end
end
local function default_print_tip(tip, wrap, off)
local bold = "\x1b[1m"
local cyan = "\x1b[36m"
local gray = "\x1b[30;1m"
local norm = "\x1b[m"
local function embolden(text)
return bold..text..norm
end
local heading = cyan..tip.category.." tip:"..norm
local message
if tip.key then
message = string.format("%s : %s -- %s", embolden(tip.key), embolden(tip.binding), tip.desc)
elseif tip.text then
message = tip.text
else
error("Unexpected tip type for id '"..(tip.id or "<unknown>").."'.")
end
off = gray..off..norm
-- Show the selected tip.
clink.print(heading)
wrap(message)
clink.print("")
clink.print(off)
end
local function print_tip(tip, wrap_func, off_message, default_func)
clink.print("")
default_func(tip, wrap_func, off_message)
clink.print("")
end
local function show_tip()
-- Collect available tips that haven't been seen yet.
local seen = load_seen_tips()
local external = external_tips.collect()
local tips, any_seen = collect_tips(external, seen)
if not tips[1] and any_seen then
-- Reset the seen file if all tips have been seen.
clear_seen_file()
tips = collect_tips(external, {})
end
if not tips[1] then
return
end
-- Select at random whether to use an "early" tip.
math.randomseed(os.time())
local source = tips
local chance_for_early = 1/4
if #tips.early > 0 and math.random() >= (1 - chance_for_early) then
source = tips.early
end
-- Select a tip at random.
local index = math.random(#source)
local id = source[index]
local tip = tips[id]
if not tip then
return
end
-- Prepare the selected tip.
local out = {}
for k, v in pairs(tip) do
out[k] = v
end
if tip.key then
out.key = tip.key:gsub(" +$", "")
out.desc = tip.desc:gsub("([^.])$", "%1.")
out.category = "Key binding"
elseif tip.text then
local cat = tip.category or "configuration"
out.category = cat:sub(1, 1):upper()..cat:sub(2)
else
error("Unexpected tip type for id '"..id.."'.")
end
-- Show the selected tip.
local print_func = external_tips.print or print_tip
local off = "You can turn off these tips by running 'clink set tips.enable false'."
print_func(out, print_with_wrap, off, default_print_tip)
-- Mark that the tip has been seen.
add_seen_tip(id)
end
--------------------------------------------------------------------------------
if rl and rl.getkeybindings and clink.oninject then
clink.oninject(show_tip)
end