Jim ↗

coding as self-care

(make your codebase a homebase)


a simple, non-fragile solution for configuring vim and similar programs

around the n-th time i got a new laptop and had to manually set up my vim, i realized that i was probably doing something wrong

so, like professor muratorio before me, i wrote the usage code first

THE USAGE CODE (what i really want, in my heart of hearts): want to just run a single script called vim_setup.sh or whatever, and have it set up my vim (like a build_and_run.bat, but for setting up vim) so that i don't have to remind myself how vim's directory structure works or where i found that one plugin or AHHHHHHHH

this script and all its dependencies should live in my github repo, so that when i get a new computer i can just 1) clone the repo, 2) run the script, and 3) get back to coding

while this script might sound like some very magical, complicated thing that needs to curl and git submodule and pathogen infect VimPlug puppet ???, this is incorrect

the reason it's incorrect is that on my laptop, "configure vim" literally just means "set the contents of the file ~/.vimrc and the folder ~/.vim/"

that's it.

period.

the end.

so, if my git repo contains my desired .vimrc and .vim/

(like, contains the literal contents of that file and that folder. NOT contains some other script or submodule hook or recipe or AHHHHHHH...

...but no, i really mean the repo has .vimrc and .vim/ and that file and that folder contain THE LITERAL CODE that vim will actually read)

then all vim_setup.sh needs to do is copy them into my computer's home directory

hilariously (if you find this kind of thing funny), this extends immediately to multiple profiles

i often partner code with one of my students, who has his own vim setup (basically mine, but with different colors, plus the ability to read pdf's or something)

and both of our setups live in our shared repo, along with our own vim_setup.sh's (technically, i think his is mine with a command-line argument, but the principle is the same)

to make switching back and forth really easy, we can just add remap <leader>P to run my setup script and <leader>N to run his

sidenote: i don't know what problem pathogen or VimPlug are supposed to be solving. when i find a vim plugin i want, i just manually copy its files into my repo's copy of .vim/; i have no interest in updating any of my plugins ever


i've tried a lot of ways of keeping track of my TODO's and so far only one has even sort of worked

first, here's some approaches that didn't work for me
a) scattering the TODO's throughout the code -- they accumulate and become meaningless and overwhelming; plus they distract from explanatory comments
b) curating the TODO's in GitHub issues -- way too much friction; requires me to be connected to the internet; end up piling up and becoming meaningless
c) no TODO list (follow your heart 🥺) -- unfortunately, my heart is a dumb dumb (boring ideas never gets done, fun/stupid ideas always get started but rarely get finished)

and here's an approach that did work for me
- i have one file called todo.x and another file called done.x
- if i have an idea for something to do, i add it to todo.x, for example...

// TODO make a less exciting test_eso that doesn't require transforms, etc. (just draw the primitives yo)
- if i end up doing it, i replace TODO with DONE and move it to done.x

that's pretty much it, just a big 'ol list 🤷

one last thing: since marking the TODO as done and moving it over by hand is frictious, i wrote a Vim mapping that marks the TODO as DONE and moves it over to done.txt also also pushes to github the body of the TODO as the commit message

noremap <silent> <leader>D 0fTRDONE<esc>dd:vsplit done.x<CR>ggPfDf ly$:w<cr><c-w>c:!./poosh.bat "<c-r>""<cr>

it's just that easy :)


writing stuff from scratch is delightfully infective

i started using Vjekoslav Krajačić-style sliceable strings a while back

STRUCT {
 U8 *_; // NOTE i usually name this kind of thing `_`; you could also use `data` or `v` or whatever
 U64 length;
} String;

they're great. you can slice in O(1) and you don't need to null-terminate them

// NOTE analogous to Python's string[start:one_past_end]
String _string_slice(String string, U32 start, U32 one_past_end) {
 // NOTE we allow empty slices
 ASSERT(start <= string.length);
 ASSERT(start <= one_past_end);
 ASSERT(one_past_end <= string.length);

 String result;
 result._ = &string._[start];
 result.length = (one_past_end - start);
 return(result);
}

