CE edit command
All checks were successful
Build documentation / build-and-deploy (push) Successful in 2m11s
All checks were successful
Build documentation / build-and-deploy (push) Successful in 2m11s
This commit is contained in:
50
ce/ce.c
50
ce/ce.c
@@ -7,6 +7,7 @@
|
||||
#include "strbuf.h"
|
||||
#include <arena.h>
|
||||
#include <kb.h>
|
||||
#include <liballoc.h>
|
||||
#include <list.h>
|
||||
#include <printf.h>
|
||||
#include <stdbool.h>
|
||||
@@ -17,9 +18,35 @@
|
||||
#include <tcursor.h>
|
||||
#include <tscreen.h>
|
||||
|
||||
#define LINE_INIT_MAX 40
|
||||
#define LINE_TAIL_BATCH_MAX 1024
|
||||
#define PROMPT "$ "
|
||||
#define LINE_INIT_MAX 40
|
||||
#define PROMPT "$ "
|
||||
|
||||
void* wmalloc (void* ctx, size_t size) {
|
||||
(void)ctx;
|
||||
return malloc (size);
|
||||
}
|
||||
|
||||
void* wrealloc (void* ctx, void* mem, size_t old, size_t new) {
|
||||
(void)ctx, (void)old;
|
||||
return realloc (mem, new);
|
||||
}
|
||||
|
||||
void wfree (void* ctx, void* mem) {
|
||||
(void)ctx;
|
||||
free (mem);
|
||||
}
|
||||
|
||||
void* warena_malloc (void* ctx, size_t size) {
|
||||
struct arena* a = ctx;
|
||||
return arena_malloc (a, size);
|
||||
}
|
||||
|
||||
void* warena_realloc (void* ctx, void* mem, size_t old, size_t new) {
|
||||
struct arena* a = ctx;
|
||||
return arena_realloc (a, mem, old, new);
|
||||
}
|
||||
|
||||
void warena_free (void* ctx, void* mem) { (void)ctx, (void)mem; }
|
||||
|
||||
struct edit_line {
|
||||
struct gapbuffer gb;
|
||||
@@ -44,7 +71,7 @@ void app_main (void) {
|
||||
const char* render = NULL;
|
||||
|
||||
while (interp_is_running ()) {
|
||||
gapbuffer_init (&edit_line.gb, LINE_INIT_MAX);
|
||||
gapbuffer_init (&warena_malloc, &arena, &edit_line.gb, LINE_INIT_MAX);
|
||||
edit_line.cursor = 0;
|
||||
|
||||
mprintf (PROMPT);
|
||||
@@ -66,7 +93,8 @@ void app_main (void) {
|
||||
|
||||
mprintf (ANSIQ_CUR_LEFT (1));
|
||||
|
||||
char* tail = gapbuffer_string_at (&edit_line.gb, edit_line.cursor);
|
||||
char* tail =
|
||||
gapbuffer_string_at (&warena_malloc, &arena, &edit_line.gb, edit_line.cursor);
|
||||
mprintf ("%s" ANSIQ_SCR_CLR2LEND, tail);
|
||||
|
||||
int move_back = strlen (tail);
|
||||
@@ -78,7 +106,8 @@ void app_main (void) {
|
||||
if (edit_line.cursor < gapbuffer_length (&edit_line.gb)) {
|
||||
edit_line.gb.gap_end++;
|
||||
|
||||
char* tail = gapbuffer_string_at (&edit_line.gb, edit_line.cursor);
|
||||
char* tail =
|
||||
gapbuffer_string_at (&warena_malloc, &arena, &edit_line.gb, edit_line.cursor);
|
||||
mprintf ("%s" ANSIQ_SCR_CLR2LEND, tail);
|
||||
|
||||
int move_back = strlen (tail);
|
||||
@@ -100,13 +129,14 @@ void app_main (void) {
|
||||
break;
|
||||
default:
|
||||
if (isprint (ch)) {
|
||||
gapbuffer_insert (&edit_line.gb, ch);
|
||||
gapbuffer_insert (&warena_realloc, &arena, &edit_line.gb, ch);
|
||||
edit_line.cursor++;
|
||||
|
||||
if (edit_line.cursor == gapbuffer_length (&edit_line.gb)) {
|
||||
mprintf ("%c", ch);
|
||||
} else {
|
||||
char* tail = gapbuffer_string_at (&edit_line.gb, edit_line.cursor - 1);
|
||||
char* tail =
|
||||
gapbuffer_string_at (&warena_malloc, &arena, &edit_line.gb, edit_line.cursor - 1);
|
||||
size_t move_back = strlen (tail) - 1;
|
||||
mprintf ("%s" ANSIQ_DYN_CUR_LEFT, tail, move_back);
|
||||
}
|
||||
@@ -117,12 +147,12 @@ void app_main (void) {
|
||||
|
||||
mprintf ("\n");
|
||||
|
||||
render = gapbuffer_get_string (&edit_line.gb);
|
||||
render = gapbuffer_get_string (&warena_malloc, &arena, &edit_line.gb);
|
||||
|
||||
if (render != NULL)
|
||||
exec_line (render);
|
||||
|
||||
gapbuffer_fini (&edit_line.gb);
|
||||
gapbuffer_fini (&warena_free, &arena, &edit_line.gb);
|
||||
arena_reset (&arena);
|
||||
}
|
||||
|
||||
|
||||
140
ce/edit.c
Normal file
140
ce/edit.c
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "edit.h"
|
||||
#include "arena_alloc.h"
|
||||
#include "gapbuffer.h"
|
||||
#include "mprintf.h"
|
||||
#include "self.h"
|
||||
#include "walloc.h"
|
||||
#include <arena.h>
|
||||
#include <kb.h>
|
||||
#include <liballoc.h>
|
||||
#include <list.h>
|
||||
#include <printf.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <system.h>
|
||||
#include <tcursor.h>
|
||||
#include <tgraphics.h>
|
||||
#include <tscreen.h>
|
||||
|
||||
struct edit_line {
|
||||
struct list_node_link lines_link;
|
||||
struct gapbuffer gb;
|
||||
};
|
||||
|
||||
struct cursor {
|
||||
size_t col;
|
||||
size_t line;
|
||||
};
|
||||
|
||||
struct editor {
|
||||
struct list_node_link* lines;
|
||||
struct edit_line* current_line;
|
||||
struct cursor cursor;
|
||||
};
|
||||
|
||||
static struct editor editor;
|
||||
|
||||
static bool prepare_lines_cb (void* ctx, const char* start, size_t len) {
|
||||
struct edit_line* line = malloc (sizeof (*line));
|
||||
|
||||
if (line == NULL)
|
||||
return false;
|
||||
|
||||
memset (line, 0, sizeof (*line));
|
||||
gapbuffer_init (&wmalloc, NULL, &line->gb, len);
|
||||
|
||||
for (size_t chr = 0; chr < len; chr++)
|
||||
gapbuffer_insert (&wrealloc, NULL, &line->gb, start[chr]);
|
||||
|
||||
list_append (editor.lines, &line->lines_link);
|
||||
|
||||
if (editor.current_line == NULL)
|
||||
editor.current_line = line;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prepare_lines (const char* text) { strtokenize (text, '\n', NULL, &prepare_lines_cb); }
|
||||
|
||||
void edit_start (const char* text) {
|
||||
prepare_lines (text);
|
||||
struct arena temp_arena;
|
||||
|
||||
mprintf (ANSIQ_SCR_SAVE ANSIQ_SCR_CLR_ALL ANSIQ_CUR_INVISIBLE);
|
||||
|
||||
for (;;) {
|
||||
const size_t backbuffer_max = 128 * 1024;
|
||||
char* backbuffer = arena_malloc (&temp_arena, backbuffer_max);
|
||||
memset (backbuffer, 0, backbuffer_max);
|
||||
|
||||
strncat (backbuffer, ANSIQ_CUR_HOME ANSIQ_SCR_CLR_ALL, backbuffer_max);
|
||||
|
||||
struct list_node_link *line_link, *tmp_line_link;
|
||||
list_foreach (editor.lines, line_link, tmp_line_link) {
|
||||
struct edit_line* line = list_entry (line_link, struct edit_line, lines_link);
|
||||
|
||||
strncat (backbuffer, ANSIQ_SCR_CLR_LINE, backbuffer_max);
|
||||
|
||||
char* render = gapbuffer_get_string (&warena_malloc, &temp_arena, &line->gb);
|
||||
strncat (backbuffer, render, backbuffer_max);
|
||||
strncat (backbuffer, "\n", backbuffer_max);
|
||||
}
|
||||
|
||||
char tmp[128];
|
||||
snprintf (tmp, sizeof (tmp), ANSIQ_DYN_CUR_SET ANSIQ_CUR_VISIBLE, (int)editor.cursor.line + 1,
|
||||
(int)editor.cursor.col + 1);
|
||||
strncat (backbuffer, tmp, backbuffer_max);
|
||||
|
||||
mail_send (e_pgid, backbuffer, strlen (backbuffer));
|
||||
|
||||
uint8_t ch = 0;
|
||||
mail_receive (&ch, 1);
|
||||
|
||||
gapbuffer_move (&editor.current_line->gb, editor.cursor.col);
|
||||
|
||||
switch (ch) {
|
||||
case '\b':
|
||||
if (editor.cursor.col > 0) {
|
||||
editor.cursor.col--;
|
||||
gapbuffer_backspace (&editor.current_line->gb);
|
||||
}
|
||||
break;
|
||||
case KB_DELETE:
|
||||
if (editor.cursor.col < gapbuffer_length (&editor.current_line->gb)) {
|
||||
editor.current_line->gb.gap_end++;
|
||||
}
|
||||
break;
|
||||
case KB_LEFT:
|
||||
if (editor.cursor.col > 0) {
|
||||
editor.cursor.col--;
|
||||
}
|
||||
break;
|
||||
case KB_RIGHT:
|
||||
if (editor.cursor.col < gapbuffer_length (&editor.current_line->gb)) {
|
||||
editor.cursor.col++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (isprint (ch)) {
|
||||
gapbuffer_insert (&wrealloc, NULL, &editor.current_line->gb, ch);
|
||||
editor.cursor.col++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
arena_reset (&temp_arena);
|
||||
}
|
||||
|
||||
mprintf (ANSIQ_CUR_VISIBLE ANSIQ_SCR_RESTORE);
|
||||
|
||||
arena_destroy (&temp_arena);
|
||||
|
||||
struct list_node_link *line_link, *tmp_line_link;
|
||||
list_foreach (editor.lines, line_link, tmp_line_link) {
|
||||
struct edit_line* line = list_entry (line_link, struct edit_line, lines_link);
|
||||
free (line->gb.buffer);
|
||||
free (line);
|
||||
}
|
||||
}
|
||||
6
ce/edit.h
Normal file
6
ce/edit.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef _EDIT_H
|
||||
#define _EDIT_H
|
||||
|
||||
void edit_start (const char* text);
|
||||
|
||||
#endif // _EDIT_H
|
||||
@@ -1,11 +1,12 @@
|
||||
#include "gapbuffer.h"
|
||||
#include "arena_alloc.h"
|
||||
#include <arena.h>
|
||||
#include <liballoc.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
void gapbuffer_init (struct gapbuffer* gb, size_t capacity) {
|
||||
gb->buffer = arena_malloc (&arena, capacity);
|
||||
void gapbuffer_init (gb_malloc_func_t mallocfn, void* ctx, struct gapbuffer* gb, size_t capacity) {
|
||||
gb->buffer = mallocfn (ctx, capacity);
|
||||
|
||||
if (gb->buffer == NULL)
|
||||
return;
|
||||
@@ -16,13 +17,16 @@ void gapbuffer_init (struct gapbuffer* gb, size_t capacity) {
|
||||
gb->gap_end = capacity;
|
||||
}
|
||||
|
||||
void gapbuffer_fini (struct gapbuffer* gb) { memset (gb, 0, sizeof (*gb)); }
|
||||
void gapbuffer_fini (gb_free_func_t freefn, void* ctx, struct gapbuffer* gb) {
|
||||
freefn (ctx, gb->buffer);
|
||||
memset (gb, 0, sizeof (*gb));
|
||||
}
|
||||
|
||||
void gapbuffer_grow (struct gapbuffer* gb) {
|
||||
void gapbuffer_grow (gb_realloc_func_t reallocfn, void* ctx, struct gapbuffer* gb) {
|
||||
size_t old_size = gb->size;
|
||||
size_t new_size = gb->size * 2;
|
||||
|
||||
char* new = arena_realloc (&arena, gb->buffer, old_size, new_size);
|
||||
char* new = reallocfn (ctx, gb->buffer, old_size, new_size);
|
||||
|
||||
if (new == NULL)
|
||||
return;
|
||||
@@ -55,9 +59,9 @@ void gapbuffer_move (struct gapbuffer* gb, size_t pos) {
|
||||
}
|
||||
}
|
||||
|
||||
void gapbuffer_insert (struct gapbuffer* gb, char c) {
|
||||
void gapbuffer_insert (gb_realloc_func_t reallocfn, void* ctx, struct gapbuffer* gb, char c) {
|
||||
if (gb->gap_start == gb->gap_end)
|
||||
gapbuffer_grow (gb);
|
||||
gapbuffer_grow (reallocfn, ctx, gb);
|
||||
|
||||
gb->buffer[gb->gap_start] = c;
|
||||
gb->gap_start++;
|
||||
@@ -68,10 +72,10 @@ void gapbuffer_backspace (struct gapbuffer* gb) {
|
||||
gb->gap_start--;
|
||||
}
|
||||
|
||||
char* gapbuffer_get_string (struct gapbuffer* gb) {
|
||||
char* gapbuffer_get_string (gb_malloc_func_t mallocfn, void* ctx, struct gapbuffer* gb) {
|
||||
size_t size = gb->size - (gb->gap_end - gb->gap_start);
|
||||
|
||||
char* str = arena_malloc (&arena, size + 1);
|
||||
char* str = mallocfn (ctx, size + 1);
|
||||
|
||||
if (str == NULL)
|
||||
return NULL;
|
||||
@@ -86,7 +90,7 @@ char* gapbuffer_get_string (struct gapbuffer* gb) {
|
||||
|
||||
size_t gapbuffer_length (struct gapbuffer* gb) { return gb->size - (gb->gap_end - gb->gap_start); }
|
||||
|
||||
char* gapbuffer_string_at (struct gapbuffer* gb, size_t pos) {
|
||||
char* gapbuffer_string_at (gb_malloc_func_t mallocfn, void* ctx, struct gapbuffer* gb, size_t pos) {
|
||||
size_t total_size = gapbuffer_length (gb);
|
||||
|
||||
if (pos >= total_size)
|
||||
@@ -94,7 +98,7 @@ char* gapbuffer_string_at (struct gapbuffer* gb, size_t pos) {
|
||||
|
||||
size_t tail_size = total_size - pos;
|
||||
|
||||
char* res = arena_malloc (&arena, tail_size + 1);
|
||||
char* res = mallocfn (ctx, tail_size + 1);
|
||||
|
||||
if (res == NULL)
|
||||
return NULL;
|
||||
@@ -124,3 +128,17 @@ char* gapbuffer_string_at (struct gapbuffer* gb, size_t pos) {
|
||||
res[written] = '\0';
|
||||
return res;
|
||||
}
|
||||
|
||||
char gapbuffer_at (struct gapbuffer* gb, size_t index) {
|
||||
if (index < gb->gap_start)
|
||||
return gb->buffer[index];
|
||||
else
|
||||
return gb->buffer[index + (gb->gap_end - gb->gap_start)];
|
||||
}
|
||||
|
||||
void gapbuffer_delete_at (struct gapbuffer* gb, size_t index) {
|
||||
gapbuffer_move (gb, index);
|
||||
|
||||
if (gb->gap_end < gb->size)
|
||||
gb->gap_end++;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef _GAPBUFFER_H
|
||||
#define _GAPBUFFER_H
|
||||
|
||||
#include <arena.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct gapbuffer {
|
||||
@@ -10,22 +11,32 @@ struct gapbuffer {
|
||||
size_t gap_end;
|
||||
};
|
||||
|
||||
void gapbuffer_init (struct gapbuffer* gb, size_t capacity);
|
||||
typedef void* (*gb_malloc_func_t) (void*, size_t);
|
||||
|
||||
void gapbuffer_fini (struct gapbuffer* gb);
|
||||
typedef void* (*gb_realloc_func_t) (void*, void*, size_t, size_t);
|
||||
|
||||
typedef void (*gb_free_func_t) (void*, void*);
|
||||
|
||||
void gapbuffer_init (gb_malloc_func_t mallocfn, void* ctx, struct gapbuffer* gb, size_t capacity);
|
||||
|
||||
void gapbuffer_fini (gb_free_func_t freefn, void* ctx, struct gapbuffer* gb);
|
||||
|
||||
void gapbuffer_move (struct gapbuffer* gb, size_t pos);
|
||||
|
||||
void gapbuffer_insert (struct gapbuffer* gb, char c);
|
||||
void gapbuffer_insert (gb_realloc_func_t reallocfn, void* ctx, struct gapbuffer* gb, char c);
|
||||
|
||||
void gapbuffer_backspace (struct gapbuffer* gb);
|
||||
|
||||
void gapbuffer_grow (struct gapbuffer* gb);
|
||||
void gapbuffer_grow (gb_realloc_func_t reallocfn, void* ctx, struct gapbuffer* gb);
|
||||
|
||||
char* gapbuffer_get_string (struct gapbuffer* gb);
|
||||
char* gapbuffer_get_string (gb_malloc_func_t mallocfn, void* ctx, struct gapbuffer* gb);
|
||||
|
||||
size_t gapbuffer_length (struct gapbuffer* gb);
|
||||
|
||||
char* gapbuffer_string_at (struct gapbuffer* gb, size_t pos);
|
||||
char* gapbuffer_string_at (gb_malloc_func_t mallocfn, void* ctx, struct gapbuffer* gb, size_t pos);
|
||||
|
||||
char gapbuffer_at (struct gapbuffer* gb, size_t index);
|
||||
|
||||
void gapbuffer_delete_at (struct gapbuffer* gb, size_t index);
|
||||
|
||||
#endif // _GAPBUFFER_H
|
||||
|
||||
47
ce/interp.c
47
ce/interp.c
@@ -1,12 +1,14 @@
|
||||
#include "interp.h"
|
||||
#include "arena_alloc.h"
|
||||
#include "context.h"
|
||||
#include "edit.h"
|
||||
#include "parser.h"
|
||||
#include "self.h"
|
||||
#include <desc.h>
|
||||
#include <filereader.h>
|
||||
#include <filewriter.h>
|
||||
#include <kb.h>
|
||||
#include <liballoc.h>
|
||||
#include <path.h>
|
||||
#include <process.h>
|
||||
#include <stddef.h>
|
||||
@@ -189,6 +191,48 @@ static void ls (struct context* context, const char* path_string) {
|
||||
volume_close ();
|
||||
}
|
||||
|
||||
static void edit (struct context* context, const char* path_string) {
|
||||
struct desc desc;
|
||||
char volume[VOLUME_MAX];
|
||||
const char* path;
|
||||
int ret;
|
||||
|
||||
if (!path_parse (path_string, volume, &path)) {
|
||||
cprintf (context, "ERROR bad path '%s'\n", path_string);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = volume_open (volume)) < 0) {
|
||||
cprintf (context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = describe (path, &desc)) < 0) {
|
||||
cprintf (context, "ERROR could not describe '%s': %s\n", path, str_status[-ret]);
|
||||
volume_close ();
|
||||
return;
|
||||
}
|
||||
|
||||
char* str = malloc (desc.size + 1);
|
||||
|
||||
if (str == NULL) {
|
||||
volume_close ();
|
||||
return;
|
||||
}
|
||||
|
||||
memset (str, 0, desc.size);
|
||||
|
||||
if ((ret = read_file (path, 0, (uint8_t*)str, desc.size)) < 0) {
|
||||
free (str);
|
||||
volume_close ();
|
||||
return;
|
||||
}
|
||||
|
||||
edit_start (str);
|
||||
|
||||
volume_close ();
|
||||
}
|
||||
|
||||
static void quit1 (struct context* context) {
|
||||
cprintf (context, "Goodbye!\n");
|
||||
interp_shutdown ();
|
||||
@@ -203,6 +247,7 @@ static void help (struct context* context) {
|
||||
cprintf (context, "mkfile <file path>\n");
|
||||
cprintf (context, "mkdir <directory path>\n");
|
||||
cprintf (context, "rm <path>\n");
|
||||
cprintf (context, "edit <path>\n");
|
||||
cprintf (context, "quit\n");
|
||||
}
|
||||
|
||||
@@ -239,6 +284,8 @@ static void execute_cmd (struct ast_cmd* cmd, struct context* context) {
|
||||
mkdir (context, cmd->args, cmd->arg_count);
|
||||
} else if (strcmp (cmd->name, "rm") == 0) {
|
||||
rm (context, cmd->args, cmd->arg_count);
|
||||
} else if (strcmp (cmd->name, "edit") == 0) {
|
||||
edit (context, cmd->args[0]);
|
||||
} else {
|
||||
char volume[VOLUME_MAX];
|
||||
const char* path;
|
||||
|
||||
@@ -6,7 +6,8 @@ c += ce.c \
|
||||
interp.c \
|
||||
mprintf.c \
|
||||
self.c \
|
||||
gapbuffer.c
|
||||
gapbuffer.c \
|
||||
edit.c
|
||||
|
||||
o += ce.o \
|
||||
arena_alloc.o \
|
||||
@@ -16,4 +17,5 @@ o += ce.o \
|
||||
interp.o \
|
||||
mprintf.o \
|
||||
self.o \
|
||||
gapbuffer.o
|
||||
gapbuffer.o \
|
||||
edit.o
|
||||
|
||||
16
ce/walloc.h
Normal file
16
ce/walloc.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef _WALLOC_H
|
||||
#define _WALLOC_H
|
||||
|
||||
void* wmalloc (void* ctx, size_t size);
|
||||
|
||||
void* wrealloc (void* ctx, void* mem, size_t old, size_t new);
|
||||
|
||||
void wfree (void* ctx, void* mem);
|
||||
|
||||
void* warena_malloc (void* ctx, size_t size);
|
||||
|
||||
void* warena_realloc (void* ctx, void* mem, size_t old, size_t new);
|
||||
|
||||
void warena_free (void* ctx, void* mem);
|
||||
|
||||
#endif // _WALLOC_H
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define ARENA_CHUNK_CAPACITY (8 * 1024)
|
||||
#define ARENA_CHUNK_CAPACITY (1024 * 1024)
|
||||
|
||||
struct arena_chunk {
|
||||
struct arena_chunk* next;
|
||||
|
||||
@@ -140,3 +140,17 @@ void* memmove (void* dest, const void* src, unsigned int n) {
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
char* strncat (char* dest, const char* src, size_t n) {
|
||||
char* ptr = dest;
|
||||
while (*ptr != '\0')
|
||||
ptr++;
|
||||
|
||||
while (n > 0 && *src != '\0') {
|
||||
*ptr++ = *src++;
|
||||
n--;
|
||||
}
|
||||
|
||||
*ptr = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ int strcmp (const char* s1, const char* s2);
|
||||
/* concatinate strings */
|
||||
char* strcat (char* dest, const char* src);
|
||||
|
||||
char* strncat (char* dest, const char* src, size_t n);
|
||||
|
||||
int isalnum (int c);
|
||||
|
||||
int isalpha (int c);
|
||||
|
||||
Reference in New Issue
Block a user