mirror of
https://github.com/EnderIce2/Fennix.git
synced 2025-05-28 15:34:31 +00:00
feat(userspace/coreutils): improve fennix shell implementation
Signed-off-by: EnderIce2 <enderice2@protonmail.com>
This commit is contained in:
parent
76b3d30db9
commit
c4225f7bdf
@ -57,3 +57,11 @@ if(UNIX AND TARGET test)
|
|||||||
\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/[\"
|
\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/[\"
|
||||||
)")
|
)")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(UNIX AND TARGET sh)
|
||||||
|
install(CODE "execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||||
|
sh
|
||||||
|
\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/fsh\"
|
||||||
|
)")
|
||||||
|
endif()
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
along with Fennix C Library. If not, see <https://www.gnu.org/licenses/>.
|
along with Fennix C Library. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -27,16 +28,22 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <coreutils.h>
|
||||||
|
|
||||||
#define MAX_LINE_LEN 1024
|
#define MAX_LINE_LEN 1024
|
||||||
#define MAX_HISTORY 128
|
#define MAX_HISTORY 128
|
||||||
#define CTRL_KEY(k) ((k) & 0x1f)
|
#define CTRL_KEY(k) ((k) & 0x1F)
|
||||||
|
#define MAX_COMPLETIONS 128
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
char *items[MAX_HISTORY];
|
char *items[MAX_HISTORY];
|
||||||
int count;
|
int count;
|
||||||
int index;
|
int Index;
|
||||||
} History;
|
} History;
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
@ -49,43 +56,85 @@ typedef struct
|
|||||||
{
|
{
|
||||||
char line[MAX_LINE_LEN];
|
char line[MAX_LINE_LEN];
|
||||||
int cursor;
|
int cursor;
|
||||||
int len;
|
int length;
|
||||||
InputMode mode;
|
InputMode mode;
|
||||||
History history;
|
History history;
|
||||||
struct termios orig_termios;
|
struct termios origTermios;
|
||||||
|
char prompt[PATH_MAX + 128];
|
||||||
|
int promptLength;
|
||||||
} ShellState;
|
} ShellState;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char *items[MAX_COMPLETIONS];
|
||||||
|
int count;
|
||||||
|
} CompletionList;
|
||||||
|
|
||||||
|
static ShellState GlobalShellState;
|
||||||
|
static int progIsFsh = 0;
|
||||||
|
|
||||||
|
void DisableRawMode(ShellState *state);
|
||||||
|
void SaveHistory(ShellState *state);
|
||||||
|
void InitializeShell(ShellState *state);
|
||||||
|
void FreeCompletionList(CompletionList *list);
|
||||||
|
CompletionList GetCompletions(const char *partial);
|
||||||
|
void DisplayCompletions(ShellState *state, CompletionList *list);
|
||||||
|
void UpdatePrompt(ShellState *state);
|
||||||
|
int GetVisibleLength(const char *str);
|
||||||
|
|
||||||
|
void CleanupAndExit(int code)
|
||||||
|
{
|
||||||
|
SaveHistory(&GlobalShellState);
|
||||||
|
DisableRawMode(&GlobalShellState);
|
||||||
|
exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
void HandleSignalInterrupt(int sig)
|
void HandleSignalInterrupt(int sig)
|
||||||
{
|
{
|
||||||
(void)sig;
|
(void)sig;
|
||||||
write(STDOUT_FILENO, "\n", 1);
|
write(STDOUT_FILENO, "\n\r", 2);
|
||||||
|
CleanupAndExit(130);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnableRawMode(ShellState *state)
|
void EnableRawMode(ShellState *state)
|
||||||
{
|
{
|
||||||
tcgetattr(STDIN_FILENO, &state->orig_termios);
|
tcgetattr(STDIN_FILENO, &state->origTermios);
|
||||||
struct termios raw = state->orig_termios;
|
struct termios raw = state->origTermios;
|
||||||
raw.c_lflag &= ~(ECHO | ICANON);
|
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||||
|
raw.c_oflag &= ~(OPOST);
|
||||||
|
raw.c_cflag |= (CS8);
|
||||||
|
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||||
|
raw.c_cc[VMIN] = 1;
|
||||||
|
raw.c_cc[VTIME] = 0;
|
||||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisableRawMode(ShellState *state)
|
void DisableRawMode(ShellState *state)
|
||||||
{
|
{
|
||||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &state->orig_termios);
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &state->origTermios);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddHistory(ShellState *state, const char *line)
|
void AddHistory(ShellState *state, const char *line)
|
||||||
{
|
{
|
||||||
|
if (!line || strlen(line) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char *newEntry = strdup(line);
|
||||||
|
if (!newEntry)
|
||||||
|
return;
|
||||||
|
|
||||||
if (state->history.count < MAX_HISTORY)
|
if (state->history.count < MAX_HISTORY)
|
||||||
state->history.items[state->history.count++] = strdup(line);
|
state->history.items[state->history.count++] = newEntry;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
free(state->history.items[0]);
|
free(state->history.items[0]);
|
||||||
memmove(state->history.items, state->history.items + 1,
|
memmove(state->history.items, state->history.items + 1,
|
||||||
(MAX_HISTORY - 1) * sizeof(char *));
|
(MAX_HISTORY - 1) * sizeof(char *));
|
||||||
state->history.items[MAX_HISTORY - 1] = strdup(line);
|
state->history.items[MAX_HISTORY - 1] = newEntry;
|
||||||
}
|
}
|
||||||
state->history.index = state->history.count;
|
state->history.Index = state->history.count;
|
||||||
|
|
||||||
|
SaveHistory(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadHistory(ShellState *state)
|
void LoadHistory(ShellState *state)
|
||||||
@ -94,20 +143,42 @@ void LoadHistory(ShellState *state)
|
|||||||
if (!home)
|
if (!home)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char path[256];
|
char path[PATH_MAX];
|
||||||
snprintf(path, sizeof(path), "%s/.sh_history", home);
|
snprintf(path, sizeof(path), "%s/.fsh_history", home);
|
||||||
|
|
||||||
FILE *fp = fopen(path, "r");
|
FILE *fp = fopen(path, "r");
|
||||||
if (!fp)
|
if (!fp)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
char **tmpHistory = malloc(MAX_HISTORY * sizeof(char *));
|
||||||
|
int tmpCount = 0;
|
||||||
|
|
||||||
char line[MAX_LINE_LEN];
|
char line[MAX_LINE_LEN];
|
||||||
while (fgets(line, sizeof(line), fp))
|
while (fgets(line, sizeof(line), fp))
|
||||||
{
|
{
|
||||||
line[strcspn(line, "\n")] = '\0';
|
size_t len = strlen(line);
|
||||||
AddHistory(state, line);
|
if (len > 0 && line[len - 1] == '\n')
|
||||||
|
line[len - 1] = '\0';
|
||||||
|
|
||||||
|
if (strlen(line) > 0)
|
||||||
|
{
|
||||||
|
if (tmpCount < MAX_HISTORY)
|
||||||
|
tmpHistory[tmpCount++] = strdup(line);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
free(tmpHistory[0]);
|
||||||
|
memmove(tmpHistory, tmpHistory + 1, (MAX_HISTORY - 1) * sizeof(char *));
|
||||||
|
tmpHistory[MAX_HISTORY - 1] = strdup(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
||||||
|
for (int i = 0; i < tmpCount; i++)
|
||||||
|
state->history.items[i] = tmpHistory[i];
|
||||||
|
state->history.count = tmpCount;
|
||||||
|
state->history.Index = tmpCount;
|
||||||
|
free(tmpHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveHistory(ShellState *state)
|
void SaveHistory(ShellState *state)
|
||||||
@ -116,18 +187,39 @@ void SaveHistory(ShellState *state)
|
|||||||
if (!home)
|
if (!home)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char path[256];
|
char path[PATH_MAX];
|
||||||
snprintf(path, sizeof(path), "%s/.sh_history", home);
|
snprintf(path, sizeof(path), "%s/.fsh_history", home);
|
||||||
|
|
||||||
FILE *fp = fopen(path, "w");
|
int fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0600);
|
||||||
if (!fp)
|
if (fd < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = 0; i < state->history.count; i++)
|
struct flock fl = {
|
||||||
|
.l_type = F_WRLCK,
|
||||||
|
.l_whence = SEEK_SET,
|
||||||
|
.l_start = 0,
|
||||||
|
.l_len = 0};
|
||||||
|
|
||||||
|
if (fcntl(fd, F_SETLKW, &fl) == -1)
|
||||||
{
|
{
|
||||||
fprintf(fp, "%s\n", state->history.items[i]);
|
close(fd);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
fclose(fp);
|
|
||||||
|
if (state->history.count > 0)
|
||||||
|
{
|
||||||
|
int last = state->history.count - 1;
|
||||||
|
if (state->history.items[last])
|
||||||
|
{
|
||||||
|
char *cmd = state->history.items[last];
|
||||||
|
write(fd, cmd, strlen(cmd));
|
||||||
|
write(fd, "\n", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fl.l_type = F_UNLCK;
|
||||||
|
fcntl(fd, F_SETLK, &fl);
|
||||||
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProcessViCommand(ShellState *state, char c)
|
void ProcessViCommand(ShellState *state, char c)
|
||||||
@ -139,31 +231,27 @@ void ProcessViCommand(ShellState *state, char c)
|
|||||||
state->cursor--;
|
state->cursor--;
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
if (state->cursor < state->len)
|
if (state->cursor < state->length)
|
||||||
state->cursor++;
|
state->cursor++;
|
||||||
break;
|
break;
|
||||||
case 'k':
|
case 'k':
|
||||||
{
|
if (state->history.Index > 0)
|
||||||
if (state->history.index > 0)
|
|
||||||
{
|
{
|
||||||
state->history.index--;
|
state->history.Index--;
|
||||||
strcpy(state->line, state->history.items[state->history.index]);
|
strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN);
|
||||||
state->len = strlen(state->line);
|
state->length = strlen(state->line);
|
||||||
state->cursor = state->len;
|
state->cursor = state->length;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case 'j':
|
case 'j':
|
||||||
{
|
if (state->history.Index < state->history.count - 1)
|
||||||
if (state->history.index < state->history.count - 1)
|
|
||||||
{
|
{
|
||||||
state->history.index++;
|
state->history.Index++;
|
||||||
strcpy(state->line, state->history.items[state->history.index]);
|
strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN);
|
||||||
state->len = strlen(state->line);
|
state->length = strlen(state->line);
|
||||||
state->cursor = state->len;
|
state->cursor = state->length;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case 'i':
|
case 'i':
|
||||||
state->mode = MODE_INSERT;
|
state->mode = MODE_INSERT;
|
||||||
break;
|
break;
|
||||||
@ -173,94 +261,471 @@ void ProcessViCommand(ShellState *state, char c)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FreeCompletionList(CompletionList *list)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < list->count; i++)
|
||||||
|
free(list->items[i]);
|
||||||
|
list->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletionList GetCompletions(const char *partial)
|
||||||
|
{
|
||||||
|
CompletionList list = {0};
|
||||||
|
char *path = getenv("PATH");
|
||||||
|
if (!path)
|
||||||
|
return list;
|
||||||
|
|
||||||
|
char *pathCopy = strdup(path);
|
||||||
|
char *dir = strtok(pathCopy, ":");
|
||||||
|
|
||||||
|
while (dir && list.count < MAX_COMPLETIONS)
|
||||||
|
{
|
||||||
|
DIR *d = opendir(dir);
|
||||||
|
if (!d)
|
||||||
|
{
|
||||||
|
dir = strtok(NULL, ":");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
while ((entry = readdir(d)) && list.count < MAX_COMPLETIONS)
|
||||||
|
{
|
||||||
|
if (strncmp(entry->d_name, partial, strlen(partial)) == 0)
|
||||||
|
{
|
||||||
|
char fullPath[PATH_MAX];
|
||||||
|
snprintf(fullPath, sizeof(fullPath), "%s/%s", dir, entry->d_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(fullPath, &st) == 0 && (st.st_mode & S_IXUSR))
|
||||||
|
list.items[list.count++] = strdup(entry->d_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
dir = strtok(NULL, ":");
|
||||||
|
}
|
||||||
|
|
||||||
|
free(pathCopy);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayCompletions(ShellState *state, CompletionList *list)
|
||||||
|
{
|
||||||
|
if (list->count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
write(STDOUT_FILENO, "\n\r", 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < list->count; i++)
|
||||||
|
{
|
||||||
|
write(STDOUT_FILENO, list->items[i], strlen(list->items[i]));
|
||||||
|
write(STDOUT_FILENO, " ", 2);
|
||||||
|
}
|
||||||
|
write(STDOUT_FILENO, "\n\r", 2);
|
||||||
|
|
||||||
|
write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
|
||||||
|
write(STDOUT_FILENO, state->line, state->length);
|
||||||
|
}
|
||||||
|
|
||||||
void ReadLine(ShellState *state)
|
void ReadLine(ShellState *state)
|
||||||
{
|
{
|
||||||
state->len = 0;
|
state->length = 0;
|
||||||
state->cursor = 0;
|
state->cursor = 0;
|
||||||
state->line[0] = '\0';
|
state->line[0] = '\0';
|
||||||
state->mode = MODE_INSERT;
|
state->mode = MODE_INSERT;
|
||||||
|
|
||||||
printf("$ ");
|
UpdatePrompt(state);
|
||||||
fflush(stdout);
|
|
||||||
|
write(STDOUT_FILENO, "\r", 1);
|
||||||
|
write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
|
||||||
|
|
||||||
EnableRawMode(state);
|
EnableRawMode(state);
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
char c = '\0';
|
char c;
|
||||||
if (read(STDIN_FILENO, &c, 1) != 1)
|
ssize_t nread = read(STDIN_FILENO, &c, 1);
|
||||||
break;
|
if (nread <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (state->mode == MODE_COMMAND)
|
if (state->mode == MODE_COMMAND)
|
||||||
ProcessViCommand(state, c);
|
ProcessViCommand(state, c);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (c == CTRL_KEY('c'))
|
if (c == '\t')
|
||||||
{
|
{
|
||||||
printf("\n");
|
|
||||||
|
char *wordStart = state->line;
|
||||||
|
for (int i = state->cursor - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (state->line[i] == ' ')
|
||||||
|
{
|
||||||
|
wordStart = &state->line[i + 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char partial[MAX_LINE_LEN];
|
||||||
|
int len = state->cursor - (wordStart - state->line);
|
||||||
|
strncpy(partial, wordStart, len);
|
||||||
|
partial[len] = '\0';
|
||||||
|
|
||||||
|
CompletionList completions = GetCompletions(partial);
|
||||||
|
|
||||||
|
if (completions.count == 1)
|
||||||
|
{
|
||||||
|
|
||||||
|
int restLength = strlen(completions.items[0]) - len;
|
||||||
|
if (restLength > 0)
|
||||||
|
{
|
||||||
|
memmove(&state->line[state->cursor + restLength],
|
||||||
|
&state->line[state->cursor],
|
||||||
|
state->length - state->cursor + 1);
|
||||||
|
memcpy(&state->line[state->cursor],
|
||||||
|
&completions.items[0][len],
|
||||||
|
restLength);
|
||||||
|
state->cursor += restLength;
|
||||||
|
state->length += restLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (completions.count > 1)
|
||||||
|
{
|
||||||
|
|
||||||
|
int prefixLength = len;
|
||||||
|
int canExtend = 1;
|
||||||
|
|
||||||
|
while (canExtend)
|
||||||
|
{
|
||||||
|
char nextChar = completions.items[0][prefixLength];
|
||||||
|
if (nextChar == '\0')
|
||||||
|
break;
|
||||||
|
|
||||||
|
for (int i = 1; i < completions.count; i++)
|
||||||
|
{
|
||||||
|
if (completions.items[i][prefixLength] != nextChar)
|
||||||
|
{
|
||||||
|
canExtend = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canExtend)
|
||||||
|
prefixLength++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefixLength > len)
|
||||||
|
{
|
||||||
|
|
||||||
|
int restLength = prefixLength - len;
|
||||||
|
memmove(&state->line[state->cursor + restLength],
|
||||||
|
&state->line[state->cursor],
|
||||||
|
state->length - state->cursor + 1);
|
||||||
|
memcpy(&state->line[state->cursor],
|
||||||
|
&completions.items[0][len],
|
||||||
|
restLength);
|
||||||
|
state->cursor += restLength;
|
||||||
|
state->length += restLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayCompletions(state, &completions);
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeCompletionList(&completions);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (c == 0x1B)
|
||||||
|
{
|
||||||
|
char seq[3];
|
||||||
|
if (read(STDIN_FILENO, &seq[0], 1) != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (read(STDIN_FILENO, &seq[1], 1) != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (seq[0] == '[')
|
||||||
|
{
|
||||||
|
switch (seq[1])
|
||||||
|
{
|
||||||
|
case 'A':
|
||||||
|
if (state->history.Index > 0)
|
||||||
|
{
|
||||||
|
state->history.Index--;
|
||||||
|
strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN - 1);
|
||||||
|
state->length = strlen(state->line);
|
||||||
|
state->cursor = state->length;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'B':
|
||||||
|
if (state->history.Index < state->history.count)
|
||||||
|
{
|
||||||
|
state->history.Index++;
|
||||||
|
if (state->history.Index == state->history.count)
|
||||||
|
{
|
||||||
|
state->line[0] = '\0';
|
||||||
|
state->length = 0;
|
||||||
|
state->cursor = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN - 1);
|
||||||
|
state->length = strlen(state->line);
|
||||||
|
state->cursor = state->length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
if (state->cursor < state->length)
|
||||||
|
state->cursor++;
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
if (state->cursor > 0)
|
||||||
|
state->cursor--;
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
state->cursor = 0;
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
state->cursor = state->length;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
{
|
||||||
|
if (read(STDIN_FILENO, &seq[2], 1) != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (seq[2] == '~' && state->cursor < state->length)
|
||||||
|
{
|
||||||
|
memmove(&state->line[state->cursor], &state->line[state->cursor + 1],
|
||||||
|
state->length - state->cursor);
|
||||||
|
state->length--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (seq[0] == 'O')
|
||||||
|
{
|
||||||
|
switch (seq[1])
|
||||||
|
{
|
||||||
|
case 'H':
|
||||||
|
state->cursor = 0;
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
state->cursor = state->length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c == CTRL_KEY('c'))
|
||||||
|
{
|
||||||
|
write(STDOUT_FILENO, "\n\r", 2);
|
||||||
|
write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
|
||||||
|
state->length = 0;
|
||||||
|
state->cursor = 0;
|
||||||
|
state->line[0] = '\0';
|
||||||
|
}
|
||||||
|
else if (c == '\r' || c == '\n')
|
||||||
|
{
|
||||||
|
write(STDOUT_FILENO, "\n", 1);
|
||||||
|
state->line[state->length] = '\0';
|
||||||
|
DisableRawMode(state);
|
||||||
|
if (state->length > 0)
|
||||||
|
{
|
||||||
|
AddHistory(state, state->line);
|
||||||
|
state->history.Index = state->history.count;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (c == '\n')
|
else if (c == 0x7F)
|
||||||
{
|
|
||||||
printf("\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (c == 127)
|
|
||||||
{
|
{
|
||||||
if (state->cursor > 0)
|
if (state->cursor > 0)
|
||||||
{
|
{
|
||||||
memmove(&state->line[state->cursor - 1],
|
memmove(&state->line[state->cursor - 1], &state->line[state->cursor],
|
||||||
&state->line[state->cursor],
|
state->length - state->cursor + 1);
|
||||||
state->len - state->cursor + 1);
|
|
||||||
state->cursor--;
|
state->cursor--;
|
||||||
state->len--;
|
state->length--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (state->len < MAX_LINE_LEN - 1)
|
else if (c == CTRL_KEY('d') && state->length == 0)
|
||||||
{
|
{
|
||||||
memmove(&state->line[state->cursor + 1],
|
write(STDOUT_FILENO, "\n", 1);
|
||||||
&state->line[state->cursor],
|
DisableRawMode(state);
|
||||||
state->len - state->cursor + 1);
|
exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
else if (isprint(c) && state->length < MAX_LINE_LEN - 1)
|
||||||
|
{
|
||||||
|
memmove(&state->line[state->cursor + 1], &state->line[state->cursor],
|
||||||
|
state->length - state->cursor + 1);
|
||||||
state->line[state->cursor] = c;
|
state->line[state->cursor] = c;
|
||||||
state->cursor++;
|
state->cursor++;
|
||||||
state->len++;
|
state->length++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\r\x1B[2K$ %s", state->line);
|
write(STDOUT_FILENO, "\r", 1);
|
||||||
fflush(stdout);
|
write(STDOUT_FILENO, "\x1b[K", 3);
|
||||||
printf("\r\x1B[%dC", state->cursor + 3);
|
write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
|
||||||
|
write(STDOUT_FILENO, state->line, state->length);
|
||||||
|
|
||||||
|
int promptVisibleLength = GetVisibleLength(state->prompt);
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "\r\x1b[%dC", promptVisibleLength + state->cursor);
|
||||||
|
write(STDOUT_FILENO, buf, strlen(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetVisibleLength(const char *str)
|
||||||
|
{
|
||||||
|
int len = 0;
|
||||||
|
int inEscape = 0;
|
||||||
|
|
||||||
|
while (*str)
|
||||||
|
{
|
||||||
|
if (*str == '\x1b')
|
||||||
|
inEscape = 1;
|
||||||
|
else if (inEscape)
|
||||||
|
{
|
||||||
|
if ((*str >= 'A' && *str <= 'Z') || (*str >= 'a' && *str <= 'z'))
|
||||||
|
inEscape = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
len++;
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdatePrompt(ShellState *state)
|
||||||
|
{
|
||||||
|
char hostname[256] = {0};
|
||||||
|
gethostname(hostname, sizeof(hostname));
|
||||||
|
|
||||||
|
struct passwd *pw = getpwuid(getuid());
|
||||||
|
char *username = pw ? pw->pw_name : "user";
|
||||||
|
|
||||||
|
char cwd[PATH_MAX];
|
||||||
|
if (!getcwd(cwd, sizeof(cwd)))
|
||||||
|
strcpy(cwd, "~");
|
||||||
|
|
||||||
|
if (pw && pw->pw_dir && strncmp(cwd, pw->pw_dir, strlen(pw->pw_dir)) == 0)
|
||||||
|
{
|
||||||
|
size_t home_len = strlen(pw->pw_dir);
|
||||||
|
if (strlen(cwd) == home_len)
|
||||||
|
strcpy(cwd, "~");
|
||||||
|
else if (cwd[home_len] == '/')
|
||||||
|
{
|
||||||
|
memmove(cwd + 1, cwd + home_len, strlen(cwd) - home_len + 1);
|
||||||
|
cwd[0] = '~';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DisableRawMode(state);
|
char *customPrompt = getenv("SHELL_PROMPT");
|
||||||
AddHistory(state, state->line);
|
if (customPrompt)
|
||||||
|
{
|
||||||
|
snprintf(state->prompt, sizeof(state->prompt), "%s", customPrompt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// snprintf(state->prompt, sizeof(state->prompt),
|
||||||
|
// "\x1b[;32m┌──(%s@%s)-[\x1b[0;1m%s\x1b[;32m]\n\r└─\x1b[;32m$\x1b[00m ",
|
||||||
|
// username, hostname, cwd);
|
||||||
|
|
||||||
|
// snprintf(state->prompt, sizeof(state->prompt),
|
||||||
|
// "\x1b[;32m%s@%s\x1b[0;1m:\x1b[01;34m%s\x1b[0;1m$\x1b[00m ",
|
||||||
|
// username, hostname, cwd);
|
||||||
|
|
||||||
|
if (progIsFsh)
|
||||||
|
{
|
||||||
|
snprintf(state->prompt, sizeof(state->prompt),
|
||||||
|
"\x1b[1;34m%s\x1b[0;1m:\x1b[01;35m%s\x1b[0;1m$\x1b[00m ",
|
||||||
|
username, cwd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf(state->prompt, sizeof(state->prompt),
|
||||||
|
"$ ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state->promptLength = GetVisibleLength(state->prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExecuteCommand(char **args)
|
void ExecuteCommand(char **args)
|
||||||
{
|
{
|
||||||
if (args[0] == NULL)
|
if (!args[0])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; args[i] != NULL; i++)
|
||||||
|
{
|
||||||
|
char *arg = args[i];
|
||||||
|
size_t len = strlen(arg);
|
||||||
|
if (len >= 2 && arg[0] == '"' && arg[len - 1] == '"')
|
||||||
|
{
|
||||||
|
arg[len - 1] = '\0';
|
||||||
|
memmove(arg, arg + 1, len - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(args[0], "exit") == 0)
|
if (strcmp(args[0], "exit") == 0)
|
||||||
exit(0);
|
exit(0);
|
||||||
if (strcmp(args[0], "cd") == 0)
|
else if (strcmp(args[0], "cd") == 0)
|
||||||
{
|
{
|
||||||
if (args[1] == NULL)
|
char *targetDirectory = args[1];
|
||||||
fprintf(stderr, "cd: missing argument\n");
|
if (!targetDirectory || strcmp(targetDirectory, "~") == 0)
|
||||||
else if (chdir(args[1]))
|
{
|
||||||
|
struct passwd *pw = getpwuid(getuid());
|
||||||
|
if (pw && pw->pw_dir)
|
||||||
|
targetDirectory = pw->pw_dir;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "cd: HOME not set and no password directory available\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (targetDirectory[0] == '~')
|
||||||
|
{
|
||||||
|
struct passwd *pw = getpwuid(getuid());
|
||||||
|
if (pw && pw->pw_dir)
|
||||||
|
{
|
||||||
|
char newPath[PATH_MAX];
|
||||||
|
snprintf(newPath, sizeof(newPath), "%s%s", pw->pw_dir, targetDirectory + 1);
|
||||||
|
targetDirectory = newPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chdir(targetDirectory))
|
||||||
perror("cd");
|
perror("cd");
|
||||||
|
else
|
||||||
|
UpdatePrompt(&GlobalShellState);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
if (pid == 0)
|
if (pid == 0)
|
||||||
{
|
{
|
||||||
|
struct termios term;
|
||||||
|
tcgetattr(STDIN_FILENO, &term);
|
||||||
|
term.c_lflag |= (ECHO | ICANON | IEXTEN | ISIG);
|
||||||
|
term.c_oflag |= (OPOST | ONLCR);
|
||||||
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &term);
|
||||||
|
write(STDOUT_FILENO, "\r", 1);
|
||||||
|
|
||||||
execvp(args[0], args);
|
execvp(args[0], args);
|
||||||
perror("execvp");
|
if (errno == ENOENT)
|
||||||
|
{
|
||||||
|
write(STDERR_FILENO, "\r", 1);
|
||||||
|
fprintf(stderr, "%s: command not found\n", args[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write(STDERR_FILENO, "\r", 1);
|
||||||
|
perror(args[0]);
|
||||||
|
}
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
else if (pid > 0)
|
else if (pid > 0)
|
||||||
wait(NULL);
|
{
|
||||||
|
int status;
|
||||||
|
waitpid(pid, &status, 0);
|
||||||
|
write(STDOUT_FILENO, "\r\n", 2);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
perror("fork");
|
perror("fork");
|
||||||
}
|
}
|
||||||
@ -273,36 +738,113 @@ void ShellLoop(ShellState *state)
|
|||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
ReadLine(state);
|
ReadLine(state);
|
||||||
if (state->len == 0)
|
if (state->length == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
char *trimmed = state->line;
|
||||||
|
while (*trimmed && isspace(*trimmed))
|
||||||
|
trimmed++;
|
||||||
|
|
||||||
|
char *end = trimmed + strlen(trimmed) - 1;
|
||||||
|
while (end > trimmed && isspace(*end))
|
||||||
|
*end-- = '\0';
|
||||||
|
|
||||||
|
if (*trimmed == '\0')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
memmove(state->line, trimmed, strlen(trimmed) + 1);
|
||||||
|
state->length = strlen(state->line);
|
||||||
|
|
||||||
char *args[MAX_LINE_LEN / 2 + 1];
|
char *args[MAX_LINE_LEN / 2 + 1];
|
||||||
char *token = strtok(state->line, " ");
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (token != NULL)
|
char *p = state->line;
|
||||||
|
int inQuotes = 0;
|
||||||
|
char *start = p;
|
||||||
|
|
||||||
|
while (*p)
|
||||||
{
|
{
|
||||||
args[i++] = token;
|
if (*p == '"')
|
||||||
token = strtok(NULL, " ");
|
inQuotes = !inQuotes;
|
||||||
|
else if ((*p == ' ' || *p == '\t') && !inQuotes)
|
||||||
|
{
|
||||||
|
if (p > start)
|
||||||
|
{
|
||||||
|
*p = '\0';
|
||||||
|
args[i++] = start;
|
||||||
|
}
|
||||||
|
start = p + 1;
|
||||||
|
}
|
||||||
|
p++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (p > start)
|
||||||
|
args[i++] = start;
|
||||||
args[i] = NULL;
|
args[i] = NULL;
|
||||||
|
|
||||||
ExecuteCommand(args);
|
if (i > 0)
|
||||||
|
ExecuteCommand(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveHistory(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitializeShell(ShellState *state)
|
void InitializeShell(ShellState *state)
|
||||||
{
|
{
|
||||||
memset(state, 0, sizeof(ShellState));
|
memset(state, 0, sizeof(ShellState));
|
||||||
|
UpdatePrompt(state);
|
||||||
LoadHistory(state);
|
LoadHistory(state);
|
||||||
signal(SIGINT, HandleSignalInterrupt);
|
signal(SIGINT, HandleSignalInterrupt);
|
||||||
|
|
||||||
|
printf("\x1b[01;35mFennix Shell v%s\n\r", PROGRAM_VERSION);
|
||||||
|
printf("\x1b[;31mEarly development version!\x1b[0m\n\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisableRawModeAtExit(void)
|
||||||
|
{
|
||||||
|
DisableRawMode(&GlobalShellState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHelp()
|
||||||
|
{
|
||||||
|
printf("Usage: sh [OPTION]... [SCRIPT]\n");
|
||||||
|
printf("A simple shell implementation.\n\n");
|
||||||
|
printf("Options:\n");
|
||||||
|
printf(" -c COMMAND execute COMMAND and exit\n");
|
||||||
|
printf(" --help display this help and exit\n");
|
||||||
|
printf(" --version output version information and exit\n\n");
|
||||||
|
printf("If SCRIPT is provided, execute commands from the script file.\n");
|
||||||
|
printf("Otherwise, run in interactive mode.\n\n");
|
||||||
|
printf("Environment variables:\n");
|
||||||
|
printf(" SHELL_PROMPT custom prompt format (default: user@host:path$ )\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
ShellState state = {0};
|
if (argc > 1)
|
||||||
InitializeShell(&state);
|
{
|
||||||
|
if (strcmp(argv[1], "--help") == 0)
|
||||||
|
{
|
||||||
|
PrintHelp();
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
else if (strcmp(argv[1], "--version") == 0)
|
||||||
|
{
|
||||||
|
PRINTF_VERSION;
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *basename = strrchr(argv[0], '/');
|
||||||
|
if (basename == NULL)
|
||||||
|
basename = argv[0];
|
||||||
|
else
|
||||||
|
basename++;
|
||||||
|
if (strcmp(basename, "fsh") == 0)
|
||||||
|
progIsFsh = 1;
|
||||||
|
|
||||||
|
memset(&GlobalShellState, 0, sizeof(ShellState));
|
||||||
|
InitializeShell(&GlobalShellState);
|
||||||
|
|
||||||
|
signal(SIGINT, HandleSignalInterrupt);
|
||||||
|
atexit(DisableRawModeAtExit);
|
||||||
|
|
||||||
if (argc > 1)
|
if (argc > 1)
|
||||||
{
|
{
|
||||||
@ -314,8 +856,9 @@ int main(int argc, char *argv[])
|
|||||||
if (!fp)
|
if (!fp)
|
||||||
{
|
{
|
||||||
perror("fopen");
|
perror("fopen");
|
||||||
exit(EXIT_FAILURE);
|
CleanupAndExit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
char line[MAX_LINE_LEN];
|
char line[MAX_LINE_LEN];
|
||||||
while (fgets(line, sizeof(line), fp))
|
while (fgets(line, sizeof(line), fp))
|
||||||
{
|
{
|
||||||
@ -327,7 +870,8 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ShellLoop(&state);
|
ShellLoop(&GlobalShellState);
|
||||||
|
|
||||||
|
CleanupAndExit(EXIT_SUCCESS);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user