1073 lines
18 KiB
D
1073 lines
18 KiB
D
import dlib;
|
|
|
|
import vulkan;
|
|
import std.format : sformat;
|
|
import buffer;
|
|
import ui : Nil;
|
|
import ui;
|
|
import parsing;
|
|
|
|
import std.format;
|
|
import std.stdio;
|
|
import std.exception;
|
|
import std.file;
|
|
import std.string;
|
|
import core.stdc.stdio;
|
|
|
|
f32 g_delta = 0.0;
|
|
debug bool g_frame_step = false;
|
|
debug bool g_frame_continue = false;
|
|
|
|
struct EditorCtx
|
|
{
|
|
Arena arena;
|
|
PlatformWindow* window;
|
|
UIPanel* base_panel;
|
|
u64 panel_id;
|
|
EditState state;
|
|
u8[128] input_buf;
|
|
u32 icount;
|
|
Timer timer;
|
|
CmdPalette cmd;
|
|
u8[][] file_names;
|
|
}
|
|
|
|
struct CmdPalette
|
|
{
|
|
Arena arena;
|
|
Arena cmd_arena;
|
|
u8[] buffer;
|
|
u32 icount;
|
|
Command[] commands;
|
|
u8[][] opt_strs;
|
|
i64 selected;
|
|
Command current;
|
|
Parameter[] params;
|
|
}
|
|
|
|
struct Editor
|
|
{
|
|
Arena arena;
|
|
|
|
FlatBuffer buf;
|
|
Tokenizer tk;
|
|
|
|
Vec2 cursor_pos;
|
|
Vec2 select_start;
|
|
Vec2 select_end;
|
|
|
|
f32 text_size;
|
|
}
|
|
|
|
struct ChangeStacks
|
|
{
|
|
Arena arena;
|
|
u64 current_pos;
|
|
u8[] current_str;
|
|
u64 current_len;
|
|
EditorChange* undos;
|
|
EditorChange* redos;
|
|
}
|
|
|
|
struct EditorChange
|
|
{
|
|
u8[] str;
|
|
u64 pos;
|
|
EditorChange* next;
|
|
}
|
|
|
|
struct Command
|
|
{
|
|
u8[] name;
|
|
CmdType type;
|
|
}
|
|
|
|
struct Parameter
|
|
{
|
|
u8[] value;
|
|
bool visible;
|
|
}
|
|
|
|
enum CmdType
|
|
{
|
|
None,
|
|
OpenFile,
|
|
SaveFile,
|
|
CreateFile,
|
|
VSplit,
|
|
HSplit,
|
|
}
|
|
|
|
alias CT = CmdType;
|
|
|
|
enum EditState
|
|
{
|
|
NormalMode,
|
|
InputMode,
|
|
CmdOpen,
|
|
SetPanelFocus, // if moving left/right move up parent tree until one is found with a2d.x, same thing for up/down a2d.y
|
|
}
|
|
|
|
alias ES = EditState;
|
|
|
|
bool g_input_mode = false;
|
|
|
|
const Command NO_CMD = {
|
|
name: [],
|
|
type: CT.None,
|
|
};
|
|
|
|
void
|
|
Cycle(EditorCtx* ctx, Inputs* inputs)
|
|
{
|
|
ResetScratch(MB(4));
|
|
|
|
g_delta = DeltaTime(&ctx.timer);
|
|
|
|
debug if(g_frame_step)
|
|
{
|
|
g_delta = 0.01;
|
|
}
|
|
|
|
assert(Nil(ctx.base_panel.next));
|
|
|
|
HandleInputs(ctx, inputs);
|
|
|
|
debug if(g_frame_step && !g_frame_continue) return;
|
|
debug g_frame_continue = false;
|
|
|
|
g_input_mode = ctx.state == ES.InputMode;
|
|
|
|
BeginUI(ctx, inputs);
|
|
|
|
for(auto p = ctx.base_panel; !Nil(p); p = Recurse(p))
|
|
{
|
|
Panel(p);
|
|
}
|
|
|
|
if(ctx.state == ES.CmdOpen)
|
|
{
|
|
if(ctx.cmd.commands.length == 0 && ctx.cmd.icount == 0)
|
|
{
|
|
GetCommands(&ctx.cmd);
|
|
}
|
|
|
|
CommandPalette(&ctx.cmd);
|
|
}
|
|
|
|
EndUI();
|
|
}
|
|
|
|
|
|
EditorCtx
|
|
InitEditorCtx(PlatformWindow* window)
|
|
{
|
|
InitUICtx(window);
|
|
|
|
EditorCtx ctx = {
|
|
window: window,
|
|
arena: CreateArena(MB(2)),
|
|
cmd: {
|
|
arena: CreateArena(MB(1)),
|
|
cmd_arena: CreateArena(MB(1)),
|
|
buffer: Alloc!(u8)(1024),
|
|
},
|
|
};
|
|
|
|
ctx.base_panel = CreatePanel(&ctx);
|
|
ctx.base_panel.ed = CreateEditor(&ctx);
|
|
ctx.timer = CreateTimer();
|
|
SetFocusedPanel(ctx.base_panel);
|
|
|
|
if(getcwd() != "/")
|
|
{
|
|
// TODO: replace this with something nogc/nothrow
|
|
try
|
|
{
|
|
u64 count = 0;
|
|
foreach(DirEntry e; dirEntries(".", SpanMode.breadth))
|
|
{
|
|
if(indexOf(e.name, ".git") != -1 || e.isDir) continue;
|
|
|
|
u64 start = indexOf(e.name, "./") == 0 ? 2 : 0;
|
|
count += 1;
|
|
}
|
|
|
|
ctx.file_names = Alloc!(u8[])(&ctx.arena, count);
|
|
|
|
count = 0;
|
|
foreach(DirEntry e; dirEntries(".", SpanMode.breadth))
|
|
{
|
|
if(indexOf(e.name, ".git") != -1 || e.isDir) continue;
|
|
|
|
u64 start = indexOf(e.name, "./") == 0 ? 2 : 0;
|
|
ctx.file_names[count++] = Alloc!(u8)(&ctx.arena, CastStr!(u8)(e.name[start .. $]));
|
|
}
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
Logf("failed to open directory for filenames");
|
|
}
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
|
|
UIPanel*
|
|
CreatePanel(EditorCtx* ctx, SizeType size_type = ST.Percentage)
|
|
{
|
|
UIPanel* p = Alloc!(UIPanel)(&ctx.arena);
|
|
p.axis = A2D.Y;
|
|
p.id = Alloc!(u8)(&ctx.arena, 10);
|
|
p.pct = 1.0;
|
|
p.scroll_offset = 0.0;
|
|
p.scroll_target = 0.0;
|
|
|
|
(cast(char[])p.id).sformat("##%08s", ctx.panel_id);
|
|
p.parent = p.first = p.last = p.next = p.prev = g_UI_NIL_PANEL;
|
|
|
|
ctx.panel_id += 1;
|
|
|
|
return p;
|
|
}
|
|
|
|
bool
|
|
EditModeActive()
|
|
{
|
|
return g_input_mode;
|
|
}
|
|
|
|
char[]
|
|
ToAbsolutePath(u8[] file_name)
|
|
{
|
|
import core.stdc.string : strlen;
|
|
|
|
char[1024] name_buf = '\0';
|
|
char[1024] wd_buf = '\0';
|
|
version(linux)
|
|
{
|
|
import core.sys.posix.unistd;
|
|
getcwd(wd_buf.ptr, wd_buf.length);
|
|
}
|
|
|
|
version(Windows)
|
|
{
|
|
import core.sys.windows.direct;
|
|
_getcwd(wd_buf.ptr, wd_buf.length);
|
|
}
|
|
|
|
char[] wd = wd_buf[0 .. strlen(wd_buf.ptr)];
|
|
|
|
version(linux)
|
|
{
|
|
if(file_name[0] != '/')
|
|
{
|
|
name_buf.sformat("%s/%s", wd, cast(char[])file_name);
|
|
}
|
|
else
|
|
{
|
|
name_buf.sformat("%s", cast(char[])file_name);
|
|
}
|
|
}
|
|
|
|
version(Windows)
|
|
{
|
|
name_buf.sformat("%s/%s", wd, cast(char[])file_name);
|
|
}
|
|
|
|
char[] path_buf = ScratchAlloc!(char)(strlen(name_buf.ptr)+1);
|
|
path_buf[0 .. $] = name_buf[0 .. path_buf.length];
|
|
|
|
return path_buf;
|
|
}
|
|
|
|
void
|
|
SaveFile(Editor* ed, u8[] file_name)
|
|
{
|
|
import core.stdc.stdio;
|
|
|
|
file_name = file_name.length == 0 ? ed.buf.file_name : file_name;
|
|
|
|
if(file_name.length > 0)
|
|
{
|
|
char[] file_path = ToAbsolutePath(file_name);
|
|
auto f = fopen(cast(char*)file_path.ptr, "wb");
|
|
if(f != null)
|
|
{
|
|
u64 tab_count;
|
|
for(u64 i = 0; i < ed.buf.length; i += 1)
|
|
{
|
|
if(ed.buf.pbuf[i] == '\t')
|
|
{
|
|
tab_count += 1;
|
|
}
|
|
}
|
|
|
|
u64 tab_width = GetCtx().tab_width;
|
|
u64 buf_size = ed.buf.length + ((tab_width-1) * tab_count);
|
|
u8[] temp_buf = ScratchAlloc!(u8)(buf_size);
|
|
|
|
u64 buf_pos;
|
|
for(u64 i = 0; i < ed.buf.length; i += 1)
|
|
{
|
|
if(ed.buf.pbuf[i] == '\t')
|
|
{
|
|
for(u64 j = 0; j < tab_width; j += 1)
|
|
{
|
|
temp_buf[buf_pos++] = ' ';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
temp_buf[buf_pos++] = ed.buf.pbuf[i];
|
|
}
|
|
}
|
|
|
|
fwrite(temp_buf.ptr, 1, buf_size, f);
|
|
fflush(f);
|
|
fclose(f);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
OpenFile(Editor* ed, u8[] file_name)
|
|
{
|
|
import core.stdc.stdio;
|
|
import std.file;
|
|
import std.conv;
|
|
|
|
if(file_name.length > 0)
|
|
{
|
|
char[] file_path = ToAbsolutePath(file_name);
|
|
auto f = fopen(file_path.ptr, "rb");
|
|
if(f != null)
|
|
{
|
|
fseek(f, 0, SEEK_END);
|
|
i64 len = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
if(len > 0)
|
|
{
|
|
u8[] buf = ScratchAlloc!(u8)(len);
|
|
fread(buf.ptr, u8.sizeof, len, f);
|
|
Change(&ed.buf, buf, file_name);
|
|
ed.buf.file_name = file_name;
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
else
|
|
{
|
|
perror("[Error] Unable to open file");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load all files then move things into editor after being created when selected
|
|
|
|
Editor*
|
|
CreateEditor(EditorCtx* ctx)
|
|
{
|
|
Editor* ed = Alloc!(Editor)(&ctx.arena);
|
|
|
|
ed.arena = CreateArena(MB(4));
|
|
ed.buf = CreateFlatBuffer([], []);
|
|
|
|
return ed;
|
|
}
|
|
|
|
debug
|
|
{
|
|
__gshared u64 panel_count = 0;
|
|
__gshared UIPanel*[1024] panels = null;
|
|
}
|
|
|
|
void
|
|
AddEditor(EditorCtx* ctx, UIPanel* target, Axis2D axis)
|
|
{
|
|
if(Nil(target.parent) || target.parent.axis != axis)
|
|
{
|
|
UIPanel* first = CreatePanel(ctx);
|
|
UIPanel* second = CreatePanel(ctx);
|
|
|
|
first.ed = target.ed;
|
|
second.ed = CreateEditor(ctx);
|
|
|
|
first.pct = second.pct = 0.5;
|
|
|
|
target.axis = axis;
|
|
target.ed = null;
|
|
|
|
PushPanel(target, first);
|
|
PushPanel(target, second);
|
|
|
|
SetFocusedPanel(second);
|
|
|
|
debug
|
|
{
|
|
panels[panel_count+0] = first;
|
|
panels[panel_count+1] = second;
|
|
|
|
panel_count += 2;
|
|
}
|
|
}
|
|
else if(target.parent.axis == axis)
|
|
{
|
|
UIPanel* panel = CreatePanel(ctx);
|
|
panel.ed = CreateEditor(ctx);
|
|
|
|
InsertPanel(target.parent, target, panel);
|
|
|
|
u64 count = 0;
|
|
for(UIPanel* p = target.parent.first; !Nil(p); p = p.next, count += 1) {}
|
|
|
|
f32 pct = 1.0/count;
|
|
for(UIPanel* p = target.parent.first; !Nil(p); p = p.next)
|
|
{
|
|
p.pct = pct;
|
|
}
|
|
|
|
SetFocusedPanel(panel);
|
|
|
|
debug
|
|
{
|
|
panels[panel_count] = panel;
|
|
|
|
panel_count += 1;
|
|
}
|
|
}
|
|
|
|
debug for(u64 i = 0; i < panel_count; i += 1)
|
|
{
|
|
bool root_found = false;
|
|
for(UIPanel* p = panels[i]; !Nil(p); p = p.parent)
|
|
{
|
|
if(p == ctx.base_panel)
|
|
{
|
|
root_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!root_found)
|
|
{
|
|
Logf("[DEBUG] panel %s unable to reach root", cast(char[])panels[panel_count].id);
|
|
assert(root_found);
|
|
}
|
|
}
|
|
}
|
|
|
|
pragma(inline) void
|
|
InsertInputToBuf(EditorCtx* ctx)
|
|
{
|
|
if(ctx.icount > 0)
|
|
{
|
|
UIPanel* p = GetFocusedPanel();
|
|
if(!Nil(p))
|
|
{
|
|
Insert(&p.ed.buf, ctx.input_buf, ctx.icount);
|
|
ctx.icount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ResetCtx(EditorCtx* ctx)
|
|
{
|
|
InsertInputToBuf(ctx);
|
|
|
|
ctx.state = ES.NormalMode;
|
|
ctx.cmd.icount = 0;
|
|
ctx.cmd.commands = [];
|
|
ctx.cmd.current = cast(Command)NO_CMD;
|
|
ctx.cmd.selected = 0;
|
|
}
|
|
|
|
bool
|
|
Shift(Modifier md)
|
|
{
|
|
return cast(bool)(md & (MD.LeftShift | MD.RightShift));
|
|
}
|
|
|
|
bool
|
|
MovePanelFocus(A2D axis, bool prev)(UIPanel* panel)
|
|
{
|
|
bool result = false;
|
|
|
|
if(!Nil(panel))
|
|
{
|
|
UIPanel* target = prev ? panel.prev : panel.next;
|
|
if(panel.parent.axis == axis && !Nil(target) && target.ed != null)
|
|
{
|
|
SetFocusedPanel(target);
|
|
result = true;
|
|
}
|
|
else for(UIPanel* p = panel.parent; !Nil(p); p = p.parent)
|
|
{
|
|
if(p.parent.axis == axis)
|
|
{
|
|
for(UIPanel* t = prev ? p.prev : p.next; !Nil(t); t = prev ? t.prev : t.next)
|
|
{
|
|
UIPanel* f = t.first;
|
|
if(!Nil(f) || f.ed == null)
|
|
{
|
|
while(f.ed == null)
|
|
{
|
|
if(Nil(f.first))
|
|
{
|
|
break;
|
|
}
|
|
|
|
f = f.first;
|
|
}
|
|
}
|
|
|
|
if(!Nil(f) && f.ed != null)
|
|
{
|
|
SetFocusedPanel(f);
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
HandleInputs(EditorCtx* ctx, Inputs* inputs)
|
|
{
|
|
u8[] cb_text;
|
|
UIPanel* panel = GetFocusedPanel();
|
|
FlatBuffer* fb = !Nil(panel) ? &panel.ed.buf : null;
|
|
|
|
for(auto node = inputs.list.first; node != null; node = node.next)
|
|
{
|
|
bool taken = false;
|
|
|
|
Input key = node.value.key;
|
|
Modifier md = node.value.md;
|
|
bool pressed = node.value.pressed;
|
|
|
|
if (pressed)
|
|
{
|
|
if(key == Input.Escape)
|
|
{
|
|
ResetCtx(ctx);
|
|
taken = true;
|
|
}
|
|
else if(ctx.state == ES.InputMode)
|
|
{
|
|
taken = HandleInputMode(ctx, node.value);
|
|
}
|
|
else if(ctx.state == ES.CmdOpen)
|
|
{
|
|
taken = HandleCmdMode(ctx, node.value);
|
|
}
|
|
else if(ctx.state == ES.SetPanelFocus)
|
|
{
|
|
switch(key) with(Input)
|
|
{
|
|
case Up:
|
|
{
|
|
if(MovePanelFocus!(A2D.Y, true )(panel)) goto case Escape;
|
|
} break;
|
|
case Down:
|
|
{
|
|
if(MovePanelFocus!(A2D.Y, false)(panel)) goto case Escape;
|
|
} break;
|
|
case Left:
|
|
{
|
|
if(MovePanelFocus!(A2D.X, true )(panel)) goto case Escape;
|
|
} break;
|
|
case Right:
|
|
{
|
|
if(MovePanelFocus!(A2D.X, false)(panel)) goto case Escape;
|
|
} break;
|
|
case Escape:
|
|
{
|
|
ResetCtx(ctx);
|
|
taken = true;
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(key) with(Input)
|
|
{
|
|
case a, i:
|
|
{
|
|
if(key == a && Shift(md) && fb)
|
|
{
|
|
MoveToEOL(fb);
|
|
}
|
|
else if(key == a)
|
|
{
|
|
Move(fb, Right, MD.None);
|
|
}
|
|
else if(key == i && Shift(md))
|
|
{
|
|
MoveToSOL(fb);
|
|
}
|
|
|
|
ctx.state = ES.InputMode;
|
|
taken = true;
|
|
} break;
|
|
case Semicolon:
|
|
{
|
|
if(Shift(node.value.md))
|
|
{
|
|
ctx.state = ES.CmdOpen;
|
|
taken = true;
|
|
}
|
|
} break;
|
|
case v:
|
|
{
|
|
if(Ctrl(md))
|
|
{
|
|
cb_text = ClipboardText(ctx.window);
|
|
}
|
|
if(Shift(md))
|
|
{
|
|
ToggleSelection(fb, SM.Line);
|
|
}
|
|
else
|
|
{
|
|
ToggleSelection(fb, SM.Normal);
|
|
}
|
|
} break;
|
|
case c:
|
|
{
|
|
if(Ctrl(md))
|
|
{
|
|
// Copy
|
|
taken = true;
|
|
}
|
|
} break;
|
|
case w:
|
|
{
|
|
if(Ctrl(md))
|
|
{
|
|
ctx.state = ES.SetPanelFocus;
|
|
}
|
|
} break;
|
|
debug case d:
|
|
{
|
|
static bool dbg = false;
|
|
dbg = !dbg;
|
|
SetDebug(dbg);
|
|
} break;
|
|
debug case g:
|
|
{
|
|
g_frame_step = !g_frame_step;
|
|
} break;
|
|
debug case s:
|
|
{
|
|
g_frame_continue = true;
|
|
} break;
|
|
default: taken = Move(fb, key, md); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(taken)
|
|
{
|
|
DLLRemove(&inputs.list, node, null);
|
|
}
|
|
}
|
|
|
|
InsertInputToBuf(ctx);
|
|
|
|
if(cb_text.length > 0)
|
|
{
|
|
Insert(fb, cb_text, cb_text.length);
|
|
}
|
|
}
|
|
|
|
void
|
|
MoveCursor(InputEvent ev)
|
|
{
|
|
UIPanel* p = GetFocusedPanel();
|
|
|
|
if(!Nil(p))
|
|
{
|
|
FlatBuffer* fb = &p.ed.buf;
|
|
switch(ev.key) with(Input)
|
|
{
|
|
case Up, Down, Left, Right: Move(fb, ev.key, ev.md); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
pragma(inline) void
|
|
InsertChar(EditorCtx* ctx, Input input, bool modified)
|
|
{
|
|
ctx.input_buf[ctx.icount++] = InputToChar(input, modified);
|
|
}
|
|
|
|
static string
|
|
CharCases()
|
|
{
|
|
import std.traits;
|
|
|
|
string result = "";
|
|
foreach(input; EnumMembers!Input)
|
|
{
|
|
u8 ch = InputToChar(input);
|
|
if(ch > 0)
|
|
{
|
|
result ~= format("case Input.%s: InsertChar(ctx, Input.%s, cast(bool)(ev.md & (MD.LeftShift | MD.RightShift))); taken = true; break;\n", input, input);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
HandleInputMode(EditorCtx* ctx, InputEvent ev)
|
|
{
|
|
bool taken = false;
|
|
|
|
switch(ev.key)
|
|
{
|
|
mixin(CharCases());
|
|
case Input.Backspace:
|
|
{
|
|
UIPanel* p = GetFocusedPanel();
|
|
if(!Nil(p))
|
|
{
|
|
Backspace(&p.ed.buf);
|
|
}
|
|
} break;
|
|
case Input.Escape:
|
|
{
|
|
ctx.state = ES.NormalMode;
|
|
} break;
|
|
default: MoveCursor(ev); break;
|
|
}
|
|
|
|
return taken;
|
|
}
|
|
|
|
static string
|
|
TextLineCharCases()
|
|
{
|
|
import std.traits;
|
|
|
|
string result = "";
|
|
foreach(input; EnumMembers!Input)
|
|
{
|
|
u8 ch = InputToChar(input);
|
|
if(ch > 0 && ch != '\n' && ch != '\t' && ch != ' ')
|
|
{
|
|
if(ch == '\'' || ch == '\\')
|
|
{
|
|
result ~= format("case %s: result = '\\%s'; taken = true; break;\n", input, cast(char)ch);
|
|
}
|
|
else
|
|
{
|
|
result ~= format("case %s: result = '%s'; taken = true; break;\n", input, cast(char)ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
pragma(inline) u8
|
|
Lower(u8 ch)
|
|
{
|
|
if(ch >= 65 && ch <= 90)
|
|
{
|
|
ch += 32;
|
|
}
|
|
|
|
return ch;
|
|
}
|
|
|
|
bool
|
|
StrContains(bool begins_with)(const u8[] str, u8[] match)
|
|
{
|
|
return StrContains!(begins_with)(cast(u8[])str, match);
|
|
}
|
|
|
|
bool
|
|
StrContains(bool begins_with)(u8[] str, u8[] match)
|
|
{
|
|
u64 count;
|
|
for(u64 i = 0; i < str.length; i += 1)
|
|
{
|
|
static if(begins_with) if(i >= match.length)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if(Lower(str[i]) == Lower(match[count]))
|
|
{
|
|
count += 1;
|
|
}
|
|
else
|
|
{
|
|
count = 0;
|
|
}
|
|
|
|
static if(!begins_with) if(count == match.length)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return count == match.length;
|
|
}
|
|
|
|
void
|
|
GetCommands(CmdPalette* cmd)
|
|
{
|
|
const Command[] cmd_list = [
|
|
{
|
|
name: CastStr!(u8)("open"),
|
|
type: CT.OpenFile,
|
|
},
|
|
{
|
|
name: CastStr!(u8)("save"),
|
|
type: CT.SaveFile,
|
|
},
|
|
{
|
|
name: CastStr!(u8)("create"),
|
|
type: CT.CreateFile,
|
|
},
|
|
{
|
|
name: CastStr!(u8)("vsplit"),
|
|
type: CT.VSplit,
|
|
},
|
|
{
|
|
name: CastStr!(u8)("hsplit"),
|
|
type: CT.HSplit,
|
|
},
|
|
];
|
|
|
|
Reset(&cmd.arena);
|
|
cmd.commands = Alloc!(Command)(&cmd.arena, cmd_list.length);
|
|
cmd.params = [];
|
|
|
|
u8[] str = cmd.buffer[0 .. cmd.icount];
|
|
|
|
u64 count = 0;
|
|
if(str.length > 0)
|
|
{
|
|
for(u64 i = 0; i < cmd_list.length; i += 1)
|
|
{
|
|
if(StrContains!(true)(cmd_list[i].name, str))
|
|
{
|
|
cmd.commands[count] = cast(Command)cmd_list[i];
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(count == 0 && cmd.icount == 0)
|
|
{
|
|
cmd.commands[] = cast(Command[])cmd_list[];
|
|
}
|
|
else
|
|
{
|
|
cmd.commands = cmd.commands[0 .. count];
|
|
}
|
|
|
|
cmd.opt_strs = Alloc!(u8[])(&cmd.arena, cmd.commands.length);
|
|
for(u64 i = 0; i < cmd.commands.length; i += 1)
|
|
{
|
|
cmd.opt_strs[i] = cmd.commands[i].name;
|
|
}
|
|
}
|
|
|
|
bool
|
|
HandleCmdMode(EditorCtx* ctx, InputEvent ev)
|
|
{
|
|
u8 result = 0;
|
|
bool taken = false;
|
|
CmdPalette* cmd = &ctx.cmd;
|
|
|
|
u64 prev_count = cmd.icount;
|
|
switch(ev.key) with(Input)
|
|
{
|
|
case Enter:
|
|
{
|
|
if(cmd.current.type == CT.None && cmd.commands.length > 0)
|
|
{
|
|
if(cmd.commands[cmd.selected].type == CT.OpenFile)
|
|
{
|
|
goto case Tab;
|
|
}
|
|
else
|
|
{
|
|
cmd.current = cmd.commands[cmd.selected];
|
|
}
|
|
}
|
|
|
|
UIPanel* p = GetFocusedPanel();
|
|
|
|
switch(cmd.current.type)
|
|
{
|
|
case CT.OpenFile:
|
|
{
|
|
if(!Nil(p) && cmd.selected >= 0 && cmd.selected < cmd.opt_strs.length)
|
|
{
|
|
OpenFile(p.ed, cmd.opt_strs[cmd.selected]);
|
|
}
|
|
} break;
|
|
case CT.SaveFile:
|
|
{
|
|
if(!Nil(p))
|
|
{
|
|
SaveFile(p.ed, GetParam(cmd));
|
|
}
|
|
} break;
|
|
case CT.VSplit, CT.HSplit:
|
|
{
|
|
AddEditor(ctx, p, cmd.current.type == CT.VSplit ? A2D.X : A2D.Y);
|
|
} break;
|
|
default: break;
|
|
}
|
|
|
|
ResetCtx(ctx);
|
|
} goto CmdInputEnd;
|
|
case Backspace:
|
|
{
|
|
if(CondIncr!(-1)(cmd.icount > 0, &cmd.icount) && cmd.buffer[cmd.icount] == ' ')
|
|
{
|
|
cmd.current = cast(Command)NO_CMD;
|
|
}
|
|
} break;
|
|
case Space:
|
|
{
|
|
Check(cmd, 1);
|
|
cmd.buffer[cmd.icount++] = ' ';
|
|
} goto case Tab;
|
|
case Tab:
|
|
{
|
|
if(cmd.commands.length > 0)
|
|
{
|
|
cmd.current = cmd.commands[cmd.selected];
|
|
cmd.buffer[0 .. cmd.current.name.length] = cmd.current.name[0 .. $];
|
|
cmd.icount = cast(u32)cmd.current.name.length;
|
|
cmd.buffer[cmd.icount++] = ' ';
|
|
}
|
|
} break;
|
|
case Up:
|
|
{
|
|
CondIncr!(-1)(cmd.selected > 0, &cmd.selected);
|
|
} break;
|
|
case Down:
|
|
{
|
|
CondIncr!(+1)(cmd.selected < cmd.opt_strs.length-1, &cmd.selected);
|
|
} break;
|
|
mixin(TextLineCharCases());
|
|
default: break;
|
|
}
|
|
|
|
if(result != 0)
|
|
{
|
|
Check(cmd, 1);
|
|
cmd.buffer[cmd.icount++] = result;
|
|
}
|
|
|
|
if(cmd.current.type == CT.None && (cmd.commands.length == 0 || prev_count != cmd.icount))
|
|
{
|
|
GetCommands(cmd);
|
|
}
|
|
else if(prev_count != cmd.icount)
|
|
{
|
|
switch(cmd.current.type) with(CT)
|
|
{
|
|
case OpenFile:
|
|
{
|
|
PopulateParams(cmd, ctx.file_names);
|
|
} break;
|
|
case SaveFile:
|
|
{
|
|
u8[] param = GetParam(cmd);
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
CmdInputEnd:
|
|
|
|
return taken;
|
|
}
|
|
|
|
pragma(inline) void
|
|
Check(CmdPalette* cmd, u64 length)
|
|
{
|
|
if(cmd.icount+length >= cmd.buffer.length)
|
|
{
|
|
cmd.buffer = Realloc!(u8)(cmd.buffer, cmd.buffer.length*2);
|
|
}
|
|
}
|
|
|
|
u8[]
|
|
GetParam(CmdPalette* cmd)
|
|
{
|
|
u8[] param = [];
|
|
for(u64 i = cmd.current.name.length; i < cmd.icount; i += 1)
|
|
{
|
|
if(cmd.buffer[i] != ' ')
|
|
{
|
|
param = cmd.buffer[i .. cmd.icount];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return param;
|
|
}
|
|
|
|
void
|
|
PopulateParams(CmdPalette* cmd, u8[][] strs)
|
|
{
|
|
u8[] param = GetParam(cmd);
|
|
if(cmd.params.length == 0 || !param.length)
|
|
{
|
|
cmd.params = Alloc!(Parameter)(&cmd.cmd_arena, strs.length);
|
|
cmd.opt_strs = Alloc!(u8[])(&cmd.cmd_arena, strs.length);
|
|
|
|
for(u64 i = 0; i < cmd.params.length; i += 1)
|
|
{
|
|
cmd.params[i].value = strs[i];
|
|
cmd.params[i].visible = true;
|
|
cmd.opt_strs[i] = cmd.params[i].value;
|
|
}
|
|
}
|
|
|
|
if(param.length)
|
|
{
|
|
cmd.opt_strs = Alloc!(u8[])(&cmd.cmd_arena, strs.length);
|
|
|
|
u64 matches;
|
|
for(u64 i = 0; i < cmd.params.length; i += 1)
|
|
{
|
|
bool contains = StrContains!(false)(cmd.params[i].value, param);
|
|
if(contains)
|
|
{
|
|
cmd.opt_strs[matches] = cmd.params[i].value;
|
|
matches += 1;
|
|
}
|
|
|
|
cmd.params[i].visible = contains;
|
|
}
|
|
|
|
cmd.opt_strs = cmd.opt_strs[0 .. matches];
|
|
}
|
|
|
|
if(cmd.selected >= cmd.opt_strs.length)
|
|
{
|
|
cmd.selected = Max(cmd.opt_strs.length, 0);
|
|
}
|
|
}
|
|
|