From 94a2aecff1134ce6af287cdf5759af67912b949a Mon Sep 17 00:00:00 2001
From: Zsombor Szabo-Antalovszky <zsombors@stud.ntnu.no>
Date: Thu, 1 May 2025 13:34:16 +0200
Subject: [PATCH] Releasing version 0.4. See changelog for more info.
 Documentation will be updated shortly

---
 noshx.cpp | 229 ++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 163 insertions(+), 66 deletions(-)

diff --git a/noshx.cpp b/noshx.cpp
index 09ae759..77a58b0 100644
--- a/noshx.cpp
+++ b/noshx.cpp
@@ -30,14 +30,16 @@
  * 
  *  CHANGELOG
  * 
- *  Noémi Shell X ver. 0.3
+ *  Noémi Shell X ver. 0.4
  *
- *  This version introduces the 'snatch' command which can be used quite like 'apt install'. It can download commands via download links.
- *  From this version, the commands 'noemi' and 'noemi -l' are no longer included in this code, but can be installed by running 'snatch noemi'.
- *  This MIGHT change in the future, but for now, it is what it is.
+ *  This version introduces a lot of new features. Firstly, all the commands available on the simple Nosh (for windows) is not available for NoshX too. 
+ *  (Plus many more of course. Nosh is not updated parallel to NoshX). This version introduces something I was planning on for a long time.
+ *  That is autocomplete. To do that, nosh is using the appropriate GNU library. More GNU features are also available, just not tested.
  */
 
 
+// Includes
+
 #include <iostream>
 #include <string>   // Handling strings
 #include <vector> 
@@ -51,18 +53,69 @@
 #include <cmath>
 #include <curl/curl.h>
 #include "snatch.h"
+#include <readline/readline.h> // For autocomplete
+#include <readline/history.h> // For autocomplete
 
-const char VERSION[] = "0.3"; ///< Change version number here for the entire code
+const char VERSION[] = "0.4"; ///< Change version number here for the entire code
 
 #define STRLEN 100 ///< Max string length
 #define WIDTH 10 ///< Width between columns
 
 char vault[STRLEN]; ///< Storing custom info 
 
-
 using namespace std;
 namespace fs = std::filesystem;
 
+// These are the commands available for autocomplete
+// Not all commands are available for autocomplete, to optimize for user experience
+
+vector<string> commands = {
+    "pwd", "about", "exec", "store", "snatch", 
+    "help", "clear", "cls", "list", "tap",
+    "crush", "crush"
+};
+
+char **command_complete(const char *text, int start, int end) {
+    vector<string> matches;
+
+    // If no text has been typed yet, return NULL to prevent autocomplete
+    if (start == 0 && text[0] == '\0') {
+        return nullptr;
+    }
+
+    // Trim the input text to remove trailing spaces/tabs
+    string trimmed_text = string(text);
+
+    // Handle built-in commands like "exit"
+    vector<string> builtins = {"exit", "help", "cd", "ls"};  // Add all your built-ins here
+
+    for (const auto &command : builtins) {
+        if (command.find(trimmed_text) == 0) {
+            matches.push_back(command);
+        }
+    }
+
+    // Find matches for the autocomplete based on the trimmed text
+    for (const auto &command : commands) {
+        if (command.find(trimmed_text) == 0) {
+            matches.push_back(command);
+        }
+    }
+
+    // If no matches, return nullptr
+    if (matches.empty()) return nullptr;
+
+    // Allocate memory for the results
+    char **result = (char **)malloc(sizeof(char *) * (matches.size() + 1));
+    for (size_t i = 0; i < matches.size(); ++i) {
+        result[i] = strdup(matches[i].c_str());
+    }
+    result[matches.size()] = nullptr;
+
+    return result;
+}
+
+
 // Declaring functions
 void handle_pwd(const vector<string>& args);
 bool is_builtin_command(const vector<string>& args);
