diff --git a/Projects/C Projects/Advance (GUI)/TextEditor-C/Makefile b/Projects/C Projects/Advance (GUI)/TextEditor-C/Makefile new file mode 100644 index 0000000..bac3887 --- /dev/null +++ b/Projects/C Projects/Advance (GUI)/TextEditor-C/Makefile @@ -0,0 +1,3 @@ +CC=gcc +tiny_editor: tiny_editor.c + $(CC) tiny_editor.c -o tiny_editor -Wall -Wextra -pedantic -std=c99 \ No newline at end of file diff --git a/Projects/C Projects/Advance (GUI)/TextEditor-C/Readme.md b/Projects/C Projects/Advance (GUI)/TextEditor-C/Readme.md new file mode 100644 index 0000000..3503d98 --- /dev/null +++ b/Projects/C Projects/Advance (GUI)/TextEditor-C/Readme.md @@ -0,0 +1,115 @@ +**tiny_editor** is a small terminal program written in C. +It opens a text file in your terminal and lets you move around and search inside it. + +Right now it is a **viewer**, not a full editor (it does not save changes). + + +## 1. Requirements + +You need: + +- Linux or WSL (Ubuntu is fine). +- `gcc` compiler. + +On Ubuntu/WSL you can install tools with: + +```bash +sudo apt update +sudo apt install build-essential +``` +## 2. Setup + +1. Create a folder and enter it: + + ```bash + mkdir tiny_editor + cd tiny_editor + ``` + +2. Create the C file: + + ```bash + nano tiny_editor.c + ``` + +3. Paste your `tiny_editor.c` code into the file, then: + + - `Ctrl+O`, Enter to save + - `Ctrl+X` to exit + +4. Check the file is there: + + ```bash + ls + # you should see: tiny_editor.c + ``` + +*** + +## 3. Compile + +In the same folder: + +```bash +gcc -Wall -Wextra -pedantic -std=c11 tiny_editor.c -o tiny_editor +``` + +If it works, you’ll see: + +```bash +ls +# tiny_editor tiny_editor.c +``` + +If you see an error like “tiny_editor.c: No such file or directory”, you are probably in the wrong folder or the filename is different. [log2base2](https://www.log2base2.com/C/basic/how-to-compile-the-c-program.html) + + +## 4. Run + +To open a file (for example, the source file itself): + +```bash +./tiny_editor tiny_editor.c +``` + +You will see the file content in a full‑screen view with `~` characters on empty lines. + +If the program crashes and your terminal looks broken (no echo, weird behavior), run: + +```bash +reset +``` + +*** + +## 5. Keys you can use + +Inside the program: + +- **Arrow Up/Down/Left/Right** + Move the cursor around the file. + +- **Ctrl+F** + Search: + - A prompt appears at the bottom: `Search: ...` + - Type your search text and press Enter to jump to the first match. + - Press Esc to cancel the search. + +- **Ctrl+Q** + Quit the program and return to the normal terminal. + +*** + +## 6. What to learn from this + +This project is useful to learn: + +- How to read single keypresses in C (using `read` and `termios`). +- How to draw a full screen “interface” using escape codes. +- How to store file lines in memory and display them. [viewsourcecode](https://viewsourcecode.org/snaptoken/kilo/) + +Later you can extend it to: + +- Insert/delete text. +- Save to a file. +- Add a status bar or syntax highlighting. \ No newline at end of file diff --git a/Projects/C Projects/Advance (GUI)/TextEditor-C/tiny_editor.c b/Projects/C Projects/Advance (GUI)/TextEditor-C/tiny_editor.c new file mode 100644 index 0000000..2de7f11 --- /dev/null +++ b/Projects/C Projects/Advance (GUI)/TextEditor-C/tiny_editor.c @@ -0,0 +1,378 @@ +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CTRL_KEY(k) ((k) & 0x1f) + +enum editorKey { + ARROW_LEFT = 1000, + ARROW_RIGHT, + ARROW_UP, + ARROW_DOWN, + DEL_KEY, + HOME_KEY, + END_KEY, + PAGE_UP, + PAGE_DOWN +}; + +struct termios orig_termios; + +typedef struct erow { + int size; + int rsize; + char *chars; + char *render; +} erow; + +struct editorConfig { + int cx, cy; + int rowoff, coloff; + int screenrows, screencols; + int numrows; + erow *row; +} E; + +void die(const char *s) { + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, "\x1b[H", 3); + perror(s); + exit(1); +} + +void disableRawMode() { + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) + die("tcsetattr"); +} + +void enableRawMode() { + if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) + die("tcgetattr"); + atexit(disableRawMode); + + struct termios raw = orig_termios; + 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] = 0; + raw.c_cc[VTIME] = 1; + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) + die("tcsetattr"); +} + +int editorReadKey() { + int nread; + char c; + while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { + if (nread == -1 && errno != EAGAIN) die("read"); + } + + if (c == '\x1b') { + char seq[3]; + + if (read(STDIN_FILENO, &seq[0], 1) != 1) return '\x1b'; + if (read(STDIN_FILENO, &seq[1], 1) != 1) return '\x1b'; + + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + if (read(STDIN_FILENO, &seq[2], 1) != 1) return '\x1b'; + if (seq[2] == '~') { + switch (seq[1]) { + case '1': return HOME_KEY; + case '3': return DEL_KEY; + case '4': return END_KEY; + case '5': return PAGE_UP; + case '6': return PAGE_DOWN; + case '7': return HOME_KEY; + case '8': return END_KEY; + } + } + } else { + switch (seq[1]) { + case 'A': return ARROW_UP; + case 'B': return ARROW_DOWN; + case 'C': return ARROW_RIGHT; + case 'D': return ARROW_LEFT; + case 'H': return HOME_KEY; + case 'F': return END_KEY; + } + } + } + + return '\x1b'; + } + + return c; +} + +int getWindowSize(int *rows, int *cols) { + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + return -1; + } else { + *cols = ws.ws_col; + *rows = ws.ws_row; + return 0; + } +} + +void editorUpdateRow(erow *row) { + free(row->render); + row->render = malloc(row->size + 1); + memcpy(row->render, row->chars, row->size); + row->render[row->size] = '\0'; + row->rsize = row->size; +} + +void editorAppendRow(const char *s, size_t len) { + E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1)); + + int at = E.numrows; + E.row[at].size = (int)len; + E.row[at].chars = malloc(len + 1); + memcpy(E.row[at].chars, s, len); + E.row[at].chars[len] = '\0'; + E.row[at].rsize = 0; + E.row[at].render = NULL; + editorUpdateRow(&E.row[at]); + + E.numrows++; +} + +void editorOpen(const char *filename) { + FILE *fp = fopen(filename, "r"); + if (!fp) return; + + char *line = NULL; + size_t cap = 0; + ssize_t len; + + while ((len = getline(&line, &cap, fp)) != -1) { + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) + len--; + editorAppendRow(line, len); + } + free(line); + fclose(fp); +} + +struct abuf { + char *b; + int len; +}; + +#define ABUF_INIT {NULL, 0} + +void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b, ab->len + len); + + if (new == NULL) return; + memcpy(&new[ab->len], s, len); + ab->b = new; + ab->len += len; +} + +void abFree(struct abuf *ab) { + free(ab->b); +} + +char *editorPrompt(const char *prompt) { + size_t bufsize = 128; + char *buf = malloc(bufsize); + size_t buflen = 0; + buf[0] = '\0'; + + while (1) { + struct abuf ab = ABUF_INIT; + + char status[256]; + snprintf(status, sizeof(status), prompt, buf); + int len = (int)strlen(status); + if (len > E.screencols) len = E.screencols; + + abAppend(&ab, "\x1b[H", 3); + for (int y = 0; y < E.screenrows - 1; y++) { + abAppend(&ab, "\x1b[K\r\n", 5); + } + abAppend(&ab, "\x1b[K", 3); + abAppend(&ab, status, len); + + write(STDOUT_FILENO, ab.b, ab.len); + abFree(&ab); + + int c = editorReadKey(); + if (c == '\x1b') { + free(buf); + return NULL; + } else if (c == '\r') { + if (buflen != 0) { + return buf; + } + } else if (!iscntrl(c) && c < 128) { + if (buflen == bufsize - 1) { + bufsize *= 2; + buf = realloc(buf, bufsize); + } + buf[buflen++] = c; + buf[buflen] = '\0'; + } + } +} + +void editorFind() { + char *query = editorPrompt("Search: %s (ESC to cancel)"); + if (query == NULL) return; + + for (int i = 0; i < E.numrows; i++) { + erow *row = &E.row[i]; + char *match = strstr(row->render, query); + if (match) { + E.cy = i; + E.cx = (int)(match - row->render); + E.rowoff = i; + E.coloff = E.cx; + break; + } + } + + free(query); +} + +void editorScroll() { + if (E.cy < E.rowoff) { + E.rowoff = E.cy; + } + if (E.cy >= E.rowoff + E.screenrows) { + E.rowoff = E.cy - E.screenrows + 1; + } + if (E.cx < E.coloff) { + E.coloff = E.cx; + } + if (E.cx >= E.coloff + E.screencols) { + E.coloff = E.cx - E.screencols + 1; + } +} + +void editorDrawRows(struct abuf *ab) { + for (int y = 0; y < E.screenrows; y++) { + int filerow = y + E.rowoff; + if (filerow >= E.numrows) { + abAppend(ab, "~", 1); + } else { + int len = E.row[filerow].rsize - E.coloff; + if (len < 0) len = 0; + if (len > E.screencols) len = E.screencols; + if (len > 0) + abAppend(ab, &E.row[filerow].render[E.coloff], len); + } + + abAppend(ab, "\x1b[K", 3); + if (y < E.screenrows - 1) { + abAppend(ab, "\r\n", 2); + } + } +} + +void editorRefreshScreen() { + editorScroll(); + + struct abuf ab = ABUF_INIT; + + abAppend(&ab, "\x1b[?25l", 6); + abAppend(&ab, "\x1b[H", 3); + + editorDrawRows(&ab); + + char buf[32]; + snprintf(buf, sizeof(buf), "\x1b[%d;%dH", + (E.cy - E.rowoff) + 1, + (E.cx - E.coloff) + 1); + abAppend(&ab, buf, (int)strlen(buf)); + + abAppend(&ab, "\x1b[?25h", 6); + + write(STDOUT_FILENO, ab.b, ab.len); + abFree(&ab); +} + +void editorMoveCursor(int key) { + erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; + + switch (key) { + case ARROW_LEFT: + if (E.cx != 0) E.cx--; + break; + case ARROW_RIGHT: + if (row && E.cx < row->size) E.cx++; + break; + case ARROW_UP: + if (E.cy != 0) E.cy--; + break; + case ARROW_DOWN: + if (E.cy < E.numrows) E.cy++; + break; + } + + row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; + int rowlen = row ? row->size : 0; + if (E.cx > rowlen) E.cx = rowlen; +} + +void editorProcessKeypress() { + int c = editorReadKey(); + + switch (c) { + case CTRL_KEY('q'): + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, "\x1b[H", 3); + exit(0); + case CTRL_KEY('f'): + editorFind(); + break; + case ARROW_LEFT: + case ARROW_RIGHT: + case ARROW_UP: + case ARROW_DOWN: + editorMoveCursor(c); + break; + } +} + +void initEditor() { + E.cx = 0; + E.cy = 0; + E.rowoff = 0; + E.coloff = 0; + E.numrows = 0; + E.row = NULL; + + if (getWindowSize(&E.screenrows, &E.screencols) == -1) + die("getWindowSize"); +} + +int main(int argc, char *argv[]) { + enableRawMode(); + initEditor(); + if (argc >= 2) { + editorOpen(argv[1]); + } + + while (1) { + editorRefreshScreen(); + editorProcessKeypress(); + } + + return 0; +} \ No newline at end of file