DREADWARE

Milestone 2 - Gap Buffers


Text Buffers

At the conclusion of Milestone 1, I could type text in a line and have it show up in the window, and could even backspace from the end of the string. Next I want to add a cursor that can move left and right with the ability to insert and delete text anywhere in the string.

Something that came up in my research of text editors was the concept of a gap buffer. A gap buffer is a segment of memory, representing a string, that is subdivided into three regions: the string that appears before cursor, a buffer of unused space, and the remainder of the string that comes after the cursor. Partitioning the memory this way allows a user to type, inserting characters at the cursor (within the gap region) without having to move memory on every keystroke.

Without any references, this is the gap buffer scheme I started with to give me a predictable, fixed-size buffer:

// constants
#define GAP_BUFFER_DATA_LENGTH	MB(2)

// types
typedef struct {
	i8  data[GAP_BUFFER_DATA_LENGTH];
	u64 gap_start;
	u64 gap_end;
} gap_buffer;

The obvious constraint here is that I am enforcing a limit of 2MB on each buffer, which might be a problem down the road. However, I'm taking a very iterative approach so I will handle dynamic/block allocations at a later time. I am doing this to try to avoid over-designing the system before I've even used it.

"Always write the usage code first." - Unknown wise man

Something I picked up along the way was the idea that the usage code should be written before the code itself. This was counterintuitive to me when I first heard it since many IDEs, and certainly the compiler, will complain if you are using code that doesn't exist yet. The red squiggles in Visual Studio certainly guide a developer to write a function first and use it second, but this can lead to bad design due to incorrect assumptions. If the usage code is written first, the API generally becomes much more clear. Instead of trying to imagine all of the different ways a function could be possibly be used in a system that doesn't exist, it makes much more sense to write the usages of the function in the system where they are needed, and then design the API based on what data is present and needed at the time of usage. This generally makes API design simple and concise.

Here are the functions I added to get the gap buffer working:

// functions
static void
gb_reset(gap_buffer* gb) {
	gb->gap_start = 0;
	gb->gap_end = GAP_BUFFER_DATA_LENGTH;
}

static u64
gb_text_length(gap_buffer* gb) {
	return GAP_BUFFER_DATA_LENGTH - (gb->gap_end - gb->gap_start);
}

static void
gb_move_gap_to_cursor(gap_buffer* gb, u64 cursor) {
	// move gap left
	if (cursor < gb->gap_start) {
		u64 len = gb->gap_start - cursor;
		mem_move(gb->data + cursor, gb->data + gb->gap_end - len, len);
		gb->gap_end   -= len;
		gb->gap_start -= len;
	}
	// move gap right
	else if (cursor > gb->gap_start) {
		u64 len = cursor - gb->gap_start;
		mem_move(gb->data + gb->gap_end, gb->data + gb->gap_start, len);
		gb->gap_start += len;
		gb->gap_end   += len;
	}
}

static void
gb_insert(gap_buffer* gb, u64 cursor, char c) {
	gb_move_gap_to_cursor(gb, cursor);
	if (gb->gap_start == gb->gap_end) {
		return;
	}
	gb->data[gb->gap_start++] = c;
}

static void
gb_backspace(gap_buffer* gb, u64 cursor) {
	if (cursor == 0) {
		return;
	}
	gb_move_gap_to_cursor(gb, cursor);
	gb->gap_start--;
}

static void
gb_delete(gap_buffer* gb, u64 cursor) {
	u64 length = gb_text_length(gb);
	if (length <= 0 || cursor >= length) {
		return;
	}
	gb_move_gap_to_cursor(gb, cursor);
	gb->gap_end++;
}

Most of those functions are pretty straight forward, leveraging gb_move_gap_to_cursor to manage the gap as a user types. It takes an offset that I am calling "cursor" as the point of future insertion. That means I will copy all of the memory within a span to the left or right of the buffer, based on the the cursor's position relative to the existing gap. The cursor does not know about the gap, so it's an offset of characters within the string.

To connect the gap buffer to keyboard events, I simply call the functions within my WndProc and replace the old instances of insert_character:

case WM_KEYDOWN: {
	data = (ded_data*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
	switch (wParam) {
		case VK_LEFT: {
			if (data->cursor > 0) {
						data->cursor--;
			}
		}
		break;
		
		case VK_RIGHT: {
			if (data->cursor < gb_text_length(&data->gb)) {
				data->cursor++;
			}
		}
		break;

		case VK_DELETE: {
			gb_delete(&data->gb, data->cursor);
		}
		break;

		case VK_BACK: {
			gb_backspace(&data->gb, data->cursor);
			if (data->cursor > 0) {
				data->cursor--;
			}
		}
		break;

		case VK_ESCAPE: {
			PostMessage(hwnd, WM_CLOSE, 0, 0);
		}
		break;

	}
	InvalidateRect(hwnd, NULL, TRUE);
}
return 0;

case WM_CHAR: {
	data = (ded_data*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
	switch (wParam) {
		case VK_TAB:{
			gb_insert(&data->gb, data->cursor, '\t');
			data->cursor++;
		}
		break;

		default: {
			if (wParam >= 32 && wParam <= 126) {
				gb_insert(&data->gb, data->cursor, (char)wParam);
				data->cursor++;
			}
		}
	}
	InvalidateRect(hwnd, NULL, TRUE);
}
break;

Printing

There is, of course, a lot of room for optimizations here, but I don't think most of this code is going to end up staying around very long. This became clear as I started writing the print function for the gap buffer. Here is what I started with:

static i32
print_token(HDC hdc, char* s, u64 len, i32 x, i32 y) {
	// TODO: determine token type and select colors
	SetBkColor(hdc, COLOR_BACK);
	SetTextColor(hdc, COLOR_TEXT);

	SIZE size;
	if (*s == '\t') {
		TextOutA(hdc, x, y, "    ", 4);
		GetTextExtentPoint32A(hdc, "    ", 4, &size);
	}
	else {
		TextOutA(hdc, x, y, s, len);
		GetTextExtentPoint32A(hdc, s, len, &size);
	}
	
	return size.cx;
}

static void
gb_draw(HDC hdc, gap_buffer* gb, u64 cursor) {
	i32 x = 0;
	i32 y = 0;

	char token[255];
	u64  token_length = 0;
	for (u64 i = 0; i < GAP_BUFFER_DATA_LENGTH; i++) {
		if (i == gb->gap_start) {
			i = gb->gap_end - 1;
			continue;
		}
		switch (gb->data[i]) {
			case ' ' :
			case '\t': {
				if (token_length > 0) {
					x += print_token(hdc, token, token_length, x, y);
				}
				token_length = 0;
				x += print_token(hdc, gb->data + i, 1, x, y);
			}
			break;

			// TODO: handle /r/n
			// TODO: increment Y
			case '\r':
			case '\n': {
				if (token_length > 0) {
					x += print_token(hdc, token, token_length, x, y);
				}
				token_length = 0;
			}
			break;

			default: {
				token[token_length++] = gb->data[i];
			}
		}
	
		
	}

	if (token_length > 0) {
		print_token(hdc, token, token_length, x, y);
	}
}

At first, I was just printing the entire buffer (minus the gap), but I wanted to make sure I have the ability to color different tokens as most editors do. To do that I tried to divide the string up as I went where a token was defined based on surrounding whitespace. When I added tabs I noticed that they would not print as expected. It seems TextOutA does not support tabbed printing, so I needed to add that as a special case which you can see in print_token.

Once tabs were handled, I thought line endings should be next and that is where I ran into some trouble. Different systems emit different line ending characters. For example, most modern Linux and Apple devices use \n to signal end-of-line, Apple before OSX uses \r alone, and Windows has seemingly always used \r\n. The Windows use case is what throws a wrench in my code, since it's a 2-character signal and I am only walking 1-character at a time. I could add some special code here to look forwards or backwards an additional character in certain cases, but what I came to realize is that what I am really after is to capture an 'end of line' token.

Plan Update

I can see that I am very slowly starting to build a lexer, or at least I am finding the use-case for one. This seems like a great time to update and revise my plan:

  • Win32 Window
    • Message Loop
    • Back Buffer + Blit
    • Simple screen painting
  • Font + Text Rendering
    • Evaluate stb_truetype integration
    • Render hardcoded text to screen
  • Typing, Cursor and Gap Buffer
    • Translate keystrokes to text
    • Insert and delete text
    • Blinking cursor
  • Lexer
    • Handle new lines
    • Support syntax highlighting
  • Code Navigation
    • Blinking cursor
    • Show line numbers
    • Implement ability to search for, and jump to, text
  • Hotkeys
    • Copy
    • Cut
    • Paste
    • Support common code-related commands
  • File I/O
    • Open files on disc
    • Save files to disc
  • Multi-pane
    • Support having two files open at once
    • Support having the same file open twice with independent cursors
  • External Process
    • Support running a BAT from a hotkey to compile code
    • Support debugger integration (jump to line)
  • Color Themes
    • Support custom colors

Milestone 1


First Steps

Earlier, I told you why I’m insane enough to write my own code editor and explained my approach. Today, I'm sharing some of the code. It’s starting to feel like I might actually get this thing to work.

First, I had to setup a compiler. Since I recently tried and failed to get the Microsoft compiler working, I tried GCC next. This was actually a great experience! I managed to find a version of GCC that works with Windows 7 (GCC 14.2.0) and grabbed it from winlibs.com. I opted for the version without LLVM/Clang/LLD/LLDB, downloaded the zip, extracted the binaries to a folder on my machine, and added it to my PATH. I could successfully hit it with gcc --version. This is how software should be distributed! Why can't Microsoft distribute their compiler this way?

I then wrote a tiny C program:

int main(int argc, char* argv[]) {
	return 0;
}

...and a Windows batch file to automate compilation:

@echo off
setlocal

set FLAGS=-Wall -Wextra -Werror -std=c99

echo.
if "%~1"=="debug" (
	echo [DEBUG BUILD]
	set FLAGS=-Og -g -DINTERNAL_BUILD %FLAGS%
	set OUTPUT_DIR=.\bin\debug\
) else (
	echo [RELEASE BUILD]
	set FLAGS=-O2 -s %FLAGS%
	set OUTPUT_DIR=.\bin\release\
)
if not exist "%OUTPUT_DIR%" (
	mkdir "%OUTPUT_DIR%"
)

echo Invoking cGCC...
pushd %OUTPUT_DIR%
gcc %FLAGS% -o de.exe ..\..\src\dreadedit.c
if %ERRORLEVEL% equ 0 (
	echo Build Success
) else (
	echo Build Failure
)
popd

echo Cleaning temp files...
pushd "%OUTPUT_DIR%"
del *.o *.obj 2> NUL
popd

endlocal
exit /b 0

This let me use build or build debug commands to build my executable.

  • Prereq: get a compiler installed and working

The next step in my plan was to create and open a window. I found that win32 has some built-in functions for text rendering which I leveraged to present some hardcoded text:

milestone

At this point I had most of items 1 and 2 of my plan complete:

  • Win32 Window
    • Message Loop
    • Back Buffer + Blit
    • Simple screen painting
  • Font + Text Rendering
    • Evaluate stb_truetype integration
    • Render hardcoded text to screen

I skipped looking at stb_truetype since win32 has built-in text rendering. I may revisit it later.

Next up was handling input. I made a simple character buffer and added handling of WM_KEYDOWN and WM_CHAR messages to get some text into the buffer to prove that typing works:

int position = 0;
char buffer[255];
void insert_character(char c) {
	buffer[position++] = c;
}
TextOutA(hdc, 0, 0, buffer, length);

However, it wasn't drawing to the screen as I expected. I fired up RemedyBG and ran into a problem: breakpoints were not working. I suspected the debugger was not able to load the debug symbols, so I started to investigate.

I didn't see a .pdb generated, but GCC supposedly embeds debug stuff into the .exe itself, so I didn't think much of it. After reading, it seems RemedyBG requires the information within a .pdb to get breakpoints and variable inspection working. I looked to see if GCC could output a .pdb, and the answer was: yes it can! With -g -gcodeview flags it spits out a .pdb. However, RemedyBG was still not able to add breakpoints. I did some more reading, asked a few questions to an LLM, and discovered that GCC can output a .pdb, but the generated debug information is not sufficient for RemdyBG. I could find no obvious way to coerce GCC into providing what RemedyBG requires.

At this point I had a compiler that worked but no debugger, which was going to become an increasingly nasty problem for me. I looked at the RAD Debugger, but from what I can tell it suffers from the same problems as RemedyBG without a valid Microsoft .pdb near the executable.

Luckily, my trusty LLM told me that Clang is able to output a proper .pdb. My decision to download GCC without Clang now seemed foolish. I looked for a version of Clang for Windows 7, downloaded LLVM-15.0.7-win64.exe and installed it.

This was the first time I've used Clang, so I wasn't sure what I was in for. I'm happy to say that going from GCC to Clang was pain free. I updated a couple lines in my .bat and Clang generated a .pdb that RemedyBG could use. After a single debug session I found that I was not calling InvalidateRect(hwnd, NULL, TRUE); in the right place, so I wasn't seeing text because Windows was not redrawing the graphics for me. Once I added that, I was in business!

Current State

Here is where I am today:

  • Typing, Cursor and Gap Buffer
    • Translate keystrokes to text
    • Insert and delete text
    • Blinking cursor

I'll dump my current .bat here for posterity:

@echo off
setlocal

set FLAGS=-Wall -Wextra -Werror -std=c99
set FLAGS=%FLAGS% -target x86_64-w64-windows-gnu -fuse-ld=lld
set FLAGS=%FLAGS% -lgdi32 -luser32 -mwindows

echo.
if "%~1"=="debug" (
	echo [DEBUG BUILD]
	set FLAGS=%FLAGS% -Og -g -gcodeview -Wl,--pdb=de.pdb -DINTERNAL_BUILD
	set OUTPUT_DIR=.\bin\debug\
) else (
	echo [RELEASE BUILD]
	set FLAGS=-O2 -s %COMPILER_FLAGS%
	set OUTPUT_DIR=.\bin\release\
)
if not exist "%OUTPUT_DIR%" (
	mkdir "%OUTPUT_DIR%"
)

echo Invoking compiler...
pushd %OUTPUT_DIR%
clang %FLAGS% -o de.exe ..\..\src\dreadedit.c
if %ERRORLEVEL% equ 0 (
	echo Build Success
) else (
	echo Build Failure
)
popd

echo Cleaning temp files...
pushd "%OUTPUT_DIR%"
del *.o *.obj 2> NUL
popd

endlocal
exit /b 0

Here is what I have at the top of my .c file to handle GCC and Clang warnings, along with MSC since that is what I am accustomed to using:

//////////////////////////////////////////////////////////////////////////////////////////
// COMPILER
//////////////////////////////////////////////////////////////////////////////////////////

#if defined(__GNUC__)
#   define COMPILER_GCC
#elif defined(__clang__)
#  define COMPILER_CLANG
#elif defined(_MSC_VER)
#   define COMPILER_MSC
#else
#   error compiler not supported
#endif

#if defined(COMPILER_GCC)
#  if defined(INTERNAL_BUILD)
#    pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#    pragma GCC diagnostic ignored "-Wunused-function"
#    pragma GCC diagnostic ignored "-Wunused-parameter"
#    pragma GCC diagnostic ignored "-Wunused-variable"
#    pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#  endif
#  define inline inline __attribute__((always_inline))
#elif defined(COMPILER_CLANG)
#  if defined(INTERNAL_BUILD)
#    pragma clang diagnostic ignored "-Wmissing-field-initializers"
#    pragma clang diagnostic ignored "-Wunused-function"
#    pragma clang diagnostic ignored "-Wunused-parameter"
#    pragma clang diagnostic ignored "-Wunused-variable"
#    pragma clang diagnostic ignored "-Wunused-but-set-variable"
#  endif
#  define inline inline __attribute__((always_inline))
#elif defined(COMPILER_MSC)
#  pragma warning(disable:4201) // nameless struct/union
#  if defined(INTERNAL_BUILD)
#    pragma warning(disable:4100) // unreferenced formal parameter
#    pragma warning(disable:4101) // unreferenced local variable
#    pragma warning(disable:4127) // conditional expression is constant
#    pragma warning(disable:4189) // local variable is initialized but not referenced
#    pragma warning(disable:4505) // unreferenced local function has been removed
#    pragma warning(disable:4702) // unreachable code
#  endif
#  define inline __forceinline
#endif

I also want to share how I am currently processing keystrokes. I use WM_KEYDOWN to filter some non-character keys that I will likely need in the future. I then let Windows translate the keystrokes for me and capture characters with WM_CHAR as seen here:

#define COLOR_BACK RGB(0,0,0)
#define COLOR_TEXT RGB(255,255,255)

LRESULT CALLBACK
WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	switch (uMsg) {
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;
		case WM_CLOSE:
			DestroyWindow(hwnd);
			return 0;
		case WM_PAINT: {
				PAINTSTRUCT ps;
				HDC hdc = BeginPaint(hwnd, &ps);
				
				HBRUSH hBrush = CreateSolidBrush(COLOR_BACKGROUND);
				RECT rc;
				GetClientRect(hwnd, &rc);
				FillRect(hdc, &rc, hBrush);
				DeleteObject(hBrush);
	
				SetBkColor(hdc, COLOR_BACK);
				SetTextColor(hdc, COLOR_TEXT);
				u32 length = cstr_length(buffer);
				if (length > 0) {
					TextOutA(hdc, 0, 0, buffer, length);
				}
				EndPaint(hwnd, &ps);
			}
			return 0;
		case WM_KEYDOWN:
			switch (wParam) {
				case VK_LEFT:
				case VK_RIGHT:
				case VK_DOWN:
				case VK_UP:
				case VK_HOME:
				case VK_END:
				case VK_DELETE:
				case VK_BACK:
					break;
				case VK_ESCAPE:
					PostMessage(hwnd, WM_CLOSE, 0, 0);
					break;

			}
			return 0;
		case WM_LBUTTONDOWN:
			MessageBeep(MB_ICONINFORMATION);
			return 0;
		case WM_RBUTTONDOWN:
			MessageBeep(MB_ICONINFORMATION);
			return 0;
		case WM_CHAR:
			switch (wParam) {
				case VK_RETURN:
					break;
				case VK_TAB:
					break;
				case VK_BACK:
					break;
				default:
					if (wParam >= 32 && wParam != 127) {
						insert_character((char)wParam);
						InvalidateRect(hwnd, NULL, TRUE);
					}
			}
			break;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Getting Started


Motivation

In my last post, I described my frustration with modern software along with a desire to look back at the "good ol' days" of computing when software was simpler. Recently, simplicity in software is a guiding principal I've adopted, and that remains as one of the primary motivations for writing my own code editor. However, another important factor is that I really have no idea how these things work. There are some basic things that are obvious, but I have never entered the world of tool development or text editing, and I think having some hands-on experience would help me better apricate and understand software tools.

So I can summarize my motivations:

  • Need for a simple code editor
  • Need for a code editor that works on an air gapped Windows 7 machine
  • Need to free myself from as many dependencies as possible to remain in control of my own destiny
  • Need to learn something new

The motivations could also serve as goals for the project, and it's very likely that, by that measure, the project will fail. I will proceed, however. At the very least I will learn something new, which is my top priority. If I have to go back to using existing editors on Windows 11 it's not the end of the world, but I want to give this a try before accepting that fate.

The question I need to ask myself next is "how can I get this done?"

Software

Code Editor

If I don't already have a code editor, how can I write the code for the editor? I'm going to start with notepad.exe. This might sound crazy, but it also serves as a milestone marker. I know that I can start using my own code editor to develop itself once I have the required feature parity with notepad.

Compiler

It's obvious, but in order to compile code into a useable program I need a compiler. I already went through great pain trying and failing to install Build Tools for Visual Studio on my Windows 7 machine, because it is offline. Even after creating the offline installer and manually deploying certificates, the installer does not function and no actionable error messages are written to the logs. I will try GCC next. I've used GCC for some embedded programming in the past, but not for Windows development. It looks like installing GCC on a machine without internet access is not difficult, but who can be sure? My success or failure will be noted in the next post.

Hardware

For transparency's sake, I will include the actual hardware I am using for my dev machine:

HP EliteDesk 800 G2 Mini (refurbished)

  • Intel Core i5-6500T @ 2.5ghz
  • 16gb DDR4 RAM
  • 250gb HDD

The Plan

Before I start coding I think it's good to have a plan, even if the plan changes immediately and often. I know so little about how these things work that I expect this list to change dramatically. For now, these are the features I am looking at, roughly in order:

Prereq: get a compiler installed and working

  1. Win32 Window
    1. Message Loop
    2. Back Buffer + Blit
    3. Simple screen painting
  2. Font + Text Rendering
    1. Evaluate stb_truetype integration
    2. Render hardcoded text to screen
  3. Typing, Cursor and Gap Buffer
    1. Translate keystrokes to text
    2. Insert and delete text
    3. Blinking cursor
  4. File I/O
    1. Open, Close and Save files to disc
  5. Code Navigation
    1. Implement ability to search for, and jump to, text
    2. Show line numbers
  6. Hotkeys
    1. Copy, Cut, Paste
    2. Support common code-related commands
  7. Multi-pane
    1. Support having two files open at once
    2. Support having the same file open twice with independent cursors
  8. External Process
    1. Support running a BAT from a hotkey to compile code
    2. Support debugger integration (jump to line)
  9. Color Themes
    1. Support syntax highlighting
    2. Support custom colors

Something I want to note is that I don't have a plan to add mouse support. I have always lived in a world where a mouse was available to navigate code and I think it's always held me back. I want to get to a point where my hands never leave the keyboard, but I lack the discipline to do it myself. This could be an opportunity to force myself to forego the mouse by simply not supporting it.

Once this post goes live I will begin to put my plan into action.

Looking Back


The Past

My very first home computer was a Windows 95 machine which crashed frequently (although this could have been a hardware issue all along?). I upgraded to Windows ME which made things worse, but I was young and had time. I ended up learning a lot to play Diablo 2 without losing my hardcore character to lag or crashes. I can still vividly remember the horror of hearing "Looking for Baal" as the screen stayed black, only to finally load in with my character dead on the ground. The internet was still in its infancy at the time; there was no AI, no Google. I just had to try stuff until it worked.

When I got to college I used Windows XP. The OS crashes were mostly gone. For my C++ course work, we were instructed to use an IDE called Quincy which was primitive by modern standards. It crashed all the time (but Windows XP didn't) and I learned the hard way that pressing CTRL+S every few seconds was essential. This is a habit I continue to this day, although most modern software autosaves. Even with all these problems, I don't ever remember feeling discouraged when it came to programming, or about computers in general. Maybe I was just a naive youth, but I think my enthusiasm endured because I was witnessing computer hardware and software make continual improvements every day.

The Present

Now it's 2025. I've been using Windows 11 and Visual Studio for a long time, on both professional and personal projects. I find myself frequently feeling discouraged whenever I sit down to program. Time is now far more scarce than when I was young and every minute that I waste with OS updates, incompatibilities, and slowness feels excruciating. It's frustrating and makes me want to quit. Modern software has become exceptionally over complicated and it sucks! Even opening a folder takes forever on a modern gaming system! At least I'm not alone in feeling this way.

To mitigate this, I started using tools that were built by people who actually care about time. I started using 10x Editor and RemedyBG to make coding tolerable again. I use File Pilot to avoid the feeling of walking through tar with explorer.exe. But somehow, Windows 11 keeps getting worse and I keep noticing.

I can still remember the days before things were so broken and complicated! So, instead of looking forward I started looking back.

"If you want to set off and go develop some grand new thing, you don't need millions of dollars of capitalization. You need enough pizza and Diet Coke to stick in your refrigerator, a cheap PC to work on, and the dedication to go through with it." - John Carmack

Inspired by the numinous John Carmack quote above, I bought a cheap PC to serve as my primary programming machine and put Windows 10 LTSC on it. I briefly tried to install Linux but none of my attempts were successful. Some distros wouldn't even install on the hardware I was using. The ones that did install wouldn't boot. I actually blame Microsoft and Intel for this since I think many of the problems involve things like "TPM" or "UEFI" or whatever other complex BIOS junk that exists today. I admit that I do not have a deep understanding of chipset/BIOS minutia, and I have no interest in learning. I want to program, I don't want to be a systems admin.

I used that machine for about a year, and things were mostly good. However, LTSC still has an expiration date, and Windows 10 is still not fun. After a particularly depressing session, I thought hard about the last time I had a good experience with a computer. It was back when Windows 7 was in its prime. Windows 7 smoothed over many of the problems Vista introduced and mostly just got out of my way. It didn't have the modern Windows telemetry nightmare. It was stable. Software mostly worked. So I decided to try an experiment and give Windows 7 another try.

Immediately I discovered my machine wouldn't work because it was too new. The chipset is not supported by Windows 7 drivers. So I bought something old instead. The 10+ year old PC I bought has no wireless network adapter, which is excellent for me since putting this device online might destroy it. The software I use seems to still have support for Windows 7, even if unofficially. Unfortunately, 10x Editor does not render correctly which leaves me heartbroken.

The Future

So what can I do? I need an editor that respects me and my time, which narrows it down to very few. I used to use 4Coder, but it's long since been discontinued. There is a community version, so I gave that shot. Unfortunately, installing Build Tools for Visual Studio on an offline machine wouldn't work for me, even when using the fancy command line flags to create an offline installer. This leaves me concerned since I typically use Microsoft's compiler. I will try GCC to see if I have better luck, but for now I have no code editor and no compiler.

If I think about what I need in an editor, it's actually very little:

  • manipulate text
  • copy and paste
  • support keyboard shortcuts
  • launch a .bat to compile my code when I press CTRL+B

Syntax highlighting might be nice but isn't required. I just want to type, compile, and run programs.

The Focus editor sounds great, but doesn't run on Windows 7. I'm not cool enough or smart enough to learn emacs. Notepad is actually pretty close, but doesn't fulfil my short-list of needs.

I think I'll just write my own.