diff --git a/src/common/all_test.ts b/src/common/all_test.ts
index 7bb82a4..77e7f22 100644
--- a/src/common/all_test.ts
+++ b/src/common/all_test.ts
@@ -220,8 +220,8 @@ testSuite({
 		},
 		'.cxToRx, .rxToCx': () => {
 			const row = Row.from('foo\tbar\tbaz');
-			row.updateRender();
-			assertNotEquals(row.chars, row.render);
+			row.update();
+			assertNotEquals(row.chars, row.rchars);
 			assertNotEquals(row.size, row.rsize);
 			assertEquals(row.size, 11);
 			assertEquals(row.rsize, row.size + (SCROLL_TAB_SIZE * 2) - 2);
diff --git a/src/common/document.ts b/src/common/document.ts
index 1e92e05..f5663f9 100644
--- a/src/common/document.ts
+++ b/src/common/document.ts
@@ -1,49 +1,8 @@
 import Row from './row.ts';
 import { arrayInsert } from './fns.ts';
 import { getRuntime } from './runtime.ts';
-import { Position, SearchDirection } from './types.ts';
-import { KeyCommand } from './ansi.ts';
-
-class Search {
-	public lastMatch: number = -1;
-	public current: number = -1;
-	public direction: SearchDirection = SearchDirection.Forward;
-
-	public parseInput(key: string) {
-		switch (key) {
-			case KeyCommand.ArrowRight:
-			case KeyCommand.ArrowDown:
-				this.direction = SearchDirection.Forward;
-				break;
-
-			case KeyCommand.ArrowLeft:
-			case KeyCommand.ArrowUp:
-				this.direction = SearchDirection.Backward;
-				break;
-
-			default:
-				this.lastMatch = -1;
-				this.direction = SearchDirection.Forward;
-		}
-
-		if (this.lastMatch === -1) {
-			this.direction = SearchDirection.Forward;
-		}
-
-		this.current = this.lastMatch;
-	}
-
-	public getNextRow(rowCount: number): number {
-		this.current += this.direction;
-		if (this.current === -1) {
-			this.current = rowCount - 1;
-		} else if (this.current === rowCount) {
-			this.current = 0;
-		}
-
-		return this.current;
-	}
-}
+import { Position } from './types.ts';
+import { Search } from './search.ts';
 
 export class Document {
 	#rows: Row[];
@@ -65,7 +24,10 @@ export class Document {
 	}
 
 	public static default(): Document {
-		return new Document();
+		const self = new Document();
+		self.#search.parent = self;
+
+		return self;
 	}
 
 	public isEmpty(): boolean {
@@ -105,26 +67,14 @@ export class Document {
 
 	public resetFind() {
 		this.#search = new Search();
+		this.#search.parent = this;
 	}
 
 	public find(
 		q: string,
 		key: string,
 	): Position | null {
-		this.#search.parseInput(key);
-
-		let i = 0;
-		for (; i < this.numRows; i++) {
-			const current = this.#search.getNextRow(this.numRows);
-
-			const possible = this.#rows[current].find(q);
-			if (possible !== null) {
-				this.#search.lastMatch = current;
-				return Position.at(possible, current);
-			}
-		}
-
-		return null;
+		return this.#search.search(q, key);
 	}
 
 	public insert(at: Position, c: string): void {
@@ -132,7 +82,7 @@ export class Document {
 			this.insertRow(this.numRows, c);
 		} else {
 			this.#rows[at.y].insertChar(at.x, c);
-			this.#rows[at.y].updateRender();
+			this.#rows[at.y].update();
 		}
 
 		this.dirty = true;
@@ -149,7 +99,7 @@ export class Document {
 		}
 
 		const newRow = this.#rows[at.y].split(at.x);
-		newRow.updateRender();
+		newRow.update();
 		this.#rows = arrayInsert(this.#rows, at.y + 1, newRow);
 
 		this.dirty = true;
@@ -188,7 +138,7 @@ export class Document {
 			row.delete(at.x);
 		}
 
-		row.updateRender();
+		row.update();
 
 		this.dirty = true;
 	}
@@ -199,7 +149,7 @@ export class Document {
 
 	public insertRow(at: number = this.numRows, s: string = ''): void {
 		this.#rows = arrayInsert(this.#rows, at, Row.from(s));
-		this.#rows[at].updateRender();
+		this.#rows[at].update();
 
 		this.dirty = true;
 	}
diff --git a/src/common/editor.ts b/src/common/editor.ts
index c90afc3..fe40d92 100644
--- a/src/common/editor.ts
+++ b/src/common/editor.ts
@@ -482,7 +482,7 @@ class Editor {
 			this.#screen.cols,
 		);
 
-		this.#buffer.append(row.rstring(this.#offset.x), len);
+		this.#buffer.append(row.render(this.#offset.x, len));
 	}
 
 	private drawPlaceholderRow(y: number): void {
diff --git a/src/common/highlight.ts b/src/common/highlight.ts
new file mode 100644
index 0000000..3c9c58f
--- /dev/null
+++ b/src/common/highlight.ts
@@ -0,0 +1,16 @@
+import Ansi, { AnsiColor } from './ansi.ts';
+
+export enum HighlightType {
+	None,
+	Number,
+}
+
+export function highlightToColor(type: HighlightType): string {
+	switch (type) {
+		case HighlightType.Number:
+			return Ansi.color256(196);
+
+		default:
+			return Ansi.color(AnsiColor.FgDefault);
+	}
+}
diff --git a/src/common/row.ts b/src/common/row.ts
index 8575e77..dd20620 100644
--- a/src/common/row.ts
+++ b/src/common/row.ts
@@ -1,8 +1,12 @@
 import { SCROLL_TAB_SIZE } from './config.ts';
-import { arrayInsert, strChars } from './fns.ts';
+import { arrayInsert, isAsciiDigit, strChars } from './fns.ts';
+import { highlightToColor, HighlightType } from './highlight.ts';
+import Ansi from './ansi.ts';
 
 /**
- * One row of text in the current document
+ * One row of text in the current document. In order to handle
+ * multi-byte graphemes, all operations are done on an
+ * array of 'character' strings.
  */
 export class Row {
 	/**
@@ -14,16 +18,16 @@ export class Row {
 	 * The characters rendered for the current row
 	 * (like replacing tabs with spaces)
 	 */
-	render: string[] = [];
+	rchars: string[] = [];
 
 	/**
 	 * The syntax highlighting map
 	 */
-	hl: string[] = [];
+	hl: HighlightType[] = [];
 
 	private constructor(s: string | string[] = '') {
 		this.chars = Array.isArray(s) ? s : strChars(s);
-		this.render = [];
+		this.rchars = [];
 	}
 
 	public get size(): number {
@@ -31,11 +35,11 @@ export class Row {
 	}
 
 	public get rsize(): number {
-		return this.render.length;
+		return this.rchars.length;
 	}
 
 	public rstring(offset: number = 0): string {
-		return this.render.slice(offset).join('');
+		return this.rchars.slice(offset).join('');
 	}
 
 	public static default(): Row {
@@ -52,7 +56,7 @@ export class Row {
 
 	public append(s: string): void {
 		this.chars = this.chars.concat(strChars(s));
-		this.updateRender();
+		this.update();
 	}
 
 	public insertChar(at: number, c: string): void {
@@ -67,7 +71,7 @@ export class Row {
 	public split(at: number): Row {
 		const newRow = new Row(this.chars.slice(at));
 		this.chars = this.chars.slice(0, at);
-		this.updateRender();
+		this.update();
 
 		return newRow;
 	}
@@ -161,13 +165,45 @@ export class Row {
 		return this.chars.join('');
 	}
 
-	public updateRender(): void {
+	public update(): void {
 		const newString = this.chars.join('').replaceAll(
 			'\t',
 			' '.repeat(SCROLL_TAB_SIZE),
 		);
 
-		this.render = strChars(newString);
+		this.rchars = strChars(newString);
+		this.highlight();
+	}
+
+	public render(offset: number, len: number): string {
+		const end = Math.min(len, this.rsize);
+		const start = Math.min(offset, len);
+		let result = '';
+
+		for (let i = start; i < end; i++) {
+			// 	if (this.chars[i] === '\t') {
+			// 		result += ' '.repeat(SCROLL_TAB_SIZE);
+			// 	} else {
+			result += highlightToColor(this.hl[i]);
+			result += this.rchars[i];
+			result += Ansi.ResetFormatting;
+		}
+		// }
+
+		return result;
+	}
+
+	private highlight(): void {
+		const highlighting = [];
+		for (const ch of this.rchars) {
+			if (isAsciiDigit(ch)) {
+				highlighting.push(HighlightType.Number);
+			} else {
+				highlighting.push(HighlightType.None);
+			}
+		}
+
+		this.hl = highlighting;
 	}
 }
 
diff --git a/src/common/search.ts b/src/common/search.ts
new file mode 100644
index 0000000..3c4c2a4
--- /dev/null
+++ b/src/common/search.ts
@@ -0,0 +1,67 @@
+import { Position } from './types.ts';
+import { KeyCommand } from './ansi.ts';
+import Document from './document.ts';
+
+enum SearchDirection {
+	Forward = 1,
+	Backward = -1,
+}
+
+export class Search {
+	private lastMatch: number = -1;
+	private current: number = -1;
+	private direction: SearchDirection = SearchDirection.Forward;
+	public parent: Document | null = null;
+
+	private parseInput(key: string) {
+		switch (key) {
+			case KeyCommand.ArrowRight:
+			case KeyCommand.ArrowDown:
+				this.direction = SearchDirection.Forward;
+				break;
+
+			case KeyCommand.ArrowLeft:
+			case KeyCommand.ArrowUp:
+				this.direction = SearchDirection.Backward;
+				break;
+
+			default:
+				this.lastMatch = -1;
+				this.direction = SearchDirection.Forward;
+		}
+
+		if (this.lastMatch === -1) {
+			this.direction = SearchDirection.Forward;
+		}
+
+		this.current = this.lastMatch;
+	}
+
+	private getNextRow(rowCount: number): number {
+		this.current += this.direction;
+		if (this.current === -1) {
+			this.current = rowCount - 1;
+		} else if (this.current === rowCount) {
+			this.current = 0;
+		}
+
+		return this.current;
+	}
+
+	public search(q: string, key: string): Position | null {
+		this.parseInput(key);
+
+		let i = 0;
+		for (; i < this.parent!.numRows; i++) {
+			const current = this.getNextRow(this.parent!.numRows);
+
+			const possible = this.parent!.row(current)!.find(q);
+			if (possible !== null) {
+				this.lastMatch = current;
+				return Position.at(possible, current);
+			}
+		}
+
+		return null;
+	}
+}
diff --git a/src/common/types.ts b/src/common/types.ts
index 7745468..8d8aa44 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -157,16 +157,6 @@ export class Position {
 	}
 }
 
-export enum SearchDirection {
-	Forward = 1,
-	Backward = -1,
-}
-
-export enum HighlightType {
-	None,
-	Number,
-}
-
 // ----------------------------------------------------------------------------
 // Testing
 // ----------------------------------------------------------------------------