Tidying
This commit is contained in:
parent
379b4c47f4
commit
7e499195e5
30
src/help.cpp
30
src/help.cpp
@ -1,30 +0,0 @@
|
||||
#include "main.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
void print_help(const boost::program_options::options_description& desc) {
|
||||
std::cout << "Usage: dropshell [OPTIONS] COMMAND [ARGS]" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "A tool for managing server configurations" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Commands:" << std::endl;
|
||||
std::cout << " help Show this help message" << std::endl;
|
||||
std::cout << " version Show version information" << std::endl;
|
||||
std::cout << " init DIR Initialize the user directory for server configurations" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << " status Check system status" << std::endl;
|
||||
std::cout << " servers List configured servers" << std::endl;
|
||||
std::cout << " servers NAME Show details for specific server" << std::endl;
|
||||
std::cout << " templates List available templates" << std::endl;
|
||||
std::cout << " run SERVER SERVICE COMMAND Run a command on a specific service" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Examples:" << std::endl;
|
||||
std::cout << " dropshell servers" << std::endl;
|
||||
std::cout << " dropshell servers myserver" << std::endl;
|
||||
std::cout << " dropshell init /path/to/directory" << std::endl;
|
||||
std::cout << " dropshell templates" << std::endl;
|
||||
std::cout << " dropshell run myserver myservice status" << std::endl;
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
@ -1,613 +0,0 @@
|
||||
#include "interactive/interactive.hpp"
|
||||
#include <ncurses.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
#include <locale>
|
||||
#include <cwchar>
|
||||
#include <term.h>
|
||||
#include <string.h>
|
||||
#include "interactive.hpp"
|
||||
#include <map>
|
||||
|
||||
namespace interactive {
|
||||
|
||||
// Forward declarations
|
||||
void process_line(WINDOW* win, const std::string& text, const std::map<std::string, int>& color_pair_map);
|
||||
|
||||
// Function to convert ANSI escape codes to ncurses attributes and print text
|
||||
void ansi_print(WINDOW* win, const std::string& text) {
|
||||
// Ensure we're using a UTF-8 compatible locale
|
||||
static bool locale_initialized = false;
|
||||
if (!locale_initialized) {
|
||||
setlocale(LC_ALL, "");
|
||||
locale_initialized = true;
|
||||
}
|
||||
|
||||
static int color_pairs_initialized = 0;
|
||||
static std::map<std::string, int> color_pair_map;
|
||||
|
||||
// Initialize color pairs if not done yet
|
||||
if (!color_pairs_initialized) {
|
||||
// Check if terminal supports colors
|
||||
if (has_colors()) {
|
||||
// Define color pairs for common ANSI colors
|
||||
init_pair(1, COLOR_RED, -1);
|
||||
init_pair(2, COLOR_GREEN, -1);
|
||||
init_pair(3, COLOR_YELLOW, -1);
|
||||
init_pair(4, COLOR_BLUE, -1);
|
||||
init_pair(5, COLOR_MAGENTA, -1);
|
||||
init_pair(6, COLOR_CYAN, -1);
|
||||
init_pair(7, COLOR_WHITE, -1);
|
||||
|
||||
// Try to define extended colors if possible
|
||||
if (COLORS >= 256) {
|
||||
init_pair(8, 8, -1); // Dark grey
|
||||
init_pair(9, 142, -1); // Dark yellow (approximation)
|
||||
init_pair(10, 250, -1); // Light grey (approximation)
|
||||
} else {
|
||||
// Fallback for terminals with fewer colors
|
||||
init_pair(8, COLOR_BLACK, -1); // Dark grey fallback
|
||||
init_pair(9, COLOR_YELLOW, -1); // Dark yellow fallback
|
||||
init_pair(10, COLOR_WHITE, -1); // Light grey fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Map ANSI codes to color pairs
|
||||
color_pair_map["\033[1;31m"] = 1; // Red
|
||||
color_pair_map["\033[1;32m"] = 2; // Green
|
||||
color_pair_map["\033[1;33m"] = 3; // Yellow
|
||||
color_pair_map["\033[1;34m"] = 4; // Blue
|
||||
color_pair_map["\033[1;35m"] = 5; // Magenta
|
||||
color_pair_map["\033[1;36m"] = 6; // Cyan
|
||||
color_pair_map["\033[1;37m"] = 7; // White
|
||||
color_pair_map["\033[90m"] = 8; // Dark Grey
|
||||
color_pair_map["\033[38;5;142m"] = 9; // Dark Yellow
|
||||
color_pair_map["\033[38;5;250m"] = 10; // Light Grey
|
||||
|
||||
// Also map the non-bold versions
|
||||
color_pair_map["\033[31m"] = 1; // Red
|
||||
color_pair_map["\033[32m"] = 2; // Green
|
||||
color_pair_map["\033[33m"] = 3; // Yellow
|
||||
color_pair_map["\033[34m"] = 4; // Blue
|
||||
color_pair_map["\033[35m"] = 5; // Magenta
|
||||
color_pair_map["\033[36m"] = 6; // Cyan
|
||||
color_pair_map["\033[37m"] = 7; // White
|
||||
|
||||
color_pairs_initialized = 1;
|
||||
}
|
||||
|
||||
// Handle newlines in the text
|
||||
size_t start = 0;
|
||||
size_t end = text.find('\n');
|
||||
|
||||
// If no newlines, process the whole text at once
|
||||
if (end == std::string::npos) {
|
||||
process_line(win, text, color_pair_map);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process each line separately
|
||||
while (end != std::string::npos) {
|
||||
// Process this line
|
||||
std::string line = text.substr(start, end - start);
|
||||
process_line(win, line, color_pair_map);
|
||||
|
||||
// Move to next line
|
||||
waddch(win, '\n');
|
||||
|
||||
// Find next line
|
||||
start = end + 1;
|
||||
end = text.find('\n', start);
|
||||
}
|
||||
|
||||
// Process any remaining text after the last newline
|
||||
if (start < text.length()) {
|
||||
process_line(win, text.substr(start), color_pair_map);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to process a single line of text
|
||||
void process_line(WINDOW* win, const std::string& text, const std::map<std::string, int>& color_pair_map) {
|
||||
size_t pos = 0;
|
||||
int current_attrs = 0;
|
||||
int current_color_pair = 0;
|
||||
|
||||
while (pos < text.length()) {
|
||||
// Check for ANSI escape sequence
|
||||
if (text[pos] == '\033' && pos + 1 < text.length() && text[pos + 1] == '[') {
|
||||
// Find the end of the escape sequence (marked by 'm')
|
||||
size_t end_pos = text.find('m', pos);
|
||||
if (end_pos != std::string::npos) {
|
||||
std::string esc_seq = text.substr(pos, end_pos - pos + 1);
|
||||
|
||||
// Reset attributes if the sequence is a reset code
|
||||
if (esc_seq == "\033[0m") {
|
||||
wattrset(win, A_NORMAL);
|
||||
current_attrs = 0;
|
||||
current_color_pair = 0;
|
||||
}
|
||||
// Handle bold attribute
|
||||
else if (esc_seq == "\033[1m") {
|
||||
current_attrs |= A_BOLD;
|
||||
wattrset(win, COLOR_PAIR(current_color_pair) | current_attrs);
|
||||
}
|
||||
// Apply color pair if known
|
||||
else if (color_pair_map.find(esc_seq) != color_pair_map.end()) {
|
||||
current_color_pair = color_pair_map.at(esc_seq);
|
||||
// Check if this is a bold sequence (contains "1;")
|
||||
if (esc_seq.find("1;") != std::string::npos) {
|
||||
current_attrs |= A_BOLD;
|
||||
}
|
||||
wattrset(win, COLOR_PAIR(current_color_pair) | current_attrs);
|
||||
}
|
||||
// Handle more complex color sequences (38;5;XXXm)
|
||||
else if (esc_seq.find("38;5;") != std::string::npos) {
|
||||
// Extract the color number
|
||||
std::string color_num_str = esc_seq.substr(esc_seq.find("38;5;") + 5);
|
||||
color_num_str = color_num_str.substr(0, color_num_str.length() - 1); // Remove 'm'
|
||||
|
||||
try {
|
||||
int color_num = std::stoi(color_num_str);
|
||||
// Create a new color pair for this color if needed
|
||||
int pair_num = 11 + color_num; // Start after our predefined pairs
|
||||
|
||||
// Only initialize if in valid range and we haven't seen this color before
|
||||
if (color_num < COLORS && color_pair_map.find(esc_seq) == color_pair_map.end()) {
|
||||
// Try to initialize, but catch errors in case color is invalid
|
||||
try {
|
||||
init_pair(pair_num, color_num, -1);
|
||||
const_cast<std::map<std::string, int>&>(color_pair_map)[esc_seq] = pair_num;
|
||||
} catch (...) {
|
||||
// If we can't initialize this color, use a default
|
||||
const_cast<std::map<std::string, int>&>(color_pair_map)[esc_seq] = 7; // White
|
||||
}
|
||||
}
|
||||
|
||||
if (color_pair_map.find(esc_seq) != color_pair_map.end()) {
|
||||
current_color_pair = color_pair_map.at(esc_seq);
|
||||
wattrset(win, COLOR_PAIR(current_color_pair) | current_attrs);
|
||||
}
|
||||
} catch (...) {
|
||||
// If we can't parse the color, just ignore it
|
||||
}
|
||||
}
|
||||
|
||||
pos = end_pos + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip control characters (except newline, tab, and others that should be displayed)
|
||||
unsigned char ch = static_cast<unsigned char>(text[pos]);
|
||||
if (ch < 32 && ch != '\t' && ch != '\n' && ch != '\r') {
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Print the character as-is
|
||||
waddch(win, text[pos]);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
int fullscreen_window::ncurses_streambuf::overflow(int c) {
|
||||
if (c != EOF) {
|
||||
buffer += static_cast<char>(c);
|
||||
if (c == '\n') {
|
||||
// Process buffer with proper escape sequence handling
|
||||
ansi_print(win, buffer);
|
||||
wrefresh(win);
|
||||
buffer.clear();
|
||||
} else if (buffer.length() > 1024) {
|
||||
// If buffer is getting too large without a newline,
|
||||
// process it anyway to prevent memory issues
|
||||
ansi_print(win, buffer);
|
||||
wrefresh(win);
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
fullscreen_window::fullscreen_window(std::string title) {
|
||||
// Set locale for wide character support
|
||||
std::setlocale(LC_ALL, "");
|
||||
|
||||
// Initialize ncurses with basic terminal capabilities
|
||||
// Don't force a specific TERM type which could cause compatibility issues
|
||||
|
||||
initscr();
|
||||
raw(); // Use raw mode for better control
|
||||
noecho();
|
||||
keypad(stdscr, TRUE); // Enable keypad
|
||||
curs_set(0); // Hide cursor
|
||||
set_escdelay(25); // Set ESC delay to 25ms
|
||||
|
||||
// Enable color support if available
|
||||
if (has_colors()) {
|
||||
start_color();
|
||||
use_default_colors();
|
||||
}
|
||||
|
||||
refresh();
|
||||
|
||||
// Create display window (takes up all but bottom 4 lines)
|
||||
int max_y, max_x;
|
||||
getmaxyx(stdscr, max_y, max_x);
|
||||
display_win = newwin(max_y - 4, max_x, 0, 0);
|
||||
scrollok(display_win, TRUE);
|
||||
keypad(display_win, TRUE); // Enable keypad for display window
|
||||
|
||||
// Important: set terminal capabilities for this window
|
||||
nodelay(display_win, TRUE);
|
||||
|
||||
wrefresh(display_win);
|
||||
|
||||
// Create input window (bottom 4 lines)
|
||||
input_win = newwin(4, max_x, max_y - 4, 0);
|
||||
box(input_win, 0, 0);
|
||||
keypad(input_win, TRUE); // Enable keypad for input window
|
||||
wrefresh(input_win);
|
||||
|
||||
// Set up output redirection
|
||||
cout_buf = new ncurses_streambuf(display_win);
|
||||
cerr_buf = new ncurses_streambuf(display_win);
|
||||
|
||||
// Save original streambufs
|
||||
original_cout = std::cout.rdbuf();
|
||||
original_cerr = std::cerr.rdbuf();
|
||||
|
||||
// Redirect cout and cerr to our custom streambuf
|
||||
std::cout.rdbuf(cout_buf);
|
||||
std::cerr.rdbuf(cerr_buf);
|
||||
}
|
||||
|
||||
void fullscreen_window::close() {
|
||||
if (is_closed) return;
|
||||
is_closed = true;
|
||||
|
||||
// Restore original streambufs
|
||||
std::cout.rdbuf(original_cout);
|
||||
std::cerr.rdbuf(original_cerr);
|
||||
|
||||
// Clean up streambufs
|
||||
delete cout_buf;
|
||||
delete cerr_buf;
|
||||
|
||||
// Clean up ncurses windows
|
||||
delwin(display_win);
|
||||
delwin(input_win);
|
||||
|
||||
// End ncurses
|
||||
endwin();
|
||||
}
|
||||
|
||||
fullscreen_window::~fullscreen_window() {
|
||||
close();
|
||||
}
|
||||
|
||||
void fullscreen_window::clear_display()
|
||||
{
|
||||
werase(display_win);
|
||||
wclear(display_win); // Also call wclear for complete clearing
|
||||
wmove(display_win, 0, 0); // Move cursor to top-left position
|
||||
wrefresh(display_win);
|
||||
}
|
||||
|
||||
void fullscreen_window::set_input_text_display(std::string text) {
|
||||
werase(input_win);
|
||||
box(input_win, 0, 0);
|
||||
|
||||
// Center the text
|
||||
int max_y, max_x;
|
||||
getmaxyx(input_win, max_y, max_x);
|
||||
int start_x = (max_x - text.length()) / 2;
|
||||
mvwprintw(input_win, max_y/2, start_x, "%s", text.c_str());
|
||||
|
||||
wrefresh(input_win);
|
||||
}
|
||||
|
||||
std::string fullscreen_window::set_input_text_entry(std::string prompt) {
|
||||
werase(input_win);
|
||||
box(input_win, 0, 0);
|
||||
|
||||
// Display prompt
|
||||
mvwprintw(input_win, 1, 2, "%s", prompt.c_str());
|
||||
wrefresh(input_win);
|
||||
|
||||
// Set up input
|
||||
echo();
|
||||
curs_set(1);
|
||||
std::string input;
|
||||
int ch;
|
||||
int x = prompt.length() + 3;
|
||||
|
||||
while ((ch = wgetch(input_win)) != '\n') {
|
||||
if (ch == 27) { // ESC key
|
||||
input = "";
|
||||
break;
|
||||
}
|
||||
if (ch == KEY_BACKSPACE || ch == 127) {
|
||||
if (!input.empty()) {
|
||||
input.pop_back();
|
||||
x--;
|
||||
mvwdelch(input_win, 1, x);
|
||||
}
|
||||
} else if (isprint(ch)) {
|
||||
input += ch;
|
||||
mvwaddch(input_win, 1, x++, ch);
|
||||
}
|
||||
}
|
||||
|
||||
noecho();
|
||||
curs_set(0);
|
||||
return input;
|
||||
}
|
||||
|
||||
std::string fullscreen_window::set_input_multiple_choice(std::string prompt, std::vector<std::string> choices, std::string default_choice) {
|
||||
werase(input_win);
|
||||
box(input_win, 0, 0);
|
||||
|
||||
// Display prompt
|
||||
mvwprintw(input_win, 1, 2, "%s", prompt.c_str());
|
||||
|
||||
int selected = 0;
|
||||
if (!default_choice.empty()) {
|
||||
for (size_t i = 0; i < choices.size(); ++i) {
|
||||
if (choices[i] == default_choice) {
|
||||
selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string filter = "";
|
||||
std::string last_filter = "";
|
||||
auto last_key_time = std::chrono::steady_clock::now();
|
||||
int scroll_offset = 0;
|
||||
int last_selected = -1;
|
||||
int last_scroll_offset = -1;
|
||||
|
||||
// Pre-compute lowercase versions of choices for case-insensitive matching
|
||||
std::vector<std::string> lowercase_choices;
|
||||
lowercase_choices.reserve(choices.size());
|
||||
for (const auto& choice : choices) {
|
||||
std::string lower;
|
||||
lower.reserve(choice.size());
|
||||
std::transform(choice.begin(), choice.end(), std::back_inserter(lower), ::tolower);
|
||||
lowercase_choices.push_back(std::move(lower));
|
||||
}
|
||||
|
||||
// Initial filtered choices (all choices)
|
||||
std::vector<std::string> filtered_choices = choices;
|
||||
|
||||
while (true) {
|
||||
// Only recalculate filtered choices if filter changed
|
||||
if (filter != last_filter) {
|
||||
if (filter.empty()) {
|
||||
filtered_choices = choices;
|
||||
} else {
|
||||
filtered_choices.clear();
|
||||
// Convert filter to lowercase once
|
||||
std::string lowercase_filter;
|
||||
lowercase_filter.reserve(filter.size());
|
||||
std::transform(filter.begin(), filter.end(), std::back_inserter(lowercase_filter), ::tolower);
|
||||
|
||||
// Filter using pre-computed lowercase versions
|
||||
for (size_t i = 0; i < choices.size(); ++i) {
|
||||
if (lowercase_choices[i].find(lowercase_filter) == 0) {
|
||||
filtered_choices.push_back(choices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get window dimensions
|
||||
int max_y, max_x;
|
||||
getmaxyx(input_win, max_y, max_x);
|
||||
int available_width = max_x - 4; // -4 for borders
|
||||
|
||||
// Calculate scroll offset to keep selected item visible
|
||||
if (selected >= 0 && selected < filtered_choices.size()) {
|
||||
int item_start = 0;
|
||||
for (int i = 0; i < selected; i++) {
|
||||
item_start += filtered_choices[i].length() + 3;
|
||||
}
|
||||
int item_end = item_start + filtered_choices[selected].length();
|
||||
|
||||
if (item_start < scroll_offset) {
|
||||
scroll_offset = item_start;
|
||||
} else if (item_end > scroll_offset + available_width) {
|
||||
scroll_offset = item_end - available_width;
|
||||
}
|
||||
}
|
||||
|
||||
// Only redraw if selection or scroll changed
|
||||
if (selected != last_selected || scroll_offset != last_scroll_offset || last_filter != filter) {
|
||||
// Clear the input window except for the prompt
|
||||
werase(input_win);
|
||||
box(input_win, 0, 0);
|
||||
mvwprintw(input_win, 1, 2, "%s", prompt.c_str());
|
||||
|
||||
// Display filtered choices horizontally
|
||||
int x = 2 - scroll_offset;
|
||||
for (size_t i = 0; i < filtered_choices.size(); i++) {
|
||||
if (i == selected) {
|
||||
wattron(input_win, A_REVERSE);
|
||||
}
|
||||
|
||||
// Only draw if visible
|
||||
if (x + filtered_choices[i].length() > 0 && x < max_x - 2) {
|
||||
mvwprintw(input_win, 2, x, "%s", filtered_choices[i].c_str());
|
||||
}
|
||||
|
||||
if (i == selected) {
|
||||
wattroff(input_win, A_REVERSE);
|
||||
}
|
||||
|
||||
x += filtered_choices[i].length() + 3; // +3 for " | " separator
|
||||
|
||||
// Draw separator if not last item
|
||||
if (i < filtered_choices.size() - 1) {
|
||||
if (x - 3 + 3 > 0 && x - 3 < max_x - 2) {
|
||||
mvwprintw(input_win, 2, x - 3, " | ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrefresh(input_win);
|
||||
|
||||
// Update last state
|
||||
last_selected = selected;
|
||||
last_scroll_offset = scroll_offset;
|
||||
last_filter = filter;
|
||||
}
|
||||
|
||||
int ch = wgetch(input_win);
|
||||
|
||||
// Handle key input
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
if (!filtered_choices.empty()) {
|
||||
return filtered_choices[selected];
|
||||
}
|
||||
break;
|
||||
|
||||
case 27: // ESC key
|
||||
if (!filter.empty()) {
|
||||
// Clear the filter and reset selection
|
||||
filter = "";
|
||||
selected = 0;
|
||||
scroll_offset = 0;
|
||||
} else {
|
||||
// Only return if filter is already empty
|
||||
return "";
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_LEFT:
|
||||
case KEY_UP:
|
||||
if (selected > 0) {
|
||||
selected--;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_RIGHT:
|
||||
case KEY_DOWN:
|
||||
if (selected < filtered_choices.size() - 1) {
|
||||
selected++;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (isalnum(ch)) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_key_time).count();
|
||||
|
||||
if (elapsed > 500) { // Reset filter if too much time has passed
|
||||
filter = "";
|
||||
}
|
||||
|
||||
filter += ch;
|
||||
selected = 0;
|
||||
last_key_time = now;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool fullscreen_window::set_input_yes_no(std::string prompt) {
|
||||
werase(input_win);
|
||||
box(input_win, 0, 0);
|
||||
|
||||
// Display prompt
|
||||
mvwprintw(input_win, 1, 2, "%s", prompt.c_str());
|
||||
|
||||
int selected = 0; // 0 for Yes, 1 for No
|
||||
const char* options[] = {"Yes", "No"};
|
||||
|
||||
while (true) {
|
||||
// Display options
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (i == selected) {
|
||||
wattron(input_win, A_REVERSE);
|
||||
}
|
||||
mvwprintw(input_win, 2, 2 + i * 10, "%s", options[i]);
|
||||
if (i == selected) {
|
||||
wattroff(input_win, A_REVERSE);
|
||||
}
|
||||
}
|
||||
|
||||
wrefresh(input_win);
|
||||
|
||||
int ch = wgetch(input_win);
|
||||
|
||||
if (ch == '\n') {
|
||||
return selected == 0;
|
||||
} else if (ch == 'y' || ch == 'Y') {
|
||||
return true;
|
||||
} else if (ch == 'n' || ch == 'N') {
|
||||
return false;
|
||||
} else if (ch == KEY_LEFT) {
|
||||
if (selected > 0) {
|
||||
selected = 0;
|
||||
}
|
||||
} else if (ch == KEY_RIGHT) {
|
||||
if (selected < 1) {
|
||||
selected = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char fullscreen_window::set_input_yes_no_cancel(std::string prompt) {
|
||||
werase(input_win);
|
||||
box(input_win, 0, 0);
|
||||
|
||||
// Display prompt
|
||||
mvwprintw(input_win, 1, 2, "%s", prompt.c_str());
|
||||
|
||||
int selected = 0; // 0 for Yes, 1 for No, 2 for Cancel
|
||||
const char* options[] = {"Yes", "No", "Cancel"};
|
||||
|
||||
while (true) {
|
||||
// Display options
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (i == selected) {
|
||||
wattron(input_win, A_REVERSE);
|
||||
}
|
||||
mvwprintw(input_win, 2, 2 + i * 10, "%s", options[i]);
|
||||
if (i == selected) {
|
||||
wattroff(input_win, A_REVERSE);
|
||||
}
|
||||
}
|
||||
|
||||
wrefresh(input_win);
|
||||
|
||||
int ch = wgetch(input_win);
|
||||
|
||||
if (ch == '\n') {
|
||||
return selected == 0 ? 'y' : (selected == 1 ? 'n' : 'c');
|
||||
} else if (ch == 'y' || ch == 'Y') {
|
||||
return 'y';
|
||||
} else if (ch == 'n' || ch == 'N') {
|
||||
return 'n';
|
||||
} else if (ch == 'c' || ch == 'C') {
|
||||
return 'c';
|
||||
} else if (ch == KEY_LEFT) {
|
||||
if (selected > 0) {
|
||||
selected--;
|
||||
}
|
||||
} else if (ch == KEY_RIGHT) {
|
||||
if (selected < 2) {
|
||||
selected++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace interactive
|
@ -1,84 +0,0 @@
|
||||
#ifndef INTERACTIVE_HPP
|
||||
#define INTERACTIVE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ncurses.h>
|
||||
#include <streambuf>
|
||||
#include <iostream>
|
||||
|
||||
namespace interactive {
|
||||
|
||||
// Function to convert ANSI escape codes to ncurses attributes and print text
|
||||
void ansi_print(WINDOW* win, const std::string& text);
|
||||
|
||||
class fullscreen_window {
|
||||
private:
|
||||
WINDOW* display_win;
|
||||
WINDOW* input_win;
|
||||
|
||||
// Original streambufs to restore later
|
||||
std::streambuf* original_cout;
|
||||
std::streambuf* original_cerr;
|
||||
|
||||
// Custom streambuf for redirecting output
|
||||
class ncurses_streambuf : public std::streambuf {
|
||||
private:
|
||||
WINDOW* win;
|
||||
std::string buffer;
|
||||
|
||||
protected:
|
||||
virtual int overflow(int c) override;
|
||||
|
||||
public:
|
||||
ncurses_streambuf(WINDOW* w) : win(w) {}
|
||||
};
|
||||
|
||||
ncurses_streambuf* cout_buf;
|
||||
ncurses_streambuf* cerr_buf;
|
||||
bool is_closed = false;
|
||||
|
||||
public:
|
||||
// uses ncurses to create a fullscreen text area, with two windows:
|
||||
// one (display) that shows everything sent to stdout or stderr, displaying any text output from the program
|
||||
// and the other (input) for interactive input
|
||||
// the input window is always at the bottom of the screen, and the display window takes up the rest of the screen
|
||||
// the input window is 4 rows high, and the display window takes up the rest of the screen
|
||||
// the two windows take up the entire screen, and the program can handle resizing
|
||||
// pressing the ` key at any time will bring up a confirmation dialog to quit the program
|
||||
fullscreen_window(std::string title);
|
||||
|
||||
// Destructor to restore original streambufs
|
||||
~fullscreen_window();
|
||||
|
||||
// clears the display window and refreshes it
|
||||
void clear_display();
|
||||
|
||||
// just display centered text in the input window. returns immediately.
|
||||
void set_input_text_display(std::string text);
|
||||
|
||||
// displays the prompt and a text entry box.
|
||||
// only returns after the user has entered text and pressed enter. pressing escape returns an empty string.
|
||||
std::string set_input_text_entry(std::string prompt);
|
||||
|
||||
// displays the prompt and then a list of choices.
|
||||
// returns when user selects a choice using arrow keys and presses enter. pressing escape returns an empty string.
|
||||
// letter keys filter by choices to those that start with that letter, and if multiple letters are typed with a delay it will filter by that string of letters.
|
||||
std::string set_input_multiple_choice(std::string prompt, std::vector<std::string> choices, std::string default_choice = "");
|
||||
|
||||
// displays a yes/no confirmation prompt, with arrow keys to select yes or no. returns true if yes, false if no.
|
||||
// allows y as shortcut for yes and n as shortcut for no.
|
||||
bool set_input_yes_no(std::string prompt);
|
||||
|
||||
// displays a yes/no/cancel confirmation prompt, with arrow keys to select yes, no, or cancel.
|
||||
// allows y as shortcut for yes, n as shortcut for no, and c as shortcut for cancel.
|
||||
// returns y for yes, n for no, and c for cancel.
|
||||
char set_input_yes_no_cancel(std::string prompt);
|
||||
|
||||
// closes the fullscreen window and restores the original streambufs
|
||||
void close();
|
||||
};
|
||||
|
||||
} // namespace interactive
|
||||
|
||||
#endif
|
13
src/main.cpp
13
src/main.cpp
@ -1,10 +1,9 @@
|
||||
#include "main.hpp"
|
||||
#include "version.hpp"
|
||||
#include "config.hpp"
|
||||
#include "service_runner.hpp"
|
||||
#include "services.hpp"
|
||||
#include "servers.hpp"
|
||||
#include "utils/directories.hpp"
|
||||
#include "config.hpp"
|
||||
#include "templates.hpp"
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
@ -14,6 +13,14 @@
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
void print_version() {
|
||||
std::cout << "dropshell version " << VERSION << std::endl;
|
||||
std::cout << "Release date: " << RELEASE_DATE << std::endl;
|
||||
std::cout << "Author: " << AUTHOR << std::endl;
|
||||
std::cout << "License: " << LICENSE << std::endl;
|
||||
}
|
||||
|
||||
|
||||
void print_help() {
|
||||
std::cout << "Usage: dropshell [OPTIONS] COMMAND [ARGS]" << std::endl;
|
||||
std::cout << std::endl;
|
||||
@ -162,7 +169,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// No arguments provided
|
||||
if (argc < 2) {
|
||||
dropshell::interactive_mode();
|
||||
dropshell::print_help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
25
src/main.hpp
25
src/main.hpp
@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/program_options.hpp>
|
||||
#include "config.hpp"
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
// Version information
|
||||
const std::string VERSION = "1.0.0";
|
||||
const std::string RELEASE_DATE = "2025-04-21";
|
||||
const std::string AUTHOR = "j842";
|
||||
const std::string LICENSE = "MIT";
|
||||
|
||||
|
||||
// Command handlers
|
||||
void print_help(const boost::program_options::options_description& desc);
|
||||
void print_version();
|
||||
void check_status();
|
||||
void list_servers();
|
||||
void show_server_details(const std::string& server_name);
|
||||
void interactive_mode();
|
||||
|
||||
} // namespace dropshell
|
@ -2,7 +2,6 @@
|
||||
#include "server_env.hpp"
|
||||
#include "service_runner.hpp"
|
||||
#include "tableprint.hpp"
|
||||
#include "interactive/interactive.hpp"
|
||||
#include "utils/envmanager.hpp"
|
||||
#include "utils/directories.hpp"
|
||||
#include "services.hpp"
|
||||
@ -161,38 +160,4 @@ void show_server_details(const std::string& server_name) {
|
||||
} // end of show_server_details
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void interactive_mode() {
|
||||
interactive::fullscreen_window iw("DropShell Servers");
|
||||
|
||||
iw.set_input_text_display("Loading all servers' status...");
|
||||
|
||||
auto servers = get_configured_servers();
|
||||
std::vector<std::string> server_names;
|
||||
for (const auto& server : servers) {
|
||||
server_names.push_back(server.name);
|
||||
}
|
||||
|
||||
list_servers();
|
||||
|
||||
while (true) {
|
||||
std::string server_name = iw.set_input_multiple_choice("Select a server", server_names);
|
||||
if (server_name.empty())
|
||||
{
|
||||
iw.close();
|
||||
return;
|
||||
}
|
||||
|
||||
while (!server_name.empty()) {
|
||||
iw.clear_display();
|
||||
show_server_details(server_name);
|
||||
server_name = iw.set_input_multiple_choice("Select a server", server_names, server_name);
|
||||
}
|
||||
iw.clear_display();
|
||||
list_servers();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
@ -15,7 +15,8 @@ struct ServerInfo {
|
||||
};
|
||||
|
||||
std::vector<ServerInfo> get_configured_servers();
|
||||
|
||||
void list_servers();
|
||||
void show_server_details(const std::string& server_name);
|
||||
|
||||
} // namespace dropshell
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "main.hpp"
|
||||
#include "status.hpp"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
|
7
src/status.hpp
Normal file
7
src/status.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#ifndef STATUS_HPP
|
||||
#define STATUS_HPP
|
||||
|
||||
namespace dropshell {
|
||||
void check_status();
|
||||
}
|
||||
#endif
|
@ -1,13 +0,0 @@
|
||||
#include "main.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
void print_version() {
|
||||
std::cout << "dropshell version " << VERSION << std::endl;
|
||||
std::cout << "Release date: " << RELEASE_DATE << std::endl;
|
||||
std::cout << "Author: " << AUTHOR << std::endl;
|
||||
std::cout << "License: " << LICENSE << std::endl;
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
13
src/version.hpp
Normal file
13
src/version.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
// Version information
|
||||
const std::string VERSION = "1.0.0";
|
||||
const std::string RELEASE_DATE = "2025-04-21";
|
||||
const std::string AUTHOR = "j842";
|
||||
const std::string LICENSE = "MIT";
|
||||
|
||||
} // namespace dropshell
|
Loading…
x
Reference in New Issue
Block a user