// NOTE analogous to Python's string[start:]
String string_slice(String string, U32 start) {
 return _string_slice(string, start, string.length);
}

the only downside i experienced using these String's was that the standard printf didn't know about them -- if you pass a non-null terminated one of these String's with %s, printf will just goes marching merrily off into the distance, looking for a 0 that may never come

i saw two ways around this (like always, there are probably more options than just these):
A) remember to add null-terminators whenever i wanted to print of sprint, or
B) bring your own printf

conventional wisdom has that option (B) is Bad and Hard, but conventional wisdom is basically always wrong, so let's go with option (B)

like many Hard things, it ended up taking about 5 minutes:
1) borrow Marco Paland's tiny printf from GitHub, and
2) add ~20 mostly copy-and-pasted lines to it

case 'S' : { // our custom String struct
 String string = va_arg(va, String);
 const char *p = string._;
 unsigned int l = (unsigned int) string.length;
 if (l) {
  // NOTE this block (mostly) copy and pasted from case 's' i did need to
  // change / add one important thing or else it did bad things beware copypasta
  ...
 }
}

that's it.

and now we can do stuff like this...

printf("[gui_tracked_box] box->key_string clash: %S\n", box->key_string);

and since snprintf and vsnprintf also know about our String's now, we can use %S format specifiers in other functions, like...

GUIBox *box_field_buffer = gui_tracked_box(
 "buffer_%S%S%S",
 string_prefix,
 command->string,
 field->name
);

:D

sidenote: while i was in the neighborhood, i made a small change to _internal_vsnprintf(...) -- i prefer my functions to just crash the program when they fail, rather than returning some number i'll forget to check for and then spend hours trying to debug the downstream effects of

int _internal_vsnprintf(...) {
 ...

 // // NOTE (copied from the printf docs)                                                                        
 // \return The number of characters that COULD have been written into the buffer, not counting the terminating  
 //         null character. A value equal or larger than count indicates truncation. Only when the returned value
 //         is non-negative and less than count, the result has been completely written.                         

 B32 success = (1
  && (result >= 0)
  && ((size_t) result < maxlen)
 );
 if (!success) {
  *((volatile S32 *) 0) = 0; // (crash)
 }

 return(result);
}

i like being able to easily delete lines and swap lines without having to think about C's syntax; one useful family of tricks is to avoid syntactic "special cases" -- basically, the first and last of something should be styled just like the things in the middle

here's how i initialize structs

{
 a,
 b,
 c, // <- i put a comma here (NOTE trailing comma in struct-initialization is valid C)
}

here's how i write an else-if ladder

if (0) { // <- i put an `if (0)` here (NOTE 0 is false)
} else if (...) {
 ...
} else if (...) {
 ...
} else if (...) {
 ...
}

here's how i write an AND chain

(1 // <- i put a `1` here (NOTE 1 is true)
 && ...
 && ...
 && ...
)

here's an example snippet from Conversation's source

B32 is_prinicipal_frame = (1
 && R32_is_zero(part_projection->signed_distance_from_unprojected_origin_to_feature_plane) 
 && geom3d_vector_has_unit_length(part_projection->normal) 
 && R32_are_equal(1.0F, vec3_max(part_projection->normal))
);
if (0
 || (!part_projection->is_active)
 || (!is_prinicipal_frame)
) {
 ...
}

and here's another

Void gui2_separator(
 Axis split_axis,
 String prev_sibling_string,
 String next_sibling_string
) {
 GUIBox *separator = gui_tracked_box(
  "%S_separator_%S",
  prev_sibling_string,
  next_sibling_string
 );
 separator->flags = (0
  | GUIBoxFlagRenderFill
  | GUIBoxFlagLeftClickable
  | GUIBoxFlagIsASeparator
 );
 ...
}

it's a shame HolyC didn't catch on, because i think it allowed you to include a trailing comma in function calls and that would have been siiick