/TextDocument.cs
using System;
using System.Collections;
using System.IO;

public struct Cursor {
	private int _x;
	private int _y;

	public Cursor(int x, int y) {
		_x = x;
		_y = y;
	}

	public int x {
		get {
			return _x;
		}
		set {
			if (value < 0) _x = 0;
			else _x = value;
		}
	}

	public int y {
		get {
			return _y;
		}
		set {
			if (value < 0) _y = 0;
			else _y = value;
		}
	}
}

public class TextArray {
	private ArrayList lines;

	public TextArray() {
		lines = new ArrayList();
		lines.Add("");
	}

	public TextArray(string filename) {
		lines = new ArrayList();
		StreamReader file;
		string line;

		file = new StreamReader(filename);
		while ((line = file.ReadLine()) != null) {
			lines.Add(line);
		}
		file.Close();
	}

	public ArrayList Lines {
		get {
			return lines;
		}
	}

	public void Insert(int x, int y, char c) {
		while (lines.Count < y+1)
			lines.Add("");
		string s = (string)lines[y];

		if (x >= s.Length && (c == ' ' || c == '\t')) return;
		if (x > s.Length) {
			lines[y] = s.PadRight(x) + c;
		} else if (x <= s.Length) {
			lines[y] = s.Insert(x, new string(c, 1));
		}
	}

	public void Delete(int x, int y) {
		if (y >= lines.Count) return;

		string s = (string)lines[y];
		if (x >= s.Length) {
			if (y == lines.Count - 1) return;
			if (x > s.Length)
				s = s.PadRight(x);
			lines[y] = s + (string)lines[y+1];
			lines.RemoveAt(y+1);
		} else if (s.Length == 0 && x == 0) {
			lines.RemoveAt(y);
		} else {
			lines[y] = s.Remove(x, 1);
		}
	}

	public void Backspace(int x, int y) {
		if (x == 0 && y == 0) return;
		if (y >= lines.Count) return;

		string s = (string)lines[y];
		if (x > s.Length) return;

		if (s.Length == 0 && x == 0) {
			lines.RemoveAt(y);
		} else if (x == 0 && y > 0) {
			lines.RemoveAt(y);
			lines[y-1] += s;
		} else {
			lines[y] = s.Remove(x-1, 1);
		}
	}

	public void Newline(int x, int y) {
		if (y >= lines.Count) return;
		string s = (string) lines[y];
		if (x < s.Length) {
			lines[y] = s.Substring(0, x);
			lines.Insert(y + 1, s.Substring(x));
		} else {
			if (y == lines.Count - 1) return;
			lines.Insert(y + 1, "");
		}
	}

	public int LineLength(int y) {
		if (y >= lines.Count) return 0;
		return ((string)lines[y]).Length;
	}

	public void Save(string filename) {
		StreamWriter file = new StreamWriter(filename);
		for (int i = 0; i < lines.Count; i++) {
			file.Write((string)lines[i]);
			file.Write('\n');
		}
		file.Close();
	}
}

public class TextDocument : IKeyPress {
	private TextArray text;
	public string filename = null;
	public Cursor cursor;
	public Cursor textorigin;
	private int pagewidth, pageheight;

	public TextDocument() {
		text = new TextArray();
	}

	public TextDocument(string filename) {
		text = new TextArray(filename);
		this.filename = filename;
	}

	public string GetTextWindow() {
		string s = "";

		for (int i = 0; i <= pageheight + 1 && textorigin.y + i < text.Lines.Count; i++) {
			string l = (string)text.Lines[textorigin.y + i];
			if (textorigin.x > l.Length) {
				s += "\n";
			} else {
				s += l.Substring(textorigin.x) + "\n";
			}
		}
		return s;
	}

	private void FixTextWindow() {
		if (cursor.x < textorigin.x) textorigin.x = cursor.x;
		if (cursor.y < textorigin.y) textorigin.y = cursor.y;
		if (cursor.x > textorigin.x + pagewidth) textorigin.x = cursor.x - pagewidth;
		if (cursor.y > textorigin.y + pageheight) textorigin.y = cursor.y - pageheight;
	}

	public void SetPageSize(int w, int h) {
		pagewidth = w;
		pageheight = h;
		FixTextWindow();
	}

	public void MoveCursor(Gdk.Key k) {
		switch(k) {
		case Gdk.Key.Up:
			cursor.y--;
			break;
		case Gdk.Key.Down:
			cursor.y++;
			break;
		case Gdk.Key.Left:
			cursor.x--;
			break;
		case Gdk.Key.Right:
			cursor.x++;
			break;
		case Gdk.Key.End:
			cursor.x = text.LineLength(cursor.y);
			break;
		case Gdk.Key.Home:
			cursor.x = 0;
			break;
		case Gdk.Key.Page_Up:
			cursor.y -= pageheight;
			break;
		case Gdk.Key.Page_Down:
			cursor.y += pageheight;
			break;
		}
	}

	public void NextWord() {
		if (cursor.x >= text.LineLength(cursor.y)) {
			cursor.y++;
			cursor.x = 0;
		} else {
			string s = (String)text.Lines[cursor.y];
			int i = s.IndexOf(' ', cursor.x);
			if (i == -1) {
				cursor.y++;
				cursor.x = 0;
			} else {
				cursor.x = i + 1;
			}
		}
	}

	public void PreviousWord() {
		if (cursor.y > text.Lines.Count) {
			cursor.x = 0;
			cursor.y--;
		} else if (cursor.y == text.Lines.Count) {
			string s = (String)text.Lines[text.Lines.Count - 1];
			int i = s.LastIndexOf(' ');
			if (i == -1) {
				cursor.x = 0;
			} else {
				cursor.x = i + 1;
			}
			cursor.y--;
		} else if (cursor.x == 0 && cursor.y > 0) {
			string s = (String)text.Lines[cursor.y - 1];
			int i = s.LastIndexOf(' ');
			if (i == -1) {
				cursor.x = 0;
			} else {
				cursor.x = i + 1;
			}
			cursor.y--;
		} else if (cursor.x == 0 && cursor.y == 0) {
			return;
		} else {
			string s = (String)text.Lines[cursor.y];
			if (cursor.x >= s.Length)
				cursor.x = s.Length - 1;
			int i = s.LastIndexOf(' ', cursor.x - 2, cursor.x - 2);
			if (i == -1) {
				cursor.x = 0;
			} else {
				cursor.x = i + 1;
			}
		}
	}

	[GLib.ConnectBefore ()]
	public void KeyPress(object o, Gtk.KeyPressEventArgs args) {
		if ((args.Event.State & Gdk.ModifierType.ControlMask) != 0) {
			switch(args.Event.Key) {
			case Gdk.Key.Right:
				NextWord();
				break;
			case Gdk.Key.Left:
				PreviousWord();
				break;
			}
		} else if ((args.Event.State & Gdk.ModifierType.Mod1Mask) != 0) {
		} else {
			switch(args.Event.Key) {
			case Gdk.Key.Up:
			case Gdk.Key.Down:
			case Gdk.Key.Left:
			case Gdk.Key.Right:
			case Gdk.Key.Page_Up:
			case Gdk.Key.Page_Down:
			case Gdk.Key.Home:
			case Gdk.Key.End:
				MoveCursor(args.Event.Key);
				break;
			case Gdk.Key.Delete:
				text.Delete(cursor.x, cursor.y);
				break;
			case Gdk.Key.BackSpace:
				int l = 0;
				if (cursor.y > 0) l = text.LineLength(cursor.y-1);
				text.Backspace(cursor.x, cursor.y);
				if (cursor.x == 0) {
					cursor.y--;
					if (cursor.y >= text.Lines.Count) return;
					cursor.x = l;
				} else {
					cursor.x--;
				}
				break;
			case Gdk.Key.Return:
				text.Newline(cursor.x, cursor.y);
				cursor.x = 0;
				cursor.y++;
				break;
			default:
				AddChar((int)args.Event.KeyValue);
				break;
			}
		}
		FixTextWindow();
	}
	
	public void AddChar(int c) {
		c = c & 0xFF;
		if (c > 128) return;

		text.Insert(cursor.x, cursor.y, (char)c);
		cursor.x++;
	}

	public void Save() {
		if (filename == null)
			throw new InvalidOperationException("Cannot Save() without filename");
		text.Save(filename);
	}

	public void Save(string filename) {
		this.filename = filename;
		Save();
	}
}