#include #include #include #include "elna/interactive.hpp" namespace elna { static termios original_termios; interactive_exception::interactive_exception() : runtime_error("read") { } template static std::pair read_number() { T position{ 0 }; unsigned char c{ 0 }; while (read(STDIN_FILENO, &c, 1) == 1 && std::isdigit(c)) { position = position * 10 + (c - '0'); } return std::make_pair(position, c); } void loop() { editor_history history; do { auto line = read_line(history); if (!line.has_value()) { write(STDOUT_FILENO, "\r\n", 2); break; } history.push(line.value().command_line()); auto tokens = lex(line.value().command_line()); if (!execute(tokens.assume_value())) { break; } } while (true); } std::optional read_line(editor_history& history) { editor_state state; std::string buffer; state.draw(std::back_inserter(buffer)); write(STDOUT_FILENO, buffer.data(), buffer.size()); while (true) { buffer.clear(); auto pressed_key = read_key(); if (pressed_key.empty()) { continue; } switch (state.process_keypress(pressed_key)) { case action::finalize: if (pressed_key == key('d', modifier::ctrl)) { return std::optional(); } else if (pressed_key == '\r') { write(STDOUT_FILENO, "\r\n", 2); return std::make_optional(state); } break; case action::complete: write(STDOUT_FILENO, "\x1b[1E\x1b[2K", 8); state.insert(complete()); write(STDOUT_FILENO, "\x1b[1A", 4); state.draw(std::back_inserter(buffer)); write(STDOUT_FILENO, buffer.data(), buffer.size()); break; case action::redraw: state.draw(std::back_inserter(buffer)); write(STDOUT_FILENO, buffer.data(), buffer.size()); break; case action::write: write(STDOUT_FILENO, &pressed_key, 1); break; case action::move: buffer = cursor_to_column(state.absolute_position()); write(STDOUT_FILENO, buffer.data(), buffer.size()); break; case action::ignore: break; case action::history: editor_history::const_iterator history_iterator = history.cend(); if (pressed_key == key(special_key::arrow_up, modifier::ctrl)) { history_iterator = history.next(); } else if (pressed_key == key(special_key::arrow_down, modifier::ctrl)) { history_iterator = history.prev(); } if (history_iterator != history.cend()) { state.load(*history_iterator); state.draw(std::back_inserter(buffer)); write(STDOUT_FILENO, buffer.data(), buffer.size()); } break; } } } key read_key() { char c{ 0 }; if (read(STDIN_FILENO, &c, 1) == -1 && errno != EAGAIN) { throw interactive_exception(); } if (c != '\x1b') { return key(c); } if (read(STDIN_FILENO, &c, 1) != 1 || c != '[') { return key(); } auto [number, last_char] = read_number(); std::uint8_t modifiers{ 0 }; if (last_char == ';') { auto modifier_response = read_number(); modifiers = modifier_response.first; last_char = modifier_response.second; } if (number == 0 || number == 1) { switch (last_char) { case 'A': return key(special_key::arrow_up, modifiers); case 'B': return key(special_key::arrow_down, modifiers); case 'C': return key(special_key::arrow_right, modifiers); case 'D': return key(special_key::arrow_left, modifiers); case 'H': return key(special_key::home_key, modifiers); case 'F': return key(special_key::end_key, modifiers); } } else if (last_char == '~') { switch (number) { case 5: return key(special_key::page_up, modifiers); case 6: return key(special_key::page_up, modifiers); case 7: return key(special_key::home_key, modifiers); case 8: return key(special_key::end_key, modifiers); } } else if (last_char == 'u') { return key(number, modifiers); } return key(); } bool execute(const std::vector& tokens) { if (tokens.empty()) { return true; } std::string program = tokens.front().identifier(); if (program == "exit") { return false; } else if (program == "cd") { std::filesystem::current_path(tokens[1].identifier()); return true; } std::vector arguments; for (auto argument = tokens.cbegin() + 1; argument != std::cend(tokens); ++argument) { arguments.push_back(argument->identifier()); } launch(program, arguments); return true; } void launch(const std::string& program, const std::vector& arguments) { boost::process::system(boost::process::search_path(program), arguments); } bool enable_raw_mode() { if (tcgetattr(STDIN_FILENO, &original_termios) == -1) { return false; } termios raw = original_termios; raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); raw.c_oflag &= ~(OPOST); raw.c_cflag |= CS8; raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); write(STDOUT_FILENO, start_kitty_keybaord, strlen(start_kitty_keybaord)); return tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != -1; } void disable_raw_mode() { write(STDOUT_FILENO, end_kitty_keybaord, strlen(end_kitty_keybaord)); tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios); } std::string complete() { boost::process::ipstream output; boost::process::system("/usr/bin/fzf", "--height=10", "--layout=reverse", boost::process::std_out > output); std::string selections; std::getline(output, selections, '\n'); return selections; } }