4coder/power/4coder_handmade_hero.cpp

1396 lines
43 KiB
C++

/* TODO(casey): Here are our current issues
- Display:
- Jumping to subsequent errors in a file seems to jump to an unrelated position
then scroll back to the actual position, which results in lots of extra scrolling
- Need a way of highlighting the current line like Emacs does for the benefit
of people on The Stream(TM)
- Need a way of changing some things visually so we can indicate the modal state (this has
to be something very obvious - ideally something like
1) Change the line highlight color
2) In modal mode, highlight the whole selected region (mark to cursor) potentially?
- Some way to recenter the view so that the line containing the cursor becomes the
center line vertically.
- NOTE / IMPORTANT / TODO highlighting? Ability to customize? Whatever.
- Indentation:
- Multiple // lines don't seem to indent properly. The first one will go to the correct place, but the subsequent ones will go to the first column regardless?
- Would like the option to indent to hanging parentheses, equals signs, etc. instead of
always just "one tab in from the previous line".
- Need to have better indentation / wrapping control for typing in comments.
Right now it's a bit worse than Emacs, which does automatically put you at
the same margin as the prev. line (4coder just goes back to column 1). It'd
be nice if it got _better_ than Emacs, with no need to manually flow comments,
etc.
- Buffer management:
- Have buffers normalize slashes to always be forward-slash - right now I'm doing this manually
- Need auto-complete for things like "arbitrary command", with options listed, etc.,
so this should either be built into 4ed, or the custom DLL should have the ability
to display possible completions and iterate over internal cmdid's, etc. Possibly
the latter, for maximal ability of customizers to add their own commands?
- Macro recording/playback
NOTE(allen): Things that were on the issue list that are now fixed
- Need a way of calling things by name, so infrequently used functions won't need keybindings
- Need a way of changing some things visually so we can indicate the modal state (this has
to be something very obvious - ideally something like
1) Change the cursor color
2) Change the header bar color?
- Need a way to set the theme from the custom config file so I don't have to pick it every
time.
- Switch-to-buffer with no typing, just return, should switch to the most recently
used buffer that is not currently displayed in a view.
- Kill-buffer should perform this switch automatically, or it should be easy
to build a custom kill buffer that does
- Seems like there's no way to switch to buffers whose names are substrings of other
buffers' names without using the mouse?
- Scroll speed seems to slow. It's behind where I am a lot of the time. Should be
_much_ more accelerated than it is, I think - presumably this will be tunable?
- Crash bug with paste-and-indent that sometimes leaves things unindented then crashes
*/
// NOTE(casey): Microsoft/Windows is poopsauce.
#include <math.h>
#include <stdio.h>
#define UseHack4Coder 0
#ifndef Assert
#define internal static
#define Assert assert
#endif
static bool GlobalEditMode;
static char *GlobalCompilationBufferName = "*compilation*";
#if UseHack4Coder
#include <windows.h>
static HWND GlobalModalIndicator;
static HBRUSH GlobalEditModeBrush;
static HBRUSH GlobalNormalModeBrush;
static WNDPROC global_old_4coder_winproc;
#endif
// TODO(casey): If 4coder gets variables at some point, this would go in a variable.
static char BuildDirectory[4096] = "./";
static int ErrorParsingPosition;
static int ErrorParsingLastJumpLine;
static int ErrorParsingLastBufferID;
enum token_type
{
Token_Unknown,
Token_OpenParen,
Token_CloseParen,
Token_Asterisk,
Token_Minus,
Token_Plus,
Token_ForwardSlash,
Token_Percent,
Token_Colon,
Token_Number,
Token_EndOfStream,
};
struct token
{
token_type Type;
size_t TextLength;
char *Text;
};
struct tokenizer
{
char *At;
};
inline bool
IsEndOfLine(char C)
{
bool Result = ((C == '\n') ||
(C == '\r'));
return(Result);
}
inline bool
IsWhitespace(char C)
{
bool Result = ((C == ' ') ||
(C == '\t') ||
(C == '\v') ||
(C == '\f') ||
IsEndOfLine(C));
return(Result);
}
inline bool
IsAlpha(char C)
{
bool Result = (((C >= 'a') && (C <= 'z')) ||
((C >= 'A') && (C <= 'Z')));
return(Result);
}
inline bool
IsNumeric(char C)
{
bool Result = ((C >= '0') && (C <= '9'));
return(Result);
}
static void
EatAllWhitespace(tokenizer *Tokenizer)
{
for(;;)
{
if(IsWhitespace(Tokenizer->At[0]))
{
++Tokenizer->At;
}
else if((Tokenizer->At[0] == '/') &&
(Tokenizer->At[1] == '/'))
{
Tokenizer->At += 2;
while(Tokenizer->At[0] && !IsEndOfLine(Tokenizer->At[0]))
{
++Tokenizer->At;
}
}
else if((Tokenizer->At[0] == '/') &&
(Tokenizer->At[1] == '*'))
{
Tokenizer->At += 2;
while(Tokenizer->At[0] &&
!((Tokenizer->At[0] == '*') &&
(Tokenizer->At[1] == '/')))
{
++Tokenizer->At;
}
if(Tokenizer->At[0] == '*')
{
Tokenizer->At += 2;
}
}
else
{
break;
}
}
}
static token
GetToken(tokenizer *Tokenizer)
{
EatAllWhitespace(Tokenizer);
token Token = {};
Token.TextLength = 1;
Token.Text = Tokenizer->At;
char C = Tokenizer->At[0];
++Tokenizer->At;
switch(C)
{
case '0': {--Tokenizer->At; Token.Type = Token_EndOfStream;} break;
case '(': {Token.Type = Token_OpenParen;} break;
case ')': {Token.Type = Token_CloseParen;} break;
case '*': {Token.Type = Token_Asterisk;} break;
case '-': {Token.Type = Token_Minus;} break;
case '+': {Token.Type = Token_Plus;} break;
case '/': {Token.Type = Token_ForwardSlash;} break;
case '%': {Token.Type = Token_Percent;} break;
case ':': {Token.Type = Token_Colon;} break;
default:
{
if(IsNumeric(C))
{
// TODO(casey): Real number
Token.Type = Token_Number;
while(IsNumeric(Tokenizer->At[0]) ||
(Tokenizer->At[0] == '.') ||
(Tokenizer->At[0] == 'f'))
{
++Tokenizer->At;
Token.TextLength = Tokenizer->At - Token.Text;
}
}
else
{
Token.Type = Token_Unknown;
}
} break;
}
return(Token);
}
static token
PeekToken(tokenizer *Tokenizer)
{
tokenizer Tokenizer2 = *Tokenizer;
token Result = GetToken(&Tokenizer2);
return(Result);
}
inline bool
IsH(String extension)
{
bool Result = (match(extension, make_lit_string("h")) ||
match(extension, make_lit_string("hpp")) ||
match(extension, make_lit_string("hin")));
return(Result);
}
inline bool
IsCPP(String extension)
{
bool Result = (match(extension, make_lit_string("c")) ||
match(extension, make_lit_string("cpp")) ||
match(extension, make_lit_string("cin")));
return(Result);
}
inline bool
IsINL(String extension)
{
bool Result = (match(extension, make_lit_string("inl")));
return(Result);
}
inline bool
IsCode(String extension)
{
bool Result = (IsH(extension) || IsCPP(extension) || IsINL(extension));
return(Result);
}
HOOK_SIG(casey_start)
{
exec_command(app, cmdid_open_panel_vsplit);
app->change_theme(app, literal("Handmade Hero"));
app->change_font(app, literal("liberation mono"));
return(0);
}
CUSTOM_COMMAND_SIG(casey_open_in_other)
{
exec_command(app, cmdid_change_active_panel);
exec_command(app, cmdid_interactive_open);
}
CUSTOM_COMMAND_SIG(casey_clean_and_save)
{
exec_command(app, cmdid_clean_all_lines);
exec_command(app, cmdid_eol_nixify);
exec_command(app, cmdid_save);
}
CUSTOM_COMMAND_SIG(casey_newline_and_indent)
{
exec_command(app, cmdid_write_character);
exec_command(app, auto_tab_line_at_cursor);
}
CUSTOM_COMMAND_SIG(casey_open_file_other_window)
{
exec_command(app, cmdid_change_active_panel);
exec_command(app, cmdid_interactive_open);
}
CUSTOM_COMMAND_SIG(casey_switch_buffer_other_window)
{
exec_command(app, cmdid_change_active_panel);
exec_command(app, cmdid_interactive_switch_buffer);
}
internal void
DeleteAfterCommand(struct Application_Links *app, unsigned long long CommandID)
{
View_Summary view = app->get_active_view(app);
int pos2 = view.cursor.pos;
if (CommandID < cmdid_count){
exec_command(app, (Command_ID)CommandID);
}
else{
exec_command(app, (Custom_Command_Function*)CommandID);
}
app->refresh_view(app, &view);
int pos1 = view.cursor.pos;
Range range = make_range(pos1, pos2);
Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
app->buffer_replace_range(app, &buffer, range.min, range.max, 0, 0);
}
CUSTOM_COMMAND_SIG(casey_delete_token_left)
{
DeleteAfterCommand(app, (unsigned long long)seek_white_or_token_left);
}
CUSTOM_COMMAND_SIG(casey_delete_token_right)
{
DeleteAfterCommand(app, (unsigned long long)seek_white_or_token_right);
}
CUSTOM_COMMAND_SIG(casey_kill_to_end_of_line)
{
View_Summary view = app->get_active_view(app);
int pos2 = view.cursor.pos;
exec_command(app, cmdid_seek_end_of_line);
app->refresh_view(app, &view);
int pos1 = view.cursor.pos;
Range range = make_range(pos1, pos2);
if(pos1 == pos2)
{
range.max += 1;
}
Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
app->buffer_replace_range(app, &buffer, range.min, range.max, 0, 0);
}
CUSTOM_COMMAND_SIG(casey_paste_and_tab)
{
// NOTE(allen): Paste puts the mark at the beginning and the cursor at
// the end of the pasted chunk, so it is all set for cmdid_auto_tab_range
exec_command(app, cmdid_paste);
exec_command(app, cmdid_auto_tab_range);
}
CUSTOM_COMMAND_SIG(casey_seek_beginning_of_line_and_tab)
{
exec_command(app, cmdid_seek_beginning_of_line);
exec_command(app, auto_tab_line_at_cursor);
}
struct switch_to_result
{
bool Switched;
bool Loaded;
View_Summary view;
Buffer_Summary buffer;
};
inline void
SanitizeSlashes(String Value)
{
for(int At = 0;
At < Value.size;
++At)
{
if(Value.str[At] == '\\')
{
Value.str[At] = '/';
}
}
}
inline switch_to_result
SwitchToOrLoadFile(struct Application_Links *app, String FileName, bool CreateIfNotFound = false)
{
switch_to_result Result = {};
SanitizeSlashes(FileName);
View_Summary view = app->get_active_view(app);
Buffer_Summary buffer = app->get_buffer_by_name(app, FileName.str, FileName.size);
Result.view = view;
Result.buffer = buffer;
if(buffer.exists)
{
app->view_set_buffer(app, &view, buffer.buffer_id);
Result.Switched = true;
}
else
{
if(app->file_exists(app, FileName.str, FileName.size) || CreateIfNotFound)
{
push_parameter(app, par_name, expand_str(FileName));
// TODO(casey): Do I have to check for existence, or can I pass a parameter
// to interactive open to tell it to fail if the file isn't there?
exec_command(app, cmdid_interactive_open);
Result.buffer = app->get_buffer_by_name(app, FileName.str, FileName.size);
Result.Loaded = true;
Result.Switched = true;
}
}
return(Result);
}
CUSTOM_COMMAND_SIG(casey_load_todo)
{
String ToDoFileName = make_lit_string("w:/handmade/code/todo.txt");
SwitchToOrLoadFile(app, ToDoFileName, true);
}
inline String Empty() {String Result = {}; return(Result);}
CUSTOM_COMMAND_SIG(casey_load_handmade)
{
// NOTE(allen|a3.4.4): Here we get the list of files in this directory.
// Notice that we free_file_list at the end.
String dir = make_string(app->memory, 0, app->memory_size);
append(&dir, "w:/handmade/code/");
File_List list = app->get_file_list(app, dir.str, dir.size);
int dir_size = dir.size;
for (int i = 0; i < list.count; ++i)
{
File_Info *info = list.infos + i;
if (!info->folder)
{
String extension = file_extension(info->filename);
if (IsCode(extension))
{
// NOTE(allen): There's no way in the 4coder API to use relative
// paths at the moment, so everything should be full paths. Which is
// managable. Here simply set the dir string size back to where it
// was originally, so that new appends overwrite old ones.
dir.size = dir_size;
append(&dir, info->filename);
push_parameter(app, par_name, dir.str, dir.size);
if (!match(info->filename, make_lit_string("handmade.cpp"))){
push_parameter(app, par_do_in_background, 1);
}
exec_command(app, cmdid_interactive_open);
}
}
}
app->free_file_list(app, list);
strcpy(BuildDirectory, "w:/handmade/code/");
}
CUSTOM_COMMAND_SIG(casey_build_search)
{
int keep_going = 1;
int old_size;
// TODO(allen): It's fine to get memory this way for now, eventually
// we should properly suballocating from app->memory.
String dir = make_string(app->memory, 0, app->memory_size);
dir.size = app->directory_get_hot(app, dir.str, dir.memory_size);
while (keep_going)
{
old_size = dir.size;
append(&dir, "build.bat");
if (app->file_exists(app, dir.str, dir.size))
{
dir.size = old_size;
memcpy(BuildDirectory, dir.str, dir.size);
BuildDirectory[dir.size] = 0;
return;
}
dir.size = old_size;
if (app->directory_cd(app, dir.str, &dir.size, dir.memory_size, literal("..")) == 0)
{
keep_going = 0;
}
}
// TODO(casey): How do I print out that it found or didn't find something?
}
CUSTOM_COMMAND_SIG(casey_find_corresponding_file)
{
View_Summary view = app->get_active_view(app);
Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
String extension = file_extension(make_string(buffer.file_name, buffer.file_name_len));
if (extension.str)
{
char *HExtensions[] =
{
"hpp",
"hin",
"h",
};
char *CExtensions[] =
{
"c",
"cin",
"cpp",
};
int ExtensionCount = 0;
char **Extensions = 0;
if(IsH(extension))
{
ExtensionCount = ArrayCount(CExtensions);
Extensions = CExtensions;
}
else if(IsCPP(extension) || IsINL(extension))
{
ExtensionCount = ArrayCount(HExtensions);
Extensions = HExtensions;
}
int MaxExtensionLength = 3;
int Space = (int)(buffer.file_name_len + MaxExtensionLength);
String FileNameStem = make_string(buffer.file_name, (int)(extension.str - buffer.file_name), 0);
String TestFileName = make_string(app->push_memory(app, Space), 0, Space);
for(int ExtensionIndex = 0;
ExtensionCount;
++ExtensionIndex)
{
TestFileName.size = 0;
append(&TestFileName, FileNameStem);
append(&TestFileName, Extensions[ExtensionIndex]);
if(SwitchToOrLoadFile(app, TestFileName, ((ExtensionIndex + 1) == ExtensionCount)).Switched)
{
break;
}
}
}
}
CUSTOM_COMMAND_SIG(casey_save_and_make_without_asking)
{
exec_command(app, cmdid_change_active_panel);
Buffer_Summary buffer = {};
for(buffer = app->get_buffer_first(app);
buffer.exists;
app->get_buffer_next(app, &buffer))
{
push_parameter(app, par_name, buffer.file_name, buffer.file_name_len);
push_parameter(app, par_buffer_id, buffer.buffer_id);
exec_command(app, cmdid_save);
}
String dir = make_string(app->memory, 0, app->memory_size);
append(&dir, BuildDirectory);
push_parameter(app, par_flags, CLI_OverlapWithConflict);
push_parameter(app, par_name, GlobalCompilationBufferName, (int)strlen(GlobalCompilationBufferName));
push_parameter(app, par_cli_path, dir.str, dir.size);
if(append(&dir, "build"))
{
push_parameter(app, par_cli_command, dir.str, dir.size);
exec_command(app, cmdid_command_line);
ErrorParsingPosition = 0;
ErrorParsingLastJumpLine = 0;
ErrorParsingLastBufferID = 0;
exec_command(app, cmdid_change_active_panel);
}
else{
app->clear_parameters(app);
}
}
CUSTOM_COMMAND_SIG(casey_goto_previous_error)
{
// TODO(casey): Implement
// Buffer_Summary Buffer = app->get_buffer_by_name(app, GlobalCompilationBufferName, (int)strlen(GlobalCompilationBufferName));
}
CUSTOM_COMMAND_SIG(casey_goto_next_error)
{
Buffer_Summary Buffer = app->get_buffer_by_name(app, GlobalCompilationBufferName, (int)strlen(GlobalCompilationBufferName));
int Size = Buffer.size - ErrorParsingPosition;
if(Size > 0)
{
char *ParsingRegion = (char *)malloc(Size + 1);
app->buffer_read_range(app, &Buffer, ErrorParsingPosition, Buffer.size, ParsingRegion);
ParsingRegion[Size] = 0;
tokenizer Tokenizer = {ParsingRegion};
for(;;)
{
token Token = GetToken(&Tokenizer);
if(Token.Type == Token_OpenParen)
{
token LineToken = GetToken(&Tokenizer);
if(LineToken.Type == Token_Number)
{
token CloseToken = GetToken(&Tokenizer);
if(CloseToken.Type == Token_CloseParen)
{
token ColonToken = GetToken(&Tokenizer);
if(ColonToken.Type == Token_Colon)
{
// NOTE(casey): We maybe found an error!
int line_number = atoi(LineToken.Text);
char *Seek = Token.Text;
while(Seek != ParsingRegion)
{
if(IsEndOfLine(*Seek))
{
while(IsWhitespace(*Seek))
{
++Seek;
}
break;
}
--Seek;
}
String FileName = make_string(Seek, (int)(Token.Text - Seek));
switch_to_result Switch = SwitchToOrLoadFile(app, FileName, false);
if(Switch.Switched)
{
app->view_set_cursor(app, &Switch.view, seek_line_char(line_number, 0), 1);
}
if((line_number != ErrorParsingLastJumpLine) ||
(Switch.buffer.buffer_id != ErrorParsingLastBufferID))
{
ErrorParsingLastJumpLine = line_number;
ErrorParsingLastBufferID = Switch.buffer.buffer_id;
ErrorParsingPosition += (int)(ColonToken.Text - ParsingRegion);
View_Summary compilation_view =
get_first_view_with_buffer(app, Buffer.buffer_id);
if(compilation_view.exists)
{
app->view_set_cursor(app, &compilation_view, seek_pos(ErrorParsingPosition), 1);
}
break;
}
}
}
}
}
else if(Token.Type == Token_EndOfStream)
{
break;
}
}
free(ParsingRegion);
}
}
CUSTOM_COMMAND_SIG(casey_imenu)
{
// TODO(casey): Implement!
}
//
// TODO(casey): Everything below this line probably isn't possible yet
//
CUSTOM_COMMAND_SIG(casey_call_keyboard_macro)
{
// TODO(casey): Implement!
}
CUSTOM_COMMAND_SIG(casey_begin_keyboard_macro_recording)
{
// TODO(casey): Implement!
}
CUSTOM_COMMAND_SIG(casey_end_keyboard_macro_recording)
{
// TODO(casey): Implement!
}
CUSTOM_COMMAND_SIG(casey_fill_paragraph)
{
// TODO(casey): Implement!
}
enum calc_node_type
{
CalcNode_UnaryMinus,
CalcNode_Add,
CalcNode_Subtract,
CalcNode_Multiply,
CalcNode_Divide,
CalcNode_Mod,
CalcNode_Constant,
};
struct calc_node
{
calc_node_type Type;
double Value;
calc_node *Left;
calc_node *Right;
};
internal double
ExecCalcNode(calc_node *Node)
{
double Result = 0.0f;
if(Node)
{
switch(Node->Type)
{
case CalcNode_UnaryMinus: {Result = -ExecCalcNode(Node->Left);} break;
case CalcNode_Add: {Result = ExecCalcNode(Node->Left) + ExecCalcNode(Node->Right);} break;
case CalcNode_Subtract: {Result = ExecCalcNode(Node->Left) - ExecCalcNode(Node->Right);} break;
case CalcNode_Multiply: {Result = ExecCalcNode(Node->Left) * ExecCalcNode(Node->Right);} break;
case CalcNode_Divide: {/*TODO(casey): Guard 0*/Result = ExecCalcNode(Node->Left) / ExecCalcNode(Node->Right);} break;
case CalcNode_Mod: {/*TODO(casey): Guard 0*/Result = fmod(ExecCalcNode(Node->Left), ExecCalcNode(Node->Right));} break;
case CalcNode_Constant: {Result = Node->Value;} break;
default: {Assert(!"AHHHHH!");}
}
}
return(Result);
}
internal void
FreeCalcNode(calc_node *Node)
{
if(Node)
{
FreeCalcNode(Node->Left);
FreeCalcNode(Node->Right);
free(Node);
}
}
internal calc_node *
AddNode(calc_node_type Type, calc_node *Left = 0, calc_node *Right = 0)
{
calc_node *Node = (calc_node *)malloc(sizeof(calc_node));
Node->Type = Type;
Node->Value = 0;
Node->Left = Left;
Node->Right = Right;
return(Node);
}
internal calc_node *
ParseNumber(tokenizer *Tokenizer)
{
calc_node *Result = AddNode(CalcNode_Constant);
token Token = GetToken(Tokenizer);
Result->Value = atof(Token.Text);
return(Result);
}
internal calc_node *
ParseConstant(tokenizer *Tokenizer)
{
calc_node *Result = 0;
token Token = PeekToken(Tokenizer);
if(Token.Type == Token_Minus)
{
Token = GetToken(Tokenizer);
Result = AddNode(CalcNode_UnaryMinus);
Result->Left = ParseNumber(Tokenizer);
}
else
{
Result = ParseNumber(Tokenizer);
}
return(Result);
}
internal calc_node *
ParseMultiplyExpression(tokenizer *Tokenizer)
{
calc_node *Result = 0;
token Token = PeekToken(Tokenizer);
if((Token.Type == Token_Minus) ||
(Token.Type == Token_Number))
{
Result = ParseConstant(Tokenizer);
token Token = PeekToken(Tokenizer);
if(Token.Type == Token_ForwardSlash)
{
GetToken(Tokenizer);
Result = AddNode(CalcNode_Divide, Result, ParseNumber(Tokenizer));
}
else if(Token.Type == Token_Asterisk)
{
GetToken(Tokenizer);
Result = AddNode(CalcNode_Multiply, Result, ParseNumber(Tokenizer));
}
}
return(Result);
}
internal calc_node *
ParseAddExpression(tokenizer *Tokenizer)
{
calc_node *Result = 0;
token Token = PeekToken(Tokenizer);
if((Token.Type == Token_Minus) ||
(Token.Type == Token_Number))
{
Result = ParseMultiplyExpression(Tokenizer);
token Token = PeekToken(Tokenizer);
if(Token.Type == Token_Plus)
{
GetToken(Tokenizer);
Result = AddNode(CalcNode_Add, Result, ParseMultiplyExpression(Tokenizer));
}
else if(Token.Type == Token_Minus)
{
GetToken(Tokenizer);
Result = AddNode(CalcNode_Subtract, Result, ParseMultiplyExpression(Tokenizer));
}
}
return(Result);
}
internal calc_node *
ParseCalc(tokenizer *Tokenizer)
{
calc_node *Node = ParseAddExpression(Tokenizer);
return(Node);
}
CUSTOM_COMMAND_SIG(casey_quick_calc)
{
View_Summary view = app->get_active_view(app);
Range range = get_range(&view);
size_t Size = range.max - range.min;
char *Stuff = (char *)malloc(Size + 1);
Stuff[Size] = 0;
Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
app->buffer_read_range(app, &buffer, range.min, range.max, Stuff);
tokenizer Tokenizer = {Stuff};
calc_node *CalcTree = ParseCalc(&Tokenizer);
double ComputedValue = ExecCalcNode(CalcTree);
FreeCalcNode(CalcTree);
char ResultBuffer[256];
int ResultSize = sprintf(ResultBuffer, "%f", ComputedValue);
app->buffer_replace_range(app, &buffer, range.min, range.max, ResultBuffer, ResultSize);
free(Stuff);
}
internal void
OpenProject(Application_Links *app, char *ProjectFileName)
{
FILE *ProjectFile = fopen(ProjectFileName, "r");
if(ProjectFile)
{
fgets(BuildDirectory, sizeof(BuildDirectory) - 1, ProjectFile);
size_t BuildDirSize = strlen(BuildDirectory);
if((BuildDirSize) && (BuildDirectory[BuildDirSize - 1] == '\n'))
{
--BuildDirSize;
}
if((BuildDirSize) && (BuildDirectory[BuildDirSize - 1] != '/'))
{
BuildDirectory[BuildDirSize++] = '/';
BuildDirectory[BuildDirSize] = 0;
}
char FileDirectoryName[4096];
while(fgets(FileDirectoryName, sizeof(FileDirectoryName) - 1, ProjectFile))
{
// NOTE(allen|a3.4.4): Here we get the list of files in this directory.
// Notice that we free_file_list at the end.
String dir = make_string(app->memory, 0, app->memory_size);
append(&dir, FileDirectoryName);
if(dir.size && dir.str[dir.size] == '\n')
{
--dir.size;
}
if(dir.size && dir.str[dir.size] != '/')
{
dir.str[dir.size] = '/';
++dir.size;
}
File_List list = app->get_file_list(app, dir.str, dir.size);
int dir_size = dir.size;
for (int i = 0; i < list.count; ++i)
{
File_Info *info = list.infos + i;
if (!info->folder)
{
String extension = file_extension(info->filename);
if (IsCode(extension))
{
// NOTE(allen): There's no way in the 4coder API to use relative
// paths at the moment, so everything should be full paths. Which is
// managable. Here simply set the dir string size back to where it
// was originally, so that new appends overwrite old ones.
dir.size = dir_size;
append(&dir, info->filename);
push_parameter(app, par_name, dir.str, dir.size);
push_parameter(app, par_do_in_background, 1);
exec_command(app, cmdid_interactive_open);
}
}
}
app->free_file_list(app, list);
}
fclose(ProjectFile);
}
}
CUSTOM_COMMAND_SIG(casey_execute_arbitrary_command)
{
Query_Bar bar;
char space[1024], more_space[1024];
bar.prompt = make_lit_string("Command: ");
bar.string = make_fixed_width_string(space);
if (!query_user_string(app, &bar)) return;
app->end_query_bar(app, &bar, 0);
if(match(bar.string, make_lit_string("project")))
{
// exec_command(app, open_all_code);
}
else if(match(bar.string, make_lit_string("open menu")))
{
exec_command(app, cmdid_open_menu);
}
else
{
bar.prompt = make_fixed_width_string(more_space);
append(&bar.prompt, make_lit_string("Unrecognized: "));
append(&bar.prompt, bar.string);
bar.string.size = 0;
app->start_query_bar(app, &bar, 0);
app->get_user_input(app, EventOnAnyKey | EventOnButton, 0);
}
}
internal void
UpdateModalIndicator(Application_Links *app)
{
Theme_Color normal_colors[] = {
{Stag_Cursor, 0x40FF40},
{Stag_At_Cursor, 0x161616},
{Stag_Mark, 0x808080},
{Stag_Margin, 0x262626},
{Stag_Margin_Hover, 0x333333},
{Stag_Margin_Active, 0x404040},
{Stag_Bar, 0xCACACA}
};
Theme_Color edit_colors[] = {
{Stag_Cursor, 0xFF0000},
{Stag_At_Cursor, 0x00FFFF},
{Stag_Mark, 0xFF6F1A},
{Stag_Margin, 0x33170B},
{Stag_Margin_Hover, 0x49200F},
{Stag_Margin_Active, 0x934420},
{Stag_Bar, 0x934420}
};
if (GlobalEditMode){
app->set_theme_colors(app, edit_colors, ArrayCount(edit_colors));
}
else{
app->set_theme_colors(app, normal_colors, ArrayCount(normal_colors));
}
#if UseHack4Coder
RECT Rect;
GetClientRect(GlobalModalIndicator, &Rect);
InvalidateRect(GlobalModalIndicator, &Rect, FALSE);
#endif
}
CUSTOM_COMMAND_SIG(begin_free_typing)
{
GlobalEditMode = false;
UpdateModalIndicator(app);
}
CUSTOM_COMMAND_SIG(end_free_typing)
{
GlobalEditMode = true;
UpdateModalIndicator(app);
}
#define DEFINE_FULL_BIMODAL_KEY(binding_name,edit_code,normal_code) \
CUSTOM_COMMAND_SIG(binding_name) \
{ \
if(GlobalEditMode) \
{ \
edit_code; \
} \
else \
{ \
normal_code; \
} \
}
#define DEFINE_BIMODAL_KEY(binding_name,edit_code,normal_code) DEFINE_FULL_BIMODAL_KEY(binding_name,exec_command(app,edit_code),exec_command(app,normal_code))
#define DEFINE_MODAL_KEY(binding_name,edit_code) DEFINE_BIMODAL_KEY(binding_name,edit_code,cmdid_write_character)
// cmdid_paste_next ?
// cmdid_timeline_scrub ?
// cmdid_history_backward,
// cmdid_history_forward,
// cmdid_toggle_line_wrap,
// cmdid_close_minor_view,
DEFINE_MODAL_KEY(modal_space, cmdid_set_mark);
DEFINE_MODAL_KEY(modal_back_slash, casey_clean_and_save);
DEFINE_MODAL_KEY(modal_single_quote, casey_call_keyboard_macro);
DEFINE_MODAL_KEY(modal_comma, casey_goto_previous_error);
DEFINE_MODAL_KEY(modal_period, casey_fill_paragraph);
DEFINE_MODAL_KEY(modal_forward_slash, cmdid_change_active_panel);
DEFINE_MODAL_KEY(modal_semicolon, cmdid_cursor_mark_swap); // TODO(casey): Maybe cmdid_history_backward?
DEFINE_MODAL_KEY(modal_open_bracket, casey_begin_keyboard_macro_recording);
DEFINE_MODAL_KEY(modal_close_bracket, casey_end_keyboard_macro_recording);
DEFINE_MODAL_KEY(modal_a, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_b, cmdid_interactive_switch_buffer);
DEFINE_MODAL_KEY(modal_c, casey_find_corresponding_file);
DEFINE_MODAL_KEY(modal_d, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_e, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_f, casey_paste_and_tab);
DEFINE_MODAL_KEY(modal_g, goto_line);
DEFINE_MODAL_KEY(modal_h, cmdid_auto_tab_range);
DEFINE_MODAL_KEY(modal_i, cmdid_redo);
DEFINE_MODAL_KEY(modal_j, casey_imenu);
DEFINE_MODAL_KEY(modal_k, casey_kill_to_end_of_line);
DEFINE_MODAL_KEY(modal_l, replace_in_range);
DEFINE_MODAL_KEY(modal_m, casey_save_and_make_without_asking);
DEFINE_MODAL_KEY(modal_n, casey_goto_next_error);
DEFINE_MODAL_KEY(modal_o, query_replace);
DEFINE_MODAL_KEY(modal_p, casey_quick_calc);
DEFINE_MODAL_KEY(modal_q, cmdid_copy);
DEFINE_MODAL_KEY(modal_r, reverse_search); // NOTE(allen): I've modified my default search so you can use it now.
DEFINE_MODAL_KEY(modal_s, search);
DEFINE_MODAL_KEY(modal_t, casey_load_todo);
DEFINE_MODAL_KEY(modal_u, cmdid_undo);
DEFINE_MODAL_KEY(modal_v, casey_switch_buffer_other_window);
DEFINE_MODAL_KEY(modal_w, cmdid_cut);
DEFINE_MODAL_KEY(modal_x, casey_open_file_other_window);
DEFINE_MODAL_KEY(modal_y, auto_tab_line_at_cursor);
DEFINE_MODAL_KEY(modal_z, cmdid_interactive_open);
DEFINE_MODAL_KEY(modal_1, casey_build_search); // TODO(casey): Shouldn't need to bind a key for this?
DEFINE_MODAL_KEY(modal_2, casey_load_handmade); // TODO(casey): Shouldn't need to bind a key for this?
DEFINE_MODAL_KEY(modal_3, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_4, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_5, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_6, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_7, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_8, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_9, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_0, cmdid_kill_buffer);
DEFINE_MODAL_KEY(modal_minus, cmdid_write_character); // TODO(casey): Available
DEFINE_MODAL_KEY(modal_equals, casey_execute_arbitrary_command);
DEFINE_BIMODAL_KEY(modal_backspace, casey_delete_token_left, cmdid_backspace);
DEFINE_BIMODAL_KEY(modal_up, cmdid_move_up, cmdid_move_up);
DEFINE_BIMODAL_KEY(modal_down, cmdid_move_down, cmdid_move_down);
DEFINE_BIMODAL_KEY(modal_left, seek_white_or_token_left, cmdid_move_left);
DEFINE_BIMODAL_KEY(modal_right, seek_white_or_token_right, cmdid_move_right);
DEFINE_BIMODAL_KEY(modal_delete, casey_delete_token_right, cmdid_delete);
DEFINE_BIMODAL_KEY(modal_home, cmdid_seek_beginning_of_line, casey_seek_beginning_of_line_and_tab);
DEFINE_BIMODAL_KEY(modal_end, cmdid_seek_end_of_line, cmdid_seek_end_of_line);
DEFINE_BIMODAL_KEY(modal_page_up, cmdid_page_up, cmdid_seek_whitespace_up);
DEFINE_BIMODAL_KEY(modal_page_down, cmdid_page_down, cmdid_seek_whitespace_down);
DEFINE_BIMODAL_KEY(modal_tab, cmdid_word_complete, cmdid_word_complete);
HOOK_SIG(casey_file_settings)
{
// NOTE(allen|a4): As of alpha 4 hooks can have parameters which are
// received through functions like this app->get_parameter_buffer.
// This is different from the past where this hook got a buffer
// from app->get_active_buffer.
Buffer_Summary buffer = app->get_parameter_buffer(app, 0);
int treat_as_code = 0;
int treat_as_project = 0;
if (buffer.file_name && buffer.size < (16 << 20))
{
String ext = file_extension(make_string(buffer.file_name, buffer.file_name_len));
treat_as_code = IsCode(ext);
treat_as_project = match(ext, make_lit_string("prj"));
}
push_parameter(app, par_lex_as_cpp_file, treat_as_code);
push_parameter(app, par_wrap_lines, !treat_as_code);
push_parameter(app, par_key_mapid, (treat_as_code)?((int)my_code_map):((int)mapid_file));
exec_command(app, cmdid_set_settings);
if(treat_as_project)
{
OpenProject(app, buffer.file_name);
exec_command(app, cmdid_kill_buffer);
}
return(0);
}
// NOTE(allen): This was a bit of fun so I'll leave it in, for anyone would like to hack 4coder again.
#if UseHack4Coder
internal void
hack_place_modal_indicator(void)
{
static RECT LastParentRect;
HWND ParentWindow = GetParent(GlobalModalIndicator);
RECT ParentRect;
GetClientRect(ParentWindow, &ParentRect);
if((LastParentRect.bottom != ParentRect.bottom) ||
(LastParentRect.right != ParentRect.right))
{
SetWindowPos(GlobalModalIndicator, 0,
(ParentRect.left+ParentRect.right)/2 - 10,
ParentRect.top,
10,
ParentRect.bottom - ParentRect.top,
SWP_NOOWNERZORDER|SWP_NOACTIVATE);
}
LastParentRect = ParentRect;
}
internal LRESULT CALLBACK
main_window_intercept(HWND Window,
UINT Message,
WPARAM WParam,
LPARAM LParam)
{
LRESULT Result = CallWindowProc(global_old_4coder_winproc, Window, Message, WParam, LParam);
if(Message == WM_SIZE)
{
hack_place_modal_indicator();
}
return(Result);
}
internal LRESULT CALLBACK
modal_indicator_window_callback(HWND Window,
UINT Message,
WPARAM WParam,
LPARAM LParam)
{
LRESULT Result = 0;
switch(Message)
{
case WM_PAINT:
{
PAINTSTRUCT Paint;
HDC DeviceContext = BeginPaint(Window, &Paint);
FillRect(DeviceContext, &Paint.rcPaint, GlobalEditMode ? GlobalEditModeBrush : GlobalNormalModeBrush);
EndPaint(Window, &Paint);
} break;
default:
{
Result = DefWindowProcA(Window, Message, WParam, LParam);
} break;
}
return(Result);
}
internal void
hack_4coder(void)
{
HWND Window = FindWindow("4coder-win32-wndclass", "4coder-window: " VERSION);
ShowWindow(Window, SW_MAXIMIZE);
WNDCLASSA WindowClass = {};
HINSTANCE Instance = GetModuleHandle(0);
WindowClass.style = CS_HREDRAW|CS_VREDRAW;
WindowClass.lpfnWndProc = modal_indicator_window_callback;
WindowClass.hInstance = Instance;
WindowClass.hCursor = LoadCursor(0, IDC_ARROW);
WindowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
WindowClass.lpszClassName = "4coderhackmodaldisplay";
if(RegisterClassA(&WindowClass))
{
GlobalModalIndicator =
CreateWindowEx(
0,
WindowClass.lpszClassName,
0,
WS_VISIBLE|WS_CHILD,
0, 0, 10, 10,
Window,
0,
Instance,
0);
GlobalEditModeBrush = CreateSolidBrush(RGB(100, 20, 20));
GlobalNormalModeBrush = CreateSolidBrush(RGB(20, 100, 20));
hack_place_modal_indicator();
}
global_old_4coder_winproc = (WNDPROC)SetWindowLongPtr(Window, GWLP_WNDPROC, (LONG_PTR)main_window_intercept);
}
#endif
void
casey_get_bindings(Bind_Helper *context)
{
set_hook(context, hook_start, casey_start);
set_hook(context, hook_open_file, casey_file_settings);
set_scroll_rule(context, smooth_scroll_rule);
#if UseHack4Coder
hack_4coder();
#endif
begin_map(context, mapid_global);
{
bind(context, 'z', MDFR_NONE, cmdid_interactive_open);
bind(context, 'x', MDFR_NONE, casey_open_in_other);
bind(context, 't', MDFR_NONE, casey_load_todo);
bind(context, '/', MDFR_NONE, cmdid_change_active_panel);
bind(context, 'b', MDFR_NONE, cmdid_interactive_switch_buffer);
bind(context, '2', MDFR_NONE, casey_load_handmade);
bind(context, '4', MDFR_NONE, cmdid_open_color_tweaker);
bind(context, key_page_up, MDFR_NONE, search);
bind(context, key_page_down, MDFR_NONE, reverse_search);
// NOTE(allen): I added this here myself, I believe this is what you want.
bind(context, 'm', MDFR_NONE, casey_save_and_make_without_asking);
}
end_map(context);
begin_map(context, mapid_file);
bind_vanilla_keys(context, cmdid_write_character);
bind(context, key_insert, MDFR_NONE, begin_free_typing);
bind(context, '`', MDFR_NONE, begin_free_typing);
bind(context, key_esc, MDFR_NONE, end_free_typing);
bind(context, '\n', MDFR_NONE, casey_newline_and_indent);
bind(context, '\n', MDFR_SHIFT, casey_newline_and_indent);
bind(context, 't', MDFR_CTRL, cmdid_timeline_scrub);
// NOTE(casey): Modal keys come here.
bind(context, ' ', MDFR_NONE, modal_space);
bind(context, ' ', MDFR_SHIFT, modal_space);
bind(context, '\\', MDFR_NONE, modal_back_slash);
bind(context, '\'', MDFR_NONE, modal_single_quote);
bind(context, ',', MDFR_NONE, modal_comma);
bind(context, '.', MDFR_NONE, modal_period);
bind(context, '/', MDFR_NONE, modal_forward_slash);
bind(context, ';', MDFR_NONE, modal_semicolon);
bind(context, '[', MDFR_NONE, modal_open_bracket);
bind(context, ']', MDFR_NONE, modal_close_bracket);
bind(context, 'a', MDFR_NONE, modal_a);
bind(context, 'b', MDFR_NONE, modal_b);
bind(context, 'c', MDFR_NONE, modal_c);
bind(context, 'd', MDFR_NONE, modal_d);
bind(context, 'e', MDFR_NONE, modal_e);
bind(context, 'f', MDFR_NONE, modal_f);
bind(context, 'g', MDFR_NONE, modal_g);
bind(context, 'h', MDFR_NONE, modal_h);
bind(context, 'i', MDFR_NONE, modal_i);
bind(context, 'j', MDFR_NONE, modal_j);
bind(context, 'k', MDFR_NONE, modal_k);
bind(context, 'l', MDFR_NONE, modal_l);
bind(context, 'm', MDFR_NONE, modal_m);
bind(context, 'n', MDFR_NONE, modal_n);
bind(context, 'o', MDFR_NONE, modal_o);
bind(context, 'p', MDFR_NONE, modal_p);
bind(context, 'q', MDFR_NONE, modal_q);
bind(context, 'r', MDFR_NONE, modal_r);
bind(context, 's', MDFR_NONE, modal_s);
bind(context, 't', MDFR_NONE, modal_t);
bind(context, 'u', MDFR_NONE, modal_u);
bind(context, 'v', MDFR_NONE, modal_v);
bind(context, 'w', MDFR_NONE, modal_w);
bind(context, 'x', MDFR_NONE, modal_x);
bind(context, 'y', MDFR_NONE, modal_y);
bind(context, 'z', MDFR_NONE, modal_z);
bind(context, '1', MDFR_NONE, modal_1);
bind(context, '2', MDFR_NONE, modal_2);
bind(context, '3', MDFR_NONE, modal_3);
bind(context, '4', MDFR_NONE, modal_4);
bind(context, '5', MDFR_NONE, modal_5);
bind(context, '6', MDFR_NONE, modal_6);
bind(context, '7', MDFR_NONE, modal_7);
bind(context, '8', MDFR_NONE, modal_8);
bind(context, '9', MDFR_NONE, modal_9);
bind(context, '0', MDFR_NONE, modal_0);
bind(context, '-', MDFR_NONE, modal_minus);
bind(context, '=', MDFR_NONE, modal_equals);
bind(context, key_back, MDFR_NONE, modal_backspace);
bind(context, key_up, MDFR_NONE, modal_up);
bind(context, key_down, MDFR_NONE, modal_down);
bind(context, key_left, MDFR_NONE, modal_left);
bind(context, key_right, MDFR_NONE, modal_right);
bind(context, key_del, MDFR_NONE, modal_delete);
bind(context, key_home, MDFR_NONE, modal_home);
bind(context, key_end, MDFR_NONE, modal_end);
bind(context, key_page_up, MDFR_NONE, modal_page_up);
bind(context, key_page_down, MDFR_NONE, modal_page_down);
bind(context, '\t', MDFR_NONE, modal_tab);
// bind(context, key_esc, MDFR_NONE, modal_escape);
end_map(context);
}