Shell.h 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. /*
  2. * Copyright (c) 2020-2022, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #pragma once
  7. #include "Job.h"
  8. #include "Parser.h"
  9. #include <AK/Array.h>
  10. #include <AK/CircularQueue.h>
  11. #include <AK/DeprecatedString.h>
  12. #include <AK/HashMap.h>
  13. #include <AK/IntrusiveList.h>
  14. #include <AK/StackInfo.h>
  15. #include <AK/StringBuilder.h>
  16. #include <AK/StringView.h>
  17. #include <AK/Types.h>
  18. #include <AK/Vector.h>
  19. #include <LibCore/EventReceiver.h>
  20. #include <LibCore/Notifier.h>
  21. #include <LibLine/Editor.h>
  22. #include <LibMain/Main.h>
  23. #include <termios.h>
  24. #define ENUMERATE_SHELL_BUILTINS() \
  25. __ENUMERATE_SHELL_BUILTIN(alias, InAllModes) \
  26. __ENUMERATE_SHELL_BUILTIN(where, InAllModes) \
  27. __ENUMERATE_SHELL_BUILTIN(cd, InAllModes) \
  28. __ENUMERATE_SHELL_BUILTIN(cdh, InAllModes) \
  29. __ENUMERATE_SHELL_BUILTIN(pwd, InAllModes) \
  30. __ENUMERATE_SHELL_BUILTIN(type, InAllModes) \
  31. __ENUMERATE_SHELL_BUILTIN(exec, InAllModes) \
  32. __ENUMERATE_SHELL_BUILTIN(eval, OnlyInPOSIXMode) \
  33. __ENUMERATE_SHELL_BUILTIN(exit, InAllModes) \
  34. __ENUMERATE_SHELL_BUILTIN(export, InAllModes) \
  35. __ENUMERATE_SHELL_BUILTIN(glob, InAllModes) \
  36. __ENUMERATE_SHELL_BUILTIN(unalias, InAllModes) \
  37. __ENUMERATE_SHELL_BUILTIN(unset, InAllModes) \
  38. __ENUMERATE_SHELL_BUILTIN(set, InAllModes) \
  39. __ENUMERATE_SHELL_BUILTIN(history, InAllModes) \
  40. __ENUMERATE_SHELL_BUILTIN(umask, InAllModes) \
  41. __ENUMERATE_SHELL_BUILTIN(not, InAllModes) \
  42. __ENUMERATE_SHELL_BUILTIN(dirs, InAllModes) \
  43. __ENUMERATE_SHELL_BUILTIN(pushd, InAllModes) \
  44. __ENUMERATE_SHELL_BUILTIN(popd, InAllModes) \
  45. __ENUMERATE_SHELL_BUILTIN(setopt, InAllModes) \
  46. __ENUMERATE_SHELL_BUILTIN(shift, InAllModes) \
  47. __ENUMERATE_SHELL_BUILTIN(source, InAllModes) \
  48. __ENUMERATE_SHELL_BUILTIN(time, InAllModes) \
  49. __ENUMERATE_SHELL_BUILTIN(jobs, InAllModes) \
  50. __ENUMERATE_SHELL_BUILTIN(disown, InAllModes) \
  51. __ENUMERATE_SHELL_BUILTIN(fg, InAllModes) \
  52. __ENUMERATE_SHELL_BUILTIN(bg, InAllModes) \
  53. __ENUMERATE_SHELL_BUILTIN(wait, InAllModes) \
  54. __ENUMERATE_SHELL_BUILTIN(dump, InAllModes) \
  55. __ENUMERATE_SHELL_BUILTIN(kill, InAllModes) \
  56. __ENUMERATE_SHELL_BUILTIN(reset, InAllModes) \
  57. __ENUMERATE_SHELL_BUILTIN(noop, InAllModes) \
  58. __ENUMERATE_SHELL_BUILTIN(break, OnlyInPOSIXMode) \
  59. __ENUMERATE_SHELL_BUILTIN(continue, OnlyInPOSIXMode) \
  60. __ENUMERATE_SHELL_BUILTIN(return, InAllModes) \
  61. __ENUMERATE_SHELL_BUILTIN(read, OnlyInPOSIXMode) \
  62. __ENUMERATE_SHELL_BUILTIN(run_with_env, OnlyInPOSIXMode) \
  63. __ENUMERATE_SHELL_BUILTIN(argsparser_parse, InAllModes) \
  64. __ENUMERATE_SHELL_BUILTIN(shell_set_active_prompt, InAllModes)
  65. #define ENUMERATE_SHELL_OPTIONS() \
  66. __ENUMERATE_SHELL_OPTION(inline_exec_keep_empty_segments, false, "Keep empty segments in inline execute $(...)") \
  67. __ENUMERATE_SHELL_OPTION(verbose, false, "Announce every command that is about to be executed") \
  68. __ENUMERATE_SHELL_OPTION(invoke_program_for_autocomplete, false, "Attempt to use the program being completed itself for autocompletion via --complete")
  69. #define ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS() \
  70. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(concat_lists) \
  71. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(length) \
  72. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(length_across) \
  73. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(remove_suffix) \
  74. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(remove_prefix) \
  75. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(regex_replace) \
  76. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(filter_glob) \
  77. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(split) \
  78. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(join) \
  79. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(value_or_default) \
  80. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(assign_default) \
  81. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(error_if_empty) \
  82. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(null_or_alternative) \
  83. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(defined_value_or_default) \
  84. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(assign_defined_default) \
  85. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(error_if_unset) \
  86. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(null_if_unset_or_alternative) \
  87. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(length_of_variable) \
  88. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(reexpand) \
  89. __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(math)
  90. namespace Shell {
  91. class Shell;
  92. enum class POSIXModeRequirement {
  93. OnlyInPOSIXMode,
  94. InAllModes,
  95. };
  96. class Shell : public Core::EventReceiver {
  97. C_OBJECT(Shell);
  98. public:
  99. constexpr static auto local_init_file_path = "~/.shellrc";
  100. constexpr static auto global_init_file_path = "/etc/shellrc";
  101. constexpr static auto local_posix_init_file_path = "~/.posixshrc";
  102. constexpr static auto global_posix_init_file_path = "/etc/posixshrc";
  103. bool should_format_live() const { return m_should_format_live; }
  104. void set_live_formatting(bool value) { m_should_format_live = value; }
  105. void setup_signals();
  106. void setup_keybinds();
  107. struct SourcePosition {
  108. Optional<DeprecatedString> source_file;
  109. DeprecatedString literal_source_text;
  110. Optional<AST::Position> position;
  111. };
  112. struct RunnablePath {
  113. enum class Kind {
  114. Builtin,
  115. Function,
  116. Alias,
  117. Executable,
  118. };
  119. Kind kind;
  120. DeprecatedString path;
  121. bool operator<(RunnablePath const& other) const
  122. {
  123. return path < other.path;
  124. }
  125. bool operator==(RunnablePath const&) const = default;
  126. };
  127. struct RunnablePathComparator {
  128. int operator()(RunnablePath const& lhs, RunnablePath const& rhs)
  129. {
  130. if (lhs.path > rhs.path)
  131. return 1;
  132. if (lhs.path < rhs.path)
  133. return -1;
  134. return 0;
  135. }
  136. int operator()(StringView lhs, RunnablePath const& rhs)
  137. {
  138. if (lhs > rhs.path)
  139. return 1;
  140. if (lhs < rhs.path)
  141. return -1;
  142. return 0;
  143. }
  144. };
  145. int run_command(StringView, Optional<SourcePosition> = {});
  146. Optional<RunnablePath> runnable_path_for(StringView);
  147. Optional<DeprecatedString> help_path_for(Vector<RunnablePath> visited, RunnablePath const& runnable_path);
  148. ErrorOr<RefPtr<Job>> run_command(const AST::Command&);
  149. Vector<NonnullRefPtr<Job>> run_commands(Vector<AST::Command>&);
  150. bool run_file(DeprecatedString const&, bool explicitly_invoked = true);
  151. ErrorOr<bool> run_builtin(const AST::Command&, Vector<NonnullRefPtr<AST::Rewiring>> const&, int& retval);
  152. bool has_builtin(StringView) const;
  153. ErrorOr<RefPtr<AST::Node>> run_immediate_function(StringView name, AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const&);
  154. static bool has_immediate_function(StringView);
  155. void block_on_job(RefPtr<Job>);
  156. void block_on_pipeline(RefPtr<AST::Pipeline>);
  157. DeprecatedString prompt() const;
  158. static DeprecatedString expand_tilde(StringView expression);
  159. static Vector<DeprecatedString> expand_globs(StringView path, StringView base);
  160. static Vector<DeprecatedString> expand_globs(Vector<StringView> path_segments, StringView base);
  161. ErrorOr<Vector<AST::Command>> expand_aliases(Vector<AST::Command>);
  162. DeprecatedString resolve_path(DeprecatedString) const;
  163. Optional<DeprecatedString> resolve_alias(StringView) const;
  164. static bool has_history_event(StringView);
  165. ErrorOr<RefPtr<AST::Value const>> get_argument(size_t) const;
  166. ErrorOr<RefPtr<AST::Value const>> look_up_local_variable(StringView) const;
  167. ErrorOr<DeprecatedString> local_variable_or(StringView, DeprecatedString const&) const;
  168. void set_local_variable(DeprecatedString const&, RefPtr<AST::Value>, bool only_in_current_frame = false);
  169. void unset_local_variable(StringView, bool only_in_current_frame = false);
  170. void define_function(DeprecatedString name, Vector<DeprecatedString> argnames, RefPtr<AST::Node> body);
  171. bool has_function(StringView);
  172. bool invoke_function(const AST::Command&, int& retval);
  173. DeprecatedString format(StringView, ssize_t& cursor) const;
  174. RefPtr<Line::Editor> editor() const { return m_editor; }
  175. enum class LocalFrameKind {
  176. FunctionOrGlobal,
  177. Block,
  178. };
  179. struct LocalFrame {
  180. LocalFrame(DeprecatedString name, HashMap<DeprecatedString, RefPtr<AST::Value>> variables, LocalFrameKind kind = LocalFrameKind::Block)
  181. : name(move(name))
  182. , local_variables(move(variables))
  183. , is_function_frame(kind == LocalFrameKind::FunctionOrGlobal)
  184. {
  185. }
  186. DeprecatedString name;
  187. HashMap<DeprecatedString, RefPtr<AST::Value>> local_variables;
  188. bool is_function_frame;
  189. };
  190. struct Frame {
  191. Frame(Vector<NonnullOwnPtr<LocalFrame>>& frames, LocalFrame const& frame)
  192. : frames(frames)
  193. , frame(frame)
  194. {
  195. }
  196. ~Frame();
  197. void leak_frame() { should_destroy_frame = false; }
  198. private:
  199. Vector<NonnullOwnPtr<LocalFrame>>& frames;
  200. LocalFrame const& frame;
  201. bool should_destroy_frame { true };
  202. };
  203. [[nodiscard]] Frame push_frame(DeprecatedString name, LocalFrameKind = LocalFrameKind::Block);
  204. void pop_frame();
  205. struct Promise {
  206. struct Data {
  207. struct Unveil {
  208. DeprecatedString path;
  209. DeprecatedString access;
  210. };
  211. DeprecatedString exec_promises;
  212. Vector<Unveil> unveils;
  213. } data;
  214. IntrusiveListNode<Promise> node;
  215. using List = IntrusiveList<&Promise::node>;
  216. };
  217. struct ScopedPromise {
  218. ScopedPromise(Promise::List& promises, Promise&& promise)
  219. : promises(promises)
  220. , promise(move(promise))
  221. {
  222. promises.append(this->promise);
  223. }
  224. ~ScopedPromise()
  225. {
  226. promises.remove(promise);
  227. }
  228. Promise::List& promises;
  229. Promise promise;
  230. };
  231. [[nodiscard]] ScopedPromise promise(Promise::Data data)
  232. {
  233. return { m_active_promises, { move(data), {} } };
  234. }
  235. enum class EscapeMode {
  236. Bareword,
  237. SingleQuotedString,
  238. DoubleQuotedString,
  239. };
  240. static DeprecatedString escape_token_for_double_quotes(StringView token);
  241. static DeprecatedString escape_token_for_single_quotes(StringView token);
  242. static DeprecatedString escape_token(StringView token, EscapeMode = EscapeMode::Bareword);
  243. static DeprecatedString escape_token(Utf32View token, EscapeMode = EscapeMode::Bareword);
  244. static DeprecatedString unescape_token(StringView token);
  245. enum class SpecialCharacterEscapeMode {
  246. Untouched,
  247. Escaped,
  248. QuotedAsEscape,
  249. QuotedAsHex,
  250. };
  251. static SpecialCharacterEscapeMode special_character_escape_mode(u32 c, EscapeMode);
  252. static bool is_glob(StringView);
  253. static Vector<StringView> split_path(StringView);
  254. enum class ExecutableOnly {
  255. Yes,
  256. No
  257. };
  258. ErrorOr<void> highlight(Line::Editor&) const;
  259. Vector<Line::CompletionSuggestion> complete();
  260. Vector<Line::CompletionSuggestion> complete(StringView);
  261. Vector<Line::CompletionSuggestion> complete_program_name(StringView, size_t offset, EscapeMode = EscapeMode::Bareword);
  262. Vector<Line::CompletionSuggestion> complete_variable(StringView, size_t offset);
  263. Vector<Line::CompletionSuggestion> complete_user(StringView, size_t offset);
  264. Vector<Line::CompletionSuggestion> complete_immediate_function_name(StringView, size_t offset);
  265. Vector<Line::CompletionSuggestion> complete_path(StringView base, StringView, size_t offset, ExecutableOnly executable_only, AST::Node const* command_node, AST::Node const*, EscapeMode = EscapeMode::Bareword);
  266. Vector<Line::CompletionSuggestion> complete_option(StringView, StringView, size_t offset, AST::Node const* command_node, AST::Node const*);
  267. ErrorOr<Vector<Line::CompletionSuggestion>> complete_via_program_itself(size_t offset, AST::Node const* command_node, AST::Node const*, EscapeMode escape_mode, StringView known_program_name);
  268. void restore_ios();
  269. u64 find_last_job_id() const;
  270. Job* find_job(u64 id, bool is_pid = false);
  271. Job* current_job() const { return m_current_job; }
  272. void kill_job(Job const*, int sig);
  273. DeprecatedString get_history_path();
  274. void print_path(StringView path);
  275. void cache_path();
  276. bool read_single_line();
  277. void notify_child_event();
  278. bool posix_mode() const { return m_in_posix_mode; }
  279. struct termios termios;
  280. struct termios default_termios;
  281. bool was_interrupted { false };
  282. bool was_resized { false };
  283. DeprecatedString cwd;
  284. DeprecatedString username;
  285. DeprecatedString home;
  286. constexpr static auto TTYNameSize = 32;
  287. constexpr static auto HostNameSize = 64;
  288. char ttyname[TTYNameSize];
  289. char hostname[HostNameSize];
  290. uid_t uid;
  291. Optional<int> last_return_code;
  292. Vector<DeprecatedString> directory_stack;
  293. CircularQueue<DeprecatedString, 8> cd_history; // FIXME: have a configurable cd history length
  294. HashMap<u64, NonnullRefPtr<Job>> jobs;
  295. Vector<RunnablePath, 256> cached_path;
  296. DeprecatedString current_script;
  297. enum ShellEventType {
  298. ReadLine,
  299. };
  300. enum class ShellError {
  301. None,
  302. InternalControlFlowBreak,
  303. InternalControlFlowContinue,
  304. InternalControlFlowReturn,
  305. InternalControlFlowInterrupted,
  306. InternalControlFlowKilled,
  307. EvaluatedSyntaxError,
  308. NonExhaustiveMatchRules,
  309. InvalidGlobError,
  310. InvalidSliceContentsError,
  311. OpenFailure,
  312. OutOfMemory,
  313. LaunchError,
  314. PipeFailure,
  315. WriteFailure,
  316. };
  317. void raise_error(ShellError kind, DeprecatedString description, Optional<AST::Position> position = {})
  318. {
  319. m_error = kind;
  320. m_error_description = move(description);
  321. if (m_source_position.has_value() && position.has_value())
  322. m_source_position.value().position = position.release_value();
  323. }
  324. bool has_error(ShellError err) const { return m_error == err; }
  325. bool has_any_error() const { return !has_error(ShellError::None); }
  326. DeprecatedString const& error_description() const { return m_error_description; }
  327. ShellError take_error()
  328. {
  329. auto err = m_error;
  330. m_error = ShellError::None;
  331. m_error_description = {};
  332. return err;
  333. }
  334. void possibly_print_error() const;
  335. static bool is_control_flow(ShellError error)
  336. {
  337. switch (error) {
  338. case ShellError::InternalControlFlowBreak:
  339. case ShellError::InternalControlFlowContinue:
  340. case ShellError::InternalControlFlowReturn:
  341. case ShellError::InternalControlFlowInterrupted:
  342. case ShellError::InternalControlFlowKilled:
  343. return true;
  344. default:
  345. return false;
  346. }
  347. }
  348. #define __ENUMERATE_SHELL_OPTION(name, default_, description) \
  349. bool name { default_ };
  350. struct Options {
  351. ENUMERATE_SHELL_OPTIONS();
  352. } options;
  353. #undef __ENUMERATE_SHELL_OPTION
  354. private:
  355. Shell(Line::Editor&, bool attempt_interactive, bool posix_mode = false);
  356. Shell();
  357. virtual ~Shell() override;
  358. void destroy();
  359. void initialize(bool attempt_interactive);
  360. RefPtr<AST::Node> parse(StringView, bool interactive = false, bool as_command = true) const;
  361. void timer_event(Core::TimerEvent&) override;
  362. void set_user_prompt();
  363. bool is_allowed_to_modify_termios(const AST::Command&) const;
  364. void bring_cursor_to_beginning_of_a_line() const;
  365. Optional<int> resolve_job_spec(StringView);
  366. void add_entry_to_cache(RunnablePath const&);
  367. void remove_entry_from_cache(StringView);
  368. void stop_all_jobs();
  369. Job* m_current_job { nullptr };
  370. LocalFrame* find_frame_containing_local_variable(StringView name);
  371. LocalFrame const* find_frame_containing_local_variable(StringView name) const
  372. {
  373. return const_cast<Shell*>(this)->find_frame_containing_local_variable(name);
  374. }
  375. void run_tail(RefPtr<Job>);
  376. void run_tail(const AST::Command&, const AST::NodeWithAction&, int head_exit_code);
  377. [[noreturn]] void execute_process(Vector<char const*>&& argv);
  378. ErrorOr<void> execute_process(Span<StringView> argv);
  379. virtual void custom_event(Core::CustomEvent&) override;
  380. #define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(name) \
  381. ErrorOr<RefPtr<AST::Node>> immediate_##name(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const&);
  382. ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS();
  383. #undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION
  384. ErrorOr<RefPtr<AST::Node>> immediate_length_impl(AST::ImmediateExpression& invoking_node, Vector<NonnullRefPtr<AST::Node>> const&, bool across);
  385. #define __ENUMERATE_SHELL_BUILTIN(builtin, _mode) \
  386. ErrorOr<int> builtin_##builtin(Main::Arguments);
  387. ENUMERATE_SHELL_BUILTINS();
  388. #undef __ENUMERATE_SHELL_BUILTIN
  389. static constexpr Array builtin_names = {
  390. #define __ENUMERATE_SHELL_BUILTIN(builtin, _mode) #builtin##sv,
  391. ENUMERATE_SHELL_BUILTINS()
  392. #undef __ENUMERATE_SHELL_BUILTIN
  393. "."sv, // Needs to be aliased to "source" in POSIX mode.
  394. // clang-format off
  395. // Clang-format does not properly indent this, it gives it 4 spaces too few.
  396. ":"sv, // POSIX-y name for "noop".
  397. // clang-format on
  398. };
  399. struct ShellFunction {
  400. DeprecatedString name;
  401. Vector<DeprecatedString> arguments;
  402. RefPtr<AST::Node> body;
  403. };
  404. ErrorOr<String> serialize_function_definition(ShellFunction const&) const;
  405. bool m_should_ignore_jobs_on_next_exit { false };
  406. pid_t m_pid { 0 };
  407. HashMap<DeprecatedString, ShellFunction> m_functions;
  408. Vector<NonnullOwnPtr<LocalFrame>> m_local_frames;
  409. Promise::List m_active_promises;
  410. Vector<NonnullRefPtr<AST::Redirection>> m_global_redirections;
  411. HashMap<DeprecatedString, DeprecatedString> m_aliases;
  412. bool m_is_interactive { true };
  413. bool m_is_subshell { false };
  414. bool m_should_reinstall_signal_handlers { true };
  415. bool m_in_posix_mode { false };
  416. ShellError m_error { ShellError::None };
  417. DeprecatedString m_error_description;
  418. Optional<SourcePosition> m_source_position;
  419. bool m_should_format_live { false };
  420. RefPtr<Line::Editor> m_editor;
  421. bool m_default_constructed { false };
  422. mutable bool m_last_continuation_state { false }; // false == not needed.
  423. Optional<size_t> m_history_autosave_time;
  424. StackInfo m_completion_stack_info;
  425. RefPtr<AST::Node> m_prompt_command_node;
  426. mutable Optional<DeprecatedString> m_next_scheduled_prompt_text;
  427. };
  428. [[maybe_unused]] static constexpr bool is_word_character(char c)
  429. {
  430. return c == '_' || (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || (c <= '9' && c >= '0');
  431. }
  432. inline size_t find_offset_into_node(StringView unescaped_text, size_t escaped_offset, Shell::EscapeMode escape_mode)
  433. {
  434. size_t unescaped_offset = 0;
  435. size_t offset = 0;
  436. auto do_find_offset = [&](auto& unescaped_text) {
  437. for (auto c : unescaped_text) {
  438. if (offset == escaped_offset)
  439. return unescaped_offset;
  440. switch (Shell::special_character_escape_mode(c, escape_mode)) {
  441. case Shell::SpecialCharacterEscapeMode::Untouched:
  442. break;
  443. case Shell::SpecialCharacterEscapeMode::Escaped:
  444. ++offset; // X -> \X
  445. break;
  446. case Shell::SpecialCharacterEscapeMode::QuotedAsEscape:
  447. switch (escape_mode) {
  448. case Shell::EscapeMode::Bareword:
  449. offset += 3; // X -> "\Y"
  450. break;
  451. case Shell::EscapeMode::SingleQuotedString:
  452. offset += 5; // X -> '"\Y"'
  453. break;
  454. case Shell::EscapeMode::DoubleQuotedString:
  455. offset += 1; // X -> \Y
  456. break;
  457. }
  458. break;
  459. case Shell::SpecialCharacterEscapeMode::QuotedAsHex:
  460. switch (escape_mode) {
  461. case Shell::EscapeMode::Bareword:
  462. offset += 2; // X -> "\..."
  463. break;
  464. case Shell::EscapeMode::SingleQuotedString:
  465. offset += 4; // X -> '"\..."'
  466. break;
  467. case Shell::EscapeMode::DoubleQuotedString:
  468. // X -> \...
  469. break;
  470. }
  471. if (c > NumericLimits<u8>::max())
  472. offset += 8; // X -> "\uhhhhhhhh"
  473. else
  474. offset += 3; // X -> "\xhh"
  475. break;
  476. }
  477. ++offset;
  478. ++unescaped_offset;
  479. }
  480. return unescaped_offset;
  481. };
  482. Utf8View view { unescaped_text };
  483. if (view.validate())
  484. return do_find_offset(view);
  485. return do_find_offset(unescaped_text);
  486. }
  487. }
  488. namespace AK {
  489. template<>
  490. struct Traits<Shell::Shell::RunnablePath> : public GenericTraits<Shell::Shell::RunnablePath> {
  491. static constexpr bool is_trivial() { return false; }
  492. static bool equals(Shell::Shell::RunnablePath const& self, Shell::Shell::RunnablePath const& other)
  493. {
  494. return self == other;
  495. }
  496. static bool equals(Shell::Shell::RunnablePath const& self, StringView other)
  497. {
  498. return self.path == other;
  499. }
  500. static bool equals(Shell::Shell::RunnablePath const& self, DeprecatedString const& other)
  501. {
  502. return self.path == other;
  503. }
  504. };
  505. }