@@ -70,73 +123,93 @@ void handle_storage(const vector<string>& args);
 void handle_now(const vector<string>& args);
 void handle_cd(const vector<string>& args);
 void handle_snatch(const vector<string>& args);
+void handle_list(const vector<string>& args);
+void handle_tap(const vector<string>& args);
+void handle_mkdir(const vector<string>& args);
+void handle_crush(const vector<string>& args);
+void handle_help(const vector<string>& args);
+void handle_read(const vector<string>& args);
+void handle_exec(const vector<string>& args);
+void handle_grasp(const vector<string>& args);
+void handle_rn(const vector<string>& args);
 
 // To be implemented commands:
 
-//void handle_list(const vector<string>& args);
-//void handle_read(const vector<string>& args, string filename);
-//void handle_grasp(const vector<string>& args);
-//void handle_tap(const vector<string>& args);
-//void handle_crush(const vector<string>& args);
-//void handle_mkdir(const vector<string>& args);
-//void handle_rn(const vector<string>& args);
-//void handle_exec(const vector<string>& args);
-//void handle_help(const vector<string>& args);
+
+
+
 
 
 /**
  *  The main function
  */
 int main() {
-    
-    cout << "Noémi Shell X (noshx) ver. " << VERSION << "\n"
+    cout << "Noémi Shell X (noshx) ver. 1.0\n"
          << "Copyright (C) 2025 Zsombor Szabo-Antalovszky\n\n"
          << "Type 'help' or '?' for list of available commands\n\n";
 
+    // Set the completion function
+    rl_attempted_completion_function = command_complete;
 
     string command; // Storing command
 
     while (true) {
-        
+        // Get current working directory
         char buffer[PATH_MAX];
         string cwd = getcwd(buffer, sizeof(buffer));
 
+        // Check if the path is under the home directory
         const char* home = getenv("HOME");
         if (home != nullptr) {
             string home_str = home;
             if (cwd.find(home_str) == 0) { // cwd starts with home
                 cwd.replace(0, home_str.length(), "~");
             }
-}
-        
-        cout << "\033[1;33mnoshx: \033[0m"   // Yellow "noshx:"
-        << "\033[1;34m" << cwd << "\033[0m"  // Bold magenta cwd
-        << "$\033[0m ";  // Normal >
-   
-        getline(cin, command);
-
-        // On 'exit' command
-        if (command == "exit"){
+        }
+
+        // Set the prompt with color formatting
+        string prompt = "\033[1;33mnoshx: \033[0m"   // Yellow "noshx:"
+                        "\033[1;34m" + cwd + "\033[0m"  // Bold magenta cwd
+                        "$\033[0m ";  // Normal >
+
+        // Use readline for input with autocompletion
+        char *input = readline(prompt.c_str());  // Convert string to const char* using c_str()
+
+        // If input is NULL, exit
+        if (input == nullptr) {
+            break;
+        }
+
+        // Add the input to history
+        add_history(input);
+
+        // Convert to string for easier handling
+        command = string(input);
+        free(input);  // Free the input buffer after use
+
+        // On 'exit' command, break the loop
+        if (command == "exit") {
             cout << "Exiting...\n";
             break;
         }
 
+        // Split command into arguments
         vector<string> args;
         string temp;
         bool in_quotes = false;
 
         for (size_t i = 0; i < command.size(); ++i) {
             char ch = command[i];
-            
+
             if (ch == '"') { // Toggle in_quotes state on encountering a quote
                 in_quotes = !in_quotes;
-            } 
+            }
             else if (ch == ' ' && !in_quotes) { // Treat space as separator only if not in quotes
                 if (!temp.empty()) {
                     args.push_back(temp);
                     temp.clear();
                 }
-            } 
+            }
             else {
                 temp += ch;
             }
@@ -162,7 +235,6 @@ int main() {
         }
     }
 
-
     return 0; // Terminate program with success
 }
 
@@ -202,9 +274,9 @@ void handle_help(const vector<string>& args){
          << "about --version: Displays shell version only\n"
          << "exec: Execute an executable file\n"
          << "exit: Exit the shell\n"
-         //<< "grasp -d: Search through directories for other directories\n"
-         //<< "grasp -f: Search through directories for files\n"
-         //<< "list: List the files or other directories whithin a directory\n"
+         << "grasp -d: Search through directories for other directories\n"
+         << "grasp -f: Search through directories for files\n"
+         << "list: List the files or other directories whithin a directory\n"
          << "mkdir: Create a new directory whithin current directory\n"
          << "noemi: Draw a heart shape made of # characters\n"
          << "noemi -l: Draw a heart shape made of # characters with more delay\n"
@@ -212,7 +284,8 @@ void handle_help(const vector<string>& args){
          //<< "rn: rename a file or a directory\n"
          << "snatch: Install additional available commands for NoshX\n"
          << "store: Save a string of maximum 100 characters during runtime\n"
-         << "store -g: Show the string saved with store\n\n";
+         << "store -g: Show the string saved with store\n"
+         << "tap: Quickly create a file of any type\n\n";
 }
 
 /**
@@ -220,17 +293,27 @@ void handle_help(const vector<string>& args){
  * 
  *  @param args for arguments
  */
-void handle_exec(const vector<string>& args){
-    if (args.size() != 2){
+void handle_exec(const vector<string>& args) {
+    if (args.size() != 2) {
         cout << "Usage: exec <file_to_execute>\n";
-        return; // Exit early
+        return;
     }
 
     string path = args[1];
 
-    system(path.c_str()); // Runs the executable
+    // If no '/' in the path, assume current directory
+    if (path.find('/') == string::npos) {
+        path = "./" + path;
+    }
+
+    int result = system(path.c_str());
+
+    if (result == -1) {
+        cout << "Failed to execute: " << path << endl;
+    }
 }
 
+
 /**
  *  Gets and prints out the current date
  * 
@@ -389,12 +472,12 @@ bool is_builtin_command(const vector<string>& args){
     }
 
     if (args[0] == "exec"){
-        //handle_exec(args);
+        handle_exec(args);
         return true;
     }
 
     if (args[0] == "list") {
-        //handle_list(args);
+        handle_list(args);
         return true;
     }
 
@@ -404,32 +487,32 @@ bool is_builtin_command(const vector<string>& args){
     }
 
     if (args[0] == "read") {
-        //handle_read(args, args[1]);
+        handle_read(args);
         return true;
     }
 
     if (args[0] == "grasp"){
-        //handle_grasp(args);
+        handle_grasp(args);
         return true;
     }
 
     if (args[0] == "tap"){
-        //handle_tap(args);
+        handle_tap(args);
         return true;
     }
 
     if (args[0] == "crush"){
-        //handle_crush(args);
+        handle_crush(args);
         return true;
     }
 
     if (args[0] == "mkdir"){
-        //handle_mkdir(args);
+        handle_mkdir(args);
         return true;
     }
 
     if (args[0] == "rn"){
-        //handle_rn(args);
+        handle_rn(args);
         return true;
     }
 
@@ -447,7 +530,7 @@ bool is_builtin_command(const vector<string>& args){
  *  @param args for arguments.
  */
 
-/*
+
 void handle_list(const vector<string>& args){
     fs::path target_path = fs::current_path();
 
@@ -472,7 +555,7 @@ void handle_list(const vector<string>& args){
         // If there is an error reading directory
         cerr << "list: Error reading directory: " << e.what() << endl;
     }
-}*/
+}
 
 /**
  *  Handling the 'cd' command for changing directories
@@ -532,14 +615,16 @@ void handle_list(const vector<string>& args){
  *  @param args for arguments.
  */
 
-/*
-void handle_read(const vector<string>& args, string filename) {
+
+void handle_read(const vector<string>& args) {
     // If there is no argument after command
     if (args.size() != 2) {
         cout << "Usage: read <filename>\n";
         return; // Exit early
     }
 
+    string filename = args[1];
+
     // Open the file for reading
     ifstream file(filename);
     if (!file.is_open()) {
@@ -561,7 +646,8 @@ void handle_read(const vector<string>& args, string filename) {
 
     file.close(); // CLosing the file 
 }
-*/
+
+
 /**
  *  Handling the 'store' command for storing a string of maximum 100 characters during runtime.
  * 
@@ -589,13 +675,23 @@ void handle_storage(const vector<string>& args) {
     }
 }
 
+string expand_path(const string& path) {
+    if (!path.empty() && path[0] == '~') {
+        const char* home = getenv("HOME");
+        if (home != nullptr) {
+            return string(home) + path.substr(1);
+        }
+    }
+    return path;
+}
+
+
 /**
  *  Handling the 'grasp' command for filtering results
  * 
  *  @param args for arguments.
  */
 
-/*
 void handle_grasp(const vector<string>& args){
     if (args.size() != 4){
         cout << "Usage: grasp <type> <directory> <pattern>\n";
@@ -603,7 +699,7 @@ void handle_grasp(const vector<string>& args){
     }
 
     const string type = args[1];
-    const string dirPath = args[2];
+    const string dirPath = expand_path(args[2]);
     const string pattern = args[3];
 
     // IF the type argument is invalid
@@ -660,7 +756,7 @@ void handle_grasp(const vector<string>& args){
 
     }    
 }
-*/
+
 
 /**
  *  Handling the 'tap' command for creating files.
@@ -668,7 +764,7 @@ void handle_grasp(const vector<string>& args){
  *  @param args for arguments.
  */
 
-/*
+
 void handle_tap(const vector<string>& args){
     if (args.size() != 2){
         cout << "Usage: tap <filename>\n";
@@ -686,14 +782,13 @@ void handle_tap(const vector<string>& args){
 
     file.close();
 }
-*/
 
 /**
  *  Handling the 'crush' command for removing files and directories.
  * 
  *  @param args for arguments.
  */
-/*
+
 void handle_crush(const vector<string>& args) {
     
     if (args.size() != 3){
@@ -745,7 +840,6 @@ void handle_crush(const vector<string>& args) {
     }   
 
 }
-*/
 
 /**
  *  Handling the 'mkdir' command for creating directories.
@@ -753,7 +847,7 @@ void handle_crush(const vector<string>& args) {
  *  @param args for arguments.
  */
 
-/*
+
 void handle_mkdir(const vector<string>& args) {
     if (args.size() != 2){
         cout << "Usage: mkdir <directory_name>\n";
@@ -762,25 +856,28 @@ void handle_mkdir(const vector<string>& args) {
 
     const string dirName = args[1];
 
-    if (fs::exists(dirName)) {
+    if (fs::is_directory(dirName)) {
         cout << "Directory already exists: " << dirName << endl;
         return;
     }
 
+    if (fs::exists(dirName)){
+        cout << "File already exists: " << dirName << endl;
+        return;
+    }
+
     // Create the directory
     if (!fs::create_directory(dirName)) {
         cout << "Failed to create directory: " << dirName << endl;
     }
 }
-*/
+
 
 /**
  *  Handling the 'rn' command for renaming files and directories.
  * 
  *  @param args for arguments.
  */
-
-/*
 void handle_rn(const vector<string>& args){
     if (args.size() != 3){
         cout << "Usage: rn <filename> <rename>\n";
@@ -798,4 +895,4 @@ void handle_rn(const vector<string>& args){
     {
         cerr << "Error: " << e.what() << '\n';
     }
-}   */
\ No newline at end of file
+}
\ No newline at end of file
-- 
GitLab