Skip to content

Commit c18d0e4

Browse files
committed
[small-prompt] add cpp attempt
1 parent 8c3c1b9 commit c18d0e4

File tree

1 file changed

+363
-0
lines changed

1 file changed

+363
-0
lines changed

unix/small-prompt/prompt.cpp

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
#!/usr/bin/env -S bash -c "set -x; tail -n+2 \$0 | clang++ -x c++ -std=c++20 -march=native -Ofast -flto -Wfatal-errors -Wall -Wextra -pedantic - && ./a.out"
2+
// -Ofast -flto -Og
3+
// C++
4+
#include <iostream>
5+
#include <iomanip>
6+
#include <format>
7+
8+
#include <vector>
9+
#include <iterator>
10+
11+
// C
12+
#include <cstdlib>
13+
14+
// linux
15+
#include <sys/ioctl.h>
16+
#include <unistd.h>
17+
18+
19+
using
20+
std::string, std::vector, std::format,
21+
std::cout, std::cerr, std::endl;
22+
23+
24+
inline void concat(string& dest, const vector<string>& v) {
25+
for (auto e : v) dest += e;
26+
}
27+
inline void concat(string& dest, const vector<char>& v) {
28+
for (auto e : v) dest += e;
29+
}
30+
31+
inline string concat(const vector<string>& v) {
32+
string ret;
33+
concat(ret, v);
34+
return ret;
35+
}
36+
inline string concat(const vector<char>& v) {
37+
string ret;
38+
concat(ret, v);
39+
return ret;
40+
}
41+
42+
inline string join(string delim, const vector<string>& v) {
43+
string ret;
44+
if (v.size() == 0) return ret;
45+
auto iter = v.begin();
46+
ret += *iter;
47+
for (advance(iter, 1); iter != v.end(); ++iter) {
48+
ret += delim;
49+
ret += *iter;
50+
}
51+
return ret;
52+
}
53+
54+
inline void writelines(std::ostream& out, const vector<string>& v) {
55+
for (auto s : v) out << s << endl;
56+
}
57+
58+
59+
inline auto get_term_cols() {
60+
struct winsize w;
61+
ioctl(0, TIOCGWINSZ, &w);
62+
return w.ws_col;
63+
}
64+
65+
inline string get_username() {
66+
return string(cuserid(nullptr));
67+
}
68+
69+
inline string get_workdir() {
70+
char *raw = get_current_dir_name();
71+
string ret{raw};
72+
free(raw);
73+
return ret;
74+
}
75+
76+
inline bool check_env_def(const char* k) {
77+
return !!std::getenv(k);
78+
}
79+
80+
inline bool check_env_is(const char* k, const char* v) {
81+
char *ptr = std::getenv(k);
82+
if (!ptr) return false;
83+
return string(ptr) == v;
84+
}
85+
86+
87+
/*
88+
* https://wiki.archlinux.org/title/Bash/Prompt_customization
89+
* https://man.archlinux.org/man/bash.1#PROMPTING
90+
* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
91+
* https://superuser.com/a/301355
92+
*
93+
* <PS1>[PS2]<Enter><PS0>[PS3][PS4]
94+
* PROMPT_COMMAND can be used, but not for printing;
95+
* must use it to save status.
96+
*/
97+
98+
namespace ansi {
99+
string
100+
esc = "\033",
101+
reset = "0",
102+
bold = "1",
103+
reset_all = esc + "(B" + esc + "[m";
104+
105+
struct {
106+
string cust256 = "38;5";
107+
struct { string
108+
black = "30", red = "31", green = "32",
109+
yellow = "33", blue = "34", magenta = "35",
110+
cyan = "36", white = "37";
111+
} dark;
112+
struct { string
113+
black = "90", red = "91", green = "92",
114+
yellow = "93", blue = "94", magenta = "95",
115+
cyan = "96", white = "97";
116+
} light;
117+
} fg;
118+
119+
vector rainbow_dark {
120+
"196", "202", "208", "214", "220", "226", "190", "154", "118", "82",
121+
"46", "47", "48", "49", "50", "51", "45", "39", "33", "27",
122+
"21", "57", "93", "129", "165", "201", "200", "199", "198", "197",
123+
};
124+
125+
vector rainbow_light {
126+
"160", "166", "172", "178", "142", "106", "70", "71", "72", "73",
127+
"74", "68", "62", "56", "92", "128", "164", "163", "162", "161",
128+
};
129+
130+
131+
inline string fmt(vector<string> list) {
132+
return concat({esc, "[", join(";", list), "m"});
133+
}
134+
}
135+
136+
namespace bash {
137+
constexpr char Q1 = '\'', Q2 = '"', ESC = '\\';
138+
139+
namespace ps { string
140+
a = "\x01",
141+
b = "\x02",
142+
user = "\\u",
143+
cpath = "\\w",
144+
cdir = "\\W",
145+
euid = "\\$";
146+
}
147+
148+
constexpr auto PROMPT_CMD = "PROMPT_COMMAND";
149+
150+
inline string quote1(string arg) {
151+
string ret;
152+
ret += Q1;
153+
for (char& c : arg) {
154+
if (c == Q1) concat(ret, {c, ESC, c});
155+
ret += c;
156+
}
157+
ret += Q1;
158+
return ret;
159+
}
160+
inline string quote2(string arg) {
161+
string ret;
162+
ret += Q2;
163+
for (char& c : arg) {
164+
if (c == Q2) ret += ESC;
165+
ret += c;
166+
}
167+
ret += Q2;
168+
return ret;
169+
}
170+
inline string noprint(string arg) {
171+
return concat({ps::a, arg, ps::b});
172+
}
173+
inline string set(string k, string v) {
174+
return concat({k, "=", v});
175+
}
176+
inline string var_(string v) {
177+
return "$" + v;
178+
}
179+
inline string var(string v) {
180+
return concat({"\"$", v, "\""});
181+
}
182+
inline string subsh(string arg) {
183+
return concat({"\"$(", arg, ")\""});
184+
}
185+
inline string cmd(const vector<string>& v) {
186+
return join(" ", v);
187+
}
188+
inline string cmds(const vector<string>& v) {
189+
return join("; ", v);
190+
}
191+
inline string procsub(const vector<string>& v) {
192+
return concat({"<(", cmd(v), ")"});
193+
}
194+
inline string func(string name, const vector<string>& v) {
195+
return concat({name, "() { ", cmds(v), "; }"});
196+
}
197+
}
198+
199+
200+
constexpr const char* PROMPT_MY = "_prompt_my";
201+
string PROG;
202+
203+
204+
namespace V {
205+
string user = get_username();
206+
string dir = get_workdir();
207+
ushort tcols = get_term_cols();
208+
bool is_root = geteuid() == 0;
209+
bool is_long = user.length() + dir.length() + 4 > tcols / 2;
210+
211+
bool is_vscode = check_env_is("TERM_PROGRAM", "vscode");
212+
bool is_jetbr = check_env_is("TERMINAL_EMULATOR", "JetBrains-JediTerm");
213+
bool is_kate = check_env_def("KATE_PID");
214+
bool is_xterm = check_env_def("XTERM_VERSION");
215+
216+
bool as_light = is_vscode || is_jetbr;
217+
bool as_simpl = is_long || is_kate || is_vscode || is_jetbr;
218+
bool as_rever = is_xterm;
219+
220+
vector<const char*>& rainbow = as_light ? ansi::rainbow_light : ansi::rainbow_dark;
221+
}
222+
223+
224+
namespace E { const char
225+
*pr = "prompt",
226+
*re = "resize";
227+
}
228+
229+
230+
using argnames = struct { // type is pointer to unnamed struct
231+
char *event;
232+
char *print_i;
233+
}*;
234+
235+
236+
struct state {
237+
string event = E::pr;
238+
int rbow_i = V::is_root ? -1 : (V::as_light ? 6 : 8);
239+
240+
static state read(char *args[]) {
241+
state r;
242+
auto a = reinterpret_cast<argnames>(args);
243+
r.event = string(a->event);
244+
r.rbow_i = std::stoi(a->print_i);
245+
return r;
246+
}
247+
string write() {
248+
using namespace bash;
249+
return func(PROMPT_MY, { cmd({ "source", procsub({
250+
PROG, "gen", concat({"\"${1:-", E::pr, "}\""}), std::to_string(rbow_i)
251+
}) }) });
252+
}
253+
};
254+
255+
256+
void shell_gen(char *args[]) {
257+
using namespace ansi;
258+
using namespace bash;
259+
260+
auto s = state::read(args);
261+
if (s.event == E::pr)
262+
s.rbow_i = (s.rbow_i + 1) % V::rainbow.size();
263+
264+
// cerr << TCOLS << " " << USER << " " << DIR << endl;
265+
266+
/* DRAFTS
267+
* Resizing
268+
* { sleep 2; tput sc; tput khome; tput hpa 0; tput dch1; tput rc; } &
269+
*
270+
* Get background color
271+
* \e]11;?\a -> 11;rgb:2323/2626/2727 -> (OR)>7f
272+
*
273+
* Detect cursor not at column 1
274+
* Get last command return code
275+
* Print only last 2 parts of current dir
276+
*/
277+
278+
string
279+
rain = V::rainbow[s.rbow_i],
280+
lrain = noprint(fmt({reset, bold, fg.cust256, rain})),
281+
drain = noprint(fmt({reset, fg.cust256, rain})),
282+
luser = noprint(fmt({reset, V::is_root ? fg.light.red : fg.light.green})),
283+
duser = noprint(fmt({reset, V::is_root ? fg.dark.red : fg.dark.green})),
284+
white = noprint(fmt({reset,
285+
V::as_light ? ""
286+
: V::as_rever
287+
? fg.light.black
288+
: fg.light.white })),
289+
gray = noprint(fmt({reset,
290+
V::as_light ? ""
291+
: V::as_rever
292+
? fg.dark.black
293+
: fg.dark.white }));
294+
295+
string PS1;
296+
297+
if (V::as_simpl)
298+
PS1 = concat({gray, ps::cdir, " ", lrain, ps::euid, drain, "> ", white});
299+
else
300+
PS1 = concat({
301+
lrain, ps::user, white, ") ", gray, ps::cpath, " ",
302+
luser, ps::euid, duser, "> ", white
303+
});
304+
305+
writelines(cout, {
306+
set("PS1", quote1(PS1)),
307+
s.write(),
308+
});
309+
}
310+
311+
312+
void shell_init() {
313+
using namespace bash;
314+
state defaults;
315+
316+
writelines(cout, {
317+
defaults.write(),
318+
// "trap - SIGWINCH",
319+
// format("trap '{} resize' SIGWINCH", PROMPT_MY),
320+
set("PS0", subsh("tput sgr0")),
321+
V::as_rever ? "printf '\\e[?5h\\e[?30h'" : "",
322+
format(
323+
//R"([[ "${{{0}[*]}}" =~ .*\ ?{1}[\ \;].* ]] || {0}+=('{1}'))",
324+
R"([[ "${{{0}[*]}}" == *{1}* ]] || {0}+=('{1}'))",
325+
PROMPT_CMD, PROMPT_MY
326+
),
327+
});
328+
}
329+
330+
331+
void print_help() {
332+
using namespace ansi;
333+
cerr << format(1 + R"h(
334+
Usage: {0} <args..>
335+
336+
Arguments:
337+
help, --help, -h Show this message.
338+
init Print shell script to be evaluated.
339+
gen <state..> Run at {1}.
340+
341+
Load with {3}source <({0} init){2} in your {3}~/.bashrc{2}.
342+
)h", PROG, bash::PROMPT_CMD, reset_all, fmt({fg.light.magenta}));
343+
}
344+
345+
346+
int main(int argc, char *argv[]) { // $0 <cmd>
347+
PROG = argv[0];
348+
if (argc <= 1)
349+
return print_help(), 1;
350+
351+
string arg = argv[1];
352+
353+
if (arg == "help" || arg == "--help" || arg == "-h")
354+
return print_help(), 0;
355+
if (arg == "init")
356+
return shell_init(), 0;
357+
if (arg == "gen")
358+
return shell_gen(&argv[2]), 0;
359+
360+
cerr << "Unknown argument: " << arg << endl;
361+
return print_help(), 1;
362+
}
363+

0 commit comments

Comments
 (0)