/// \file
/// the main header file for ted.
///
/// this contains almost all of the function declarations.


/// \mainpage ted doxygen documentation
/// 
/// See "files" above. You probably want to look at \ref ted.h.

#ifndef TED_H_
#define TED_H_

#ifdef __cplusplus
extern "C" {
#endif

#include "base.h"
#include "util.h"
#include "os.h"
#include "unicode.h"
#include "ds.h"
#include "lsp.h"
#include "text.h"
#include "colors.h"
#include "command.h"
#include "lib/glcorearb.h"
#include "sdl-inc.h"

/// Version number
#define TED_VERSION "2.3.1"
/// Version string
#define TED_VERSION_FULL "ted v. " TED_VERSION
/// Maximum path size ted handles.
#define TED_PATH_MAX 1024
/// Config filename
#define TED_CFG "ted.cfg"

/// Minimum text size
#define TEXT_SIZE_MIN 6
/// Maximum text size
#define TEXT_SIZE_MAX 70
/// max number of LSPs running at once
#define TED_LSP_MAX 200

// If you are adding new languages, DO NOT change the constant values
// of the previous languages. It will mess up config files which use :set-language!
enum {
	/// avoid using this and use LANG_TEXT instead.
	LANG_NONE = 0,
	/// C
	LANG_C = 1,
	/// C++
	LANG_CPP = 2,
	/// Rust
	LANG_RUST = 3,
	/// Python
	LANG_PYTHON = 4,
	/// TeX/LaTeX
	LANG_TEX = 5,
	/// Markdown
	LANG_MARKDOWN = 6,
	/// HTML
	LANG_HTML = 7,
	/// .cfg files
	LANG_CONFIG = 8,
	/// JavaScript
	LANG_JAVASCRIPT = 9,
	/// Java
	LANG_JAVA = 10,
	/// Go
	LANG_GO = 11,
	/// like \ref LANG_CONFIG, but with more highlighting for ted.cfg-specific stuff.
	LANG_TED_CFG = 12,
	/// TypeScript
	LANG_TYPESCRIPT = 13,
	/// JSON
	LANG_JSON = 14,
	/// XML
	LANG_XML = 15,
	/// GL shading language
	LANG_GLSL = 16,
	/// plain text
	LANG_TEXT = 17,
	/// CSS
	LANG_CSS = 18,
	
	/// all user-defined languages are greater than this.
	LANG_USER_MIN = 100000,
	/// all user-defined languages are less than this.
	LANG_USER_MAX = 2000000000,
};

/// A programming language
///
/// May be one of the `LANG_*` constants, or a dynamically registered language.
typedef u32 Language;

/// Current state of syntax highlighting.
typedef u32 SyntaxState;

/// types of syntax highlighting
enum SyntaxCharType {
	// do not change these numbers as it will break backwards compatibility
	SYNTAX_NORMAL = 0,
	SYNTAX_KEYWORD = 1,
	SYNTAX_BUILTIN = 2,
	SYNTAX_COMMENT = 3,
	SYNTAX_PREPROCESSOR = 4,
	SYNTAX_STRING = 5,
	SYNTAX_CHARACTER = 6,
	SYNTAX_CONSTANT = 7,
};
/// Type of syntax highlighting.
typedef u8 SyntaxCharType;
/// Function for syntax highlighting.
/// If you want to add a language to `ted`, you will need to implement this function.
///
/// `state` is used to keep track of state between lines (e.g. whether or not we are in a multiline comment)\n
/// `line` is the UTF-32 text of the line (not guaranteed to be null-terminated).\n
/// `line_len` is the length of the line, in UTF-32 codepoints.\n
/// `char_types` is either `NULL` (in which case only `state` should be updated), or a pointer to `line_len` SyntaxCharTypes, which should be filled out using the `SYNTAX_*` constants.
///
/// no guarantees are made about which order lines will be highlighted in. the only guarantee is that `*state = 0` for the first line, and for line `n > 0`,
/// `*state` was derived from calling this function on line `n-1`.
typedef void (*SyntaxHighlightFunction)(SyntaxState *state, const char32_t *line, u32 line_len, SyntaxCharType *char_types);

/// Information about a programming language
///
/// Used for dynamic language registration.
/// Please zero all the fields of the struct which you aren't using.
///
/// The fields `id` and `name` MUST NOT be 0, or `ted` will reject your language.
typedef struct {
	/// Language ID number. For user-defined languages, this must be `>= LANG_USER_MIN` and `< LANG_USER_MAX`.
	///
	/// To avoid conflict, try picking a unique number.
	Language id;
	char name[30];
	char lsp_identifier[32];
	SyntaxHighlightFunction highlighter;
	char reserved[128];
} LanguageInfo;

/// for tex
#define SYNTAX_MATH SYNTAX_STRING
/// for markdown
#define SYNTAX_CODE SYNTAX_PREPROCESSOR
/// for markdown
#define SYNTAX_LINK SYNTAX_CONSTANT

/// special keycodes for mouse X1 & X2 buttons.
enum {
	KEYCODE_X1 = 1<<20,
	KEYCODE_X2
};
/// see \ref KEY_COMBO
enum {
	KEY_MODIFIER_CTRL_BIT,
	KEY_MODIFIER_SHIFT_BIT,
	KEY_MODIFIER_ALT_BIT
};
/// see \ref KEY_COMBO
#define KEY_MODIFIER_CTRL ((u32)1<<KEY_MODIFIER_CTRL_BIT)
/// see \ref KEY_COMBO
#define KEY_MODIFIER_SHIFT ((u32)1<<KEY_MODIFIER_SHIFT_BIT)
/// see \ref KEY_COMBO
#define KEY_MODIFIER_ALT ((u32)1<<KEY_MODIFIER_ALT_BIT)
/// a "key combo" is some subset of {control, shift, alt} + some key.
typedef struct {
	/// high 32 bits = SDL_Keycode\n
	/// low 8 bits = key modifier (see e.g. \ref KEY_MODIFIER_SHIFT)\n
	/// the remaining 24 bits are currently reserved and should be 0.
	u64 value;
} KeyCombo;
/// Create \ref KeyCombo from modifier and key.
#define KEY_COMBO(modifier, key) ((KeyCombo){.value = (u64)(modifier) \
	| ((u64)(key) << 32)})
/// extract `SDL_Keycode` from \ref KeyCombo
#define KEY_COMBO_KEY(combo) ((SDL_Keycode)((combo.value) >> 32))
/// extract key modifier from \ref KeyCombo
#define KEY_COMBO_MODIFIER(combo) ((u32)((combo.value) & 0xff))

typedef struct {
	const char *string;
	i64 number;
} CommandArgument;

typedef struct {
	bool from_macro;
} CommandContext;

/// Thing to do when a key combo is pressed.
typedef struct {
	KeyCombo key_combo;
	Command command;
	CommandArgument argument;
} KeyAction;


/// A SettingsContext is a context where a specific set of settings are applied.
/// this corresponds to `[PATH//LANGUAGE.(section)]` in config files.
typedef struct {
	/// The settings apply to this language.
	Language language;
	/// The settings apply to all paths which start with this string, or all paths if path=NULL
	char *path;
} SettingsContext;

/// Need to use reference counting for textures because of Settings:
/// We copy parent settings to children
/// e.g.
/// ```
/// [core]
/// bg-texture = "blablabla.png"
/// [Javascript.core]
/// some random shit
/// ```
/// the main Settings' bg_texture will get copied to javascript's Settings,
/// so we need to be extra careful about when we delete textures.
typedef struct {
	u32 ref_count;
	GLuint texture;
} GlRcTexture;

/// Reference-counted shader-array-buffer combo.
typedef struct {
	u32 ref_count;
	GLuint shader;
	GLuint array;
	GLuint buffer;
} GlRcSAB;

typedef struct {
	Language language;
	char extension[16];
} LanguageExtension;

/// All of ted's settings
///
/// NOTE: to add more options to ted, add fields here,
/// and change the settings_<type> global constant near the top of config.c	
typedef struct {
	SettingsContext context;
	u32 colors[COLOR_COUNT];
	float cursor_blink_time_on, cursor_blink_time_off;
	float hover_time;
	float ctrl_scroll_adjust_text_size;
	u32 max_file_size;
	u32 max_file_size_view_only;
	u16 framerate_cap;
	u16 text_size;
	u16 max_menu_width;
	u16 error_display_time;
	bool auto_indent;
	bool auto_add_newline;
	bool syntax_highlighting;
	bool line_numbers;
	bool auto_reload;
	bool auto_reload_config;
	bool restore_session;
	bool regenerate_tags_if_not_found;
	bool indent_with_spaces;
	bool phantom_completions;
	bool trigger_characters;
	bool identifier_trigger_characters;
	bool signature_help_enabled;
	bool lsp_enabled;
	bool lsp_log;
	bool hover_enabled;
	bool highlight_enabled;
	bool highlight_auto;
	bool vsync;
	bool save_backup;
	bool crlf_windows;
	bool jump_to_build_error;
	KeyCombo hover_key;
	KeyCombo highlight_key;
	u8 tab_width;
	u8 cursor_width;
	u8 undo_save_time;
	u8 border_thickness;
	u8 padding;
	u8 scrolloff;
	u8 tags_max_depth;
	GlRcSAB *bg_shader;
	GlRcTexture *bg_texture;
	/// string used to start comments
	char comment_start[16];
	/// string used to end comments
	char comment_end[16];
	/// Comma-separated list of file names which identify the project root
	char root_identifiers[4096];
	/// LSP server command
	char lsp[512];
	/// LSP "configuration" JSON
	char lsp_configuration[4096];
	/// Build command. If non-empty, this overrides running `cargo build` if `Cargo.toml` exists, etc.
	char build_command[1024];
	/// Default build command for if `Cargo.toml`, `Makefile`, etc. do not exist.
	char build_default_command[1024];
	LanguageExtension *language_extensions;
	/// dynamic array, sorted by KEY_COMBO(modifier, key)
	KeyAction *key_actions;
} Settings;

/// A position in the buffer
typedef struct {
	/// line number (0-indexed)
	u32 line;
	/// UTF-32 index of character in line
	///
	/// (not the same as column, since a tab is `settings->tab_width` columns)
	u32 index;
} BufferPos;

/// A single line in a buffer
typedef struct {
	SyntaxState syntax;
	u32 len;
	char32_t *str;
} Line;

/// Sections of `ted.cfg`
typedef enum {
	SECTION_NONE,
	SECTION_CORE,
	SECTION_KEYBOARD,
	SECTION_COLORS,
	SECTION_EXTENSIONS
} ConfigSection;

/// This structure is used temporarily when loading settings
/// It's needed because we want more specific contexts to be dealt with last.
typedef struct {
	/// index in order of which part was read first.
	int index;
	SettingsContext context;
	ConfigSection section;
	char *file;
	u32 line;
	/// contents of this config part
	char *text;
} ConfigPart;

/// This refers to replacing prev_len characters (found in prev_text) at pos with new_len characters
typedef struct {
	bool chain; // should this + the next edit be treated as one?
	BufferPos pos;
	u32 new_len;
	u32 prev_len;
	char32_t *prev_text;
	double time; // time at start of edit (i.e. the time just before the edit), in seconds since epoch
} BufferEdit;

/// A buffer - this includes line buffers, unnamed buffers, the build buffer, etc.
typedef struct {
	/// NULL if this buffer is untitled or doesn't correspond to a file (e.g. line buffers)
	char *path;
	/// we keep a back-pointer to the ted instance so we don't have to pass it in to every buffer function
	struct Ted *ted;
	/// number of characters scrolled in the x direction
	double scroll_x;
	/// number of characters scrolled in the y direction
	double scroll_y;
	/// last write time to \ref path
	double last_write_time;
	/// the language the buffer has been manually set to, or \ref LANG_NONE if it hasn't been set to anything
	i64 manual_language;
	/// position of cursor
	BufferPos cursor_pos;
	/// if \ref selection is true, the text between \ref selection_pos and \ref cursor_pos is selected.
	BufferPos selection_pos;
	/// "previous" position of cursor, for \ref CMD_PREVIOUS_POSITION
	BufferPos prev_cursor_pos;
	/// "line buffers" are buffers which can only have one line of text (used for inputs)
	bool is_line_buffer;
	/// is anything selected?
	bool selection;
	/// set to false to disable undo events
	bool store_undo_events;
	/// This is set to true whenever a change is made to the buffer, and never set to false by buffer_ functions.
	/// (Distinct from \ref buffer_unsaved_changes)
	bool modified;
	/// will the next undo event be chained with the ones after?
	bool will_chain_edits;
	/// will the next undo event be chained with the previous one?
	bool chaining_edits;
	/// view-only mode
	bool view_only;
	/// (line buffers only) set to true when submitted. you have to reset it to false.
	bool line_buffer_submitted;
	/// If set to true, buffer will be scrolled to the cursor position next frame.
	/// This is to fix the problem that \ref x1, \ref y1, \ref x2, \ref y2 are not updated until the buffer is rendered.
	bool center_cursor_next_frame;
	/// x coordinate of left side of buffer
	float x1;
	/// y coordinate of top side of buffer
	float y1;
	/// x coordinate of right side of buffer
	float x2;
	/// y coordinate of bottom side of buffer
	float y2;
	/// number of lines in buffer
	u32 nlines;
	/// capacity of \ref lines
	u32 lines_capacity;
	
	/// which LSP this document is open in
	LSPID lsp_opened_in;

	/// where in the undo history was the last write? used by \ref buffer_unsaved_changes
	u32 undo_history_write_pos;
	/// which lines are on screen? updated when \ref buffer_render is called.
	u32 first_line_on_screen, last_line_on_screen;

	/// to cache syntax highlighting properly, it is important to keep track of the
	/// first and last line modified since last frame.
	u32 frame_earliest_line_modified;
	/// see \ref frame_earliest_line_modified.
	u32 frame_latest_line_modified;

	/// lines
	Line *lines;
	/// last error
	char error[256];
	/// dynamic array of undo history
	BufferEdit *undo_history;
	/// dynamic array of redo history
	BufferEdit *redo_history;
} TextBuffer;

typedef enum {
	/// No menu is open
	MENU_NONE,
	/// "Open file"
	MENU_OPEN,
	/// "Save file as"
	MENU_SAVE_AS,
	/// "X has unsaved changes"
	MENU_WARN_UNSAVED,
	/// "X has been changed by another program"
	MENU_ASK_RELOAD,
	/// "Go to definition of..."
	MENU_GOTO_DEFINITION,
	/// "Go to line"
	MENU_GOTO_LINE,
	/// "Command palette"
	MENU_COMMAND_SELECTOR,
	/// "Run a shell command"
	MENU_SHELL,
} Menu;

/// an entry in a selector menu (e.g. the "open" menu)
typedef struct {
	/// label
	const char *name;
	/// if not NULL, this will show on the right side of the entry.
	const char *detail;
	/// color to draw text in
	u32 color;
	/// use this for whatever you want
	u64 userdata;
} SelectorEntry;

/// a selector menu (e.g. the "open" menu)
typedef struct {
	SelectorEntry *entries;
	u32 n_entries;
	Rect bounds;
	/// index where the selector thing is
	u32 cursor;
	float scroll;
	/// whether or not we should let the user select entries using a cursor.
	bool enable_cursor;
} Selector;

/// file entries for file selectors
typedef struct {
	/// just the file name
	char *name;
	/// full path
	char *path;
	FsType type;
} FileEntry;

/// a selector menu for files (e.g. the "open" menu)
typedef struct {
	Selector sel;
	Rect bounds;
	u32 n_entries;
	FileEntry *entries;
	char cwd[TED_PATH_MAX];
	/// indicates that this is for creating files, not opening files
	bool create_menu;
} FileSelector;

/// options for a pop-up menu
typedef enum {
	POPUP_NONE,
	/// "Yes" button
	POPUP_YES = 1<<1,
	/// "No" button
	POPUP_NO = 1<<2,
	/// "Cancel" button
	POPUP_CANCEL = 1<<3,
} PopupOption;

/// pop-up with "yes" and "no" buttons
#define POPUP_YES_NO (POPUP_YES | POPUP_NO)
/// pop-up with "yes", "no", and "cancel" buttons
#define POPUP_YES_NO_CANCEL (POPUP_YES | POPUP_NO | POPUP_CANCEL)

/// A node is a collection of tabs OR a split of two nodes
typedef struct {
	/// dynamic array of indices into ted->buffers, or `NULL` if this is a split
	u16 *tabs;
	/// number from 0 to 1 indicating where the split is.
	float split_pos;
	/// index of active tab in `tabs`.
	u16 active_tab;
	/// is the split vertical? if false, this split looks like a|b
	bool split_vertical;
	/// split left/upper half; index into `ted->nodes`
	u16 split_a;
	/// split right/lower half
	u16 split_b;
} Node;

/// max number of buffers open at one time
#define TED_MAX_BUFFERS 256
/// max number of nodes open at one time
#define TED_MAX_NODES 256
/// max tabs per node
#define TED_MAX_TABS 100
/// max strings in all config files
#define TED_MAX_STRINGS 1000

/// "find" menu result
typedef struct {
	BufferPos start;
	BufferPos end;
} FindResult;

typedef struct {
	char *path;
	u32 line;
	u32 column;
	/// if this is 1, then column == UTF-32 index.
	/// if this is 4, for example, then column 4 in a line starting with a tab would
	/// be the character right after the tab.
	u8 columns_per_tab;
	/// which line in the build output corresponds to this error
	u32 build_output_line;
} BuildError;

/// `LSPSymbolKind`s are translated to these. this is a much coarser categorization
typedef enum {
	SYMBOL_OTHER,
	SYMBOL_FUNCTION,
	SYMBOL_FIELD,
	SYMBOL_TYPE,
	SYMBOL_VARIABLE,
	SYMBOL_CONSTANT,
	SYMBOL_KEYWORD
} SymbolKind;

/// a single autocompletion suggestion
typedef struct {
	char *label;
	char *filter;
	char *text;
	/// this can be NULL!
	char *detail;
	/// this can be NULL!
	char *documentation;
	bool deprecated;
	SymbolKind kind;
} Autocompletion;

enum {
	/// autocomplete/signature help was manually triggered
	TRIGGER_INVOKED = 0x12000,
	/// autocomplete list needs to be updated because more characters were typed
	TRIGGER_INCOMPLETE = 0x12001,
	/// signtaure help needs to be updated because the cursor was moved or
	/// the buffer's contents changed.
	TRIGGER_CONTENT_CHANGE = 0x12002,
};

/// data needed for autocompletion
typedef struct {
	/// is the autocomplete window open?
	bool open;
	/// should the completions array be updated when more characters are typed?
	bool is_list_complete;
	
	/// what trigger caused the last request for completions:
	/// either a character code (for trigger characters),
	/// or one of the `TRIGGER_*` constants above
	uint32_t trigger;
	
	LSPServerRequestID last_request;
	/// when we sent the request to the LSP for completions
	///  (this is used to figure out when we should display "Loading...")
	double last_request_time;
	
	/// dynamic array of all completions
	Autocompletion *completions;
	/// dynamic array of completions to be suggested (indices into completions)
	u32 *suggested;
	/// position of cursor last time completions were generated. if this changes, we need to recompute completions.
	BufferPos last_pos; 
	/// which completion is currently selected (index into suggested)
	i32 cursor;
	i32 scroll;
	
	/// was the last request for phantom completion?
	bool last_request_phantom;
	/// current phantom completion to be displayed
	char *phantom;
	/// rectangle where the autocomplete menu is (needed to avoid interpreting autocomplete clicks as other clicks)
	Rect rect;
} Autocomplete;

/// data needed for finding usages
typedef struct {
	LSPServerRequestID last_request;
	double last_request_time;
} Usages;

/// a single signature in the signature help.
typedef struct {
	/// displayed normal
	char *label_pre;
	/// displayed bold
	char *label_active;
	/// displayed normal
	char *label_post;
} Signature;

/// max number of signatures to display at a time.
#define SIGNATURE_HELP_MAX 5

/// "signature help" (LSP) is thing that shows the current parameter, etc.
typedef struct {
	LSPServerRequestID last_request;
	/// should we resend a signature help request this frame?
	bool retrigger;
	/// if signature_count = 0, signature help is closed
	u16 signature_count;
	Signature signatures[SIGNATURE_HELP_MAX];
} SignatureHelp;

/// "hover" information from LSP server
typedef struct {
	LSPServerRequestID last_request;
	/// is some hover info being displayed?
	bool open;
	/// text to display
	char *text;
	/// where the hover data is coming from.
	/// we use this to check if we need to refresh it.
	LSPDocumentPosition requested_position;
	/// range in document to highlight
	LSPRange range;
	/// how long the cursor has been hovering for
	double time;
} Hover;

/// symbol information for the definitions menu
typedef struct {
	char *name;
	char *detail;
	u32 color;
	/// is this from a LSP server (as opposed to ctags)?
	bool from_lsp;
	/// only set if `from_lsp = true`
	LSPDocumentPosition position;
} SymbolInfo;

/// determines which thing associated with a symbol to go to
typedef enum {
	GOTO_DECLARATION,
	GOTO_DEFINITION,
	GOTO_IMPLEMENTATION,
	GOTO_TYPE_DEFINITION,
} GotoType;

typedef struct {
	LSPServerRequestID last_request;
	double last_request_time;	
	/// last query string which we sent a request for
	char *last_request_query;
	/// for "go to definition of..." menu
	Selector selector;
	/// an array of all definitions (gotten from workspace/symbols) for "go to definition" menu
	SymbolInfo *all_definitions;
} Definitions;

/// "highlight" information from LSP server
typedef struct {
	LSPServerRequestID last_request;
	LSPDocumentPosition requested_position;
	LSPHighlight *highlights;
} Highlights;

/// more severe message types should have higher numbers.
/// they will override less severe messages.
typedef enum {
	MESSAGE_INFO,
	MESSAGE_WARNING,
	MESSAGE_ERROR
} MessageType;

typedef struct {
	Command command;
	CommandArgument argument;
} Action;

typedef struct {
	// dynamic array
	Action *actions;
} Macro;

#define TED_MACRO_MAX 256

/// (almost) all data used by the ted application
typedef struct Ted {
	/// all running LSP servers
	LSP *lsps[TED_LSP_MAX + 1];
	/// current time (see time_get_seconds), as of the start of this frame
	double frame_time;
	
	Macro macros[TED_MACRO_MAX];
	Macro *recording_macro;
	bool executing_macro;
	
	SDL_Window *window;
	Font *font_bold;
	Font *font;
	TextBuffer *active_buffer;
	/// buffer we are currently drag-to-selecting in, if any
	TextBuffer *drag_buffer;
	/// while a menu or something is open, there is no active buffer. when the menu is closed,
	/// the old active buffer needs to be restored. that's what this stores.
	TextBuffer *prev_active_buffer; 
	Node *active_node;
	/// dynamic array of Settings. use Settings.context to figure out which one to use.
	Settings *all_settings;
	/// settings to use when no buffer is open
	Settings *default_settings;
	float window_width, window_height;
	vec2 mouse_pos;
	u32 mouse_state;
	/// `nmouse_clicks[i]` = length of `mouse_clicks[i]`
	u8 nmouse_clicks[4];
	/// `mouse_clicks[SDL_BUTTON_RIGHT]`, for example, is all the right mouse-clicks that have happened this frame
	vec2 mouse_clicks[4][32];
	/// number of times mouse was clicked at each position
	u8 mouse_click_times[4][32];
	u8 nmouse_releases[4];
	vec2 mouse_releases[4][32];
	/// total amount scrolled this frame
	int scroll_total_x, scroll_total_y; 
	/// currently open menu, or \ref MENU_NONE if no menu is open.
	Menu menu;
	FileSelector file_selector;
	Selector command_selector;
	/// general-purpose line buffer for inputs -- used for menus
	TextBuffer line_buffer;
	/// use for "find" term in find/find+replace
	TextBuffer find_buffer;
	/// "replace" for find+replace
	TextBuffer replace_buffer;
	/// buffer for build output (view only)
	TextBuffer build_buffer;
	/// used for command selector
	TextBuffer argument_buffer;
	/// time which the cursor error animation started (cursor turns red, e.g. when there's no autocomplete suggestion)
	double cursor_error_time;
	/// should start_cwd be searched for files? set to true if the executable isn't "installed"
	bool search_start_cwd;
	/// CWD `ted` was started in
	char start_cwd[TED_PATH_MAX];
	/// if set to true, the window will close next frame. NOTE: this doesn't check for unsaved changes!!
	bool quit;
	/// is the find or find+replace menu open?
	bool find;
	/// is the find+replace menu open?
	bool replace;
	/// find options
	bool find_regex, find_case_sensitive;
	/// flags used last time search term was compiled
	u32 find_flags;
	struct pcre2_real_code_32 *find_code;
	struct pcre2_real_match_data_32 *find_match_data;
	FindResult *find_results;
	/// invalid regex?
	bool find_invalid_pattern;
	/// if non-zero, the user is trying to execute this command, but there are unsaved changes
	Command warn_unsaved;
	/// are we showing the build output?
	bool build_shown;
	/// is the build process running?
	bool building;
	Autocomplete autocomplete;
	SignatureHelp signature_help;
	Hover hover;
	Definitions definitions;
	Highlights highlights;
	Usages usages;
	
	FILE *log;
	
	/// dynamic array of build errors
	BuildError *build_errors;
	/// build error we are currently "on"
	u32 build_error;
	
	/// used by menus to keep track of the scroll position so we can return to it.
	vec2d prev_active_buffer_scroll;
	
	SDL_Cursor *cursor_arrow, *cursor_ibeam, *cursor_wait,
		*cursor_resize_h, *cursor_resize_v, *cursor_hand, *cursor_move;
	/// which cursor to use this frame
	/// this should be set to one of the cursor_* members above, or NULL for no cursor
	SDL_Cursor *cursor;
	
	/// node containing tab user is dragging around, NULL if user is not dragging a tab
	Node *dragging_tab_node;
	/// index in dragging_tab_node->tabs
	u16 dragging_tab_idx;
	/// where the tab is being dragged from (i.e. mouse pos at start of drag action)
	vec2 dragging_tab_origin;
	
	/// if not `NULL`, points to the node whose split the user is currently resizing.
	Node *resizing_split;
	
	/// dynamic array of history of commands run with :shell (UTF-8)
	char **shell_history;
	/// for keeping track of where we are in the shell history.
	u32 shell_history_pos;

	// points to a selector if any is open, otherwise NULL.
	Selector *selector_open;
	
	/// what % of the screen the build output takes up
	float build_output_height;
	bool resizing_build_output;
	
	/// last time a save command was executed. used for bg-shaders.
	double last_save_time;

	Process *build_process;
	/// When we read the stdout from the build process, the tail end of the read could be an
	/// incomplete UTF-8 code point. This is where we store that "tail end" until more
	/// data is available. (This is up to 3 bytes, null terminated)
	char build_incomplete_codepoint[4];
	/// allows execution of multiple commands -- needed for tags generation
	char **build_queue;
	/// comma-separated list of files with unsaved changes (only applicable if warn_unsaved != 0)
	char warn_unsaved_names[TED_PATH_MAX];
	/// file name user is trying to overwrite
	char warn_overwrite[TED_PATH_MAX];
	/// file name which we want to reload
	char ask_reload[TED_PATH_MAX];
	char local_data_dir[TED_PATH_MAX];
	char global_data_dir[TED_PATH_MAX];
	/// home directory
	char home[TED_PATH_MAX];
	/// current working directory
	char cwd[TED_PATH_MAX];
	/// directory where we run the build command
	char build_dir[TED_PATH_MAX];
	/// where we are reading tags from
	char tags_dir[TED_PATH_MAX];
	bool nodes_used[TED_MAX_NODES];
	/// `nodes[0]` is always the "root node", if any buffers are open.
	Node nodes[TED_MAX_NODES];
	/// NOTE: the buffer at index 0 is reserved as a "null buffer" and should not be used.
	bool buffers_used[TED_MAX_BUFFERS];
	TextBuffer buffers[TED_MAX_BUFFERS];
	/// number of config file strings
	u32 nstrings;
	/// config file strings
	char *strings[TED_MAX_STRINGS];
	char window_title[256];
	
	/// little box used to display errors and info.
	char message[512];
	/// time message box was opened
	double message_time;
	MessageType message_type;
	MessageType message_shown_type;
	char message_shown[512];
} Ted;

// === buffer.c ===
/// Does this buffer have an error?
bool buffer_has_error(TextBuffer *buffer);
/// get buffer error
const char *buffer_get_error(TextBuffer *buffer);
/// clear buffer error
void buffer_clear_error(TextBuffer *buffer);
/// clear undo and redo history
void buffer_clear_undo_redo(TextBuffer *buffer);
/// is this buffer empty?
bool buffer_empty(TextBuffer *buffer);
/// returns the buffer's filename (not full path), or "Untitled" if this buffer is untitled.
const char *buffer_display_filename(TextBuffer *buffer);
/// does this buffer contained a named file (i.e. not a line buffer, not the build buffer, not untitled)
bool buffer_is_named_file(TextBuffer *buffer);
/// create a new empty buffer with no file name
void buffer_create(TextBuffer *buffer, Ted *ted);
/// create a new empty line buffer
void line_buffer_create(TextBuffer *buffer, Ted *ted);
/// does this buffer have unsaved changes?
bool buffer_unsaved_changes(TextBuffer *buffer);
/// returns the character after position pos, or 0 if pos is invalid
char32_t buffer_char_at_pos(TextBuffer *buffer, BufferPos pos);
/// returns the character after the cursor
char32_t buffer_char_at_cursor(TextBuffer *buffer);
/// returns the character before position pos, or 0 if pos is invalid or at the start of a line
char32_t buffer_char_before_pos(TextBuffer *buffer, BufferPos pos);
/// returns the character to the left of the cursor, or 0 if the cursor at the start of the line.
char32_t buffer_char_before_cursor(TextBuffer *buffer);
/// buffer position of start of file
BufferPos buffer_pos_start_of_file(TextBuffer *buffer);
/// buffer position of end of file
BufferPos buffer_pos_end_of_file(TextBuffer *buffer);
/// move position to matching bracket. returns true if there was a bracket at the position, otherwise returns false and does nothing.
bool buffer_pos_move_to_matching_bracket(TextBuffer *buffer, BufferPos *pos);
/// move cursor to matching bracket. returns true if cursor was to the right of a bracket.
bool buffer_cursor_move_to_matching_bracket(TextBuffer *buffer);
/// ensures that `p` refers to a valid position, moving it if needed.
void buffer_pos_validate(TextBuffer *buffer, BufferPos *p);
/// is this a valid buffer position?
bool buffer_pos_valid(TextBuffer *buffer, BufferPos p);
Language buffer_language(TextBuffer *buffer);
/// clip the rectangle so it's all inside the buffer. returns true if there's any rectangle left.
bool buffer_clip_rect(TextBuffer *buffer, Rect *r);
/// get LSP server which deals with this buffer
LSP *buffer_lsp(TextBuffer *buffer);
/// Get the settings used for this buffer.
Settings *buffer_settings(TextBuffer *buffer);
/// Get tab width for this buffer
u8 buffer_tab_width(TextBuffer *buffer);
/// Get whether or not to indent with spaces for this buffer.
bool buffer_indent_with_spaces(TextBuffer *buffer);
/// NOTE: this string will be invalidated when the line is edited!!!
/// only use it briefly!!
/// returns an empty string if `line_number` is out of range.
String32 buffer_get_line(TextBuffer *buffer, u32 line_number);
/// get at most `nchars` characters starting from position `pos`.
/// returns the number of characters actually available.
/// you can pass NULL for text if you just want to know how many
/// characters *could* be accessed before the end of the file.
size_t buffer_get_text_at_pos(TextBuffer *buffer, BufferPos pos, char32_t *text, size_t nchars);
/// returns a UTF-32 string of at most `nchars` code points from `buffer` starting at `pos`
/// the string should be passed to str32_free.
String32 buffer_get_str32_text_at_pos(TextBuffer *buffer, BufferPos pos, size_t nchars);
/// get UTF-8 string at position, up to `nchars` code points (NOT bytes).
/// the resulting string should be freed.
char *buffer_get_utf8_text_at_pos(TextBuffer *buffer, BufferPos pos, size_t nchars);
/// Puts a UTF-8 string containing the contents of the buffer into out.
/// Returns the number of bytes, including a null terminator.
/// To use this function, first pass NULL for out to get the number of bytes you need to allocate.
size_t buffer_contents_utf8(TextBuffer *buffer, char *out);
/// Returns a UTF-8 string containing the contents of `buffer`.
/// The return value should be freed..
char *buffer_contents_utf8_alloc(TextBuffer *buffer);
/// perform a series of checks to make sure the buffer doesn't have any invalid values
void buffer_check_valid(TextBuffer *buffer);
/// free all resources used by the buffer
void buffer_free(TextBuffer *buffer);
/// clear buffer contents
void buffer_clear(TextBuffer *buffer);
/// returns the length of the `line_number`th line (0-indexed),
/// or 0 if `line_number` is out of range.
u32 buffer_line_len(TextBuffer *buffer, u32 line_number);
/// returns the number of lines of text in the buffer into *lines (if not NULL),
/// and the number of columns of text, i.e. the number of columns in the longest line displayed, into *cols (if not NULL)
void buffer_text_dimensions(TextBuffer *buffer, u32 *lines, u32 *columns);
/// returns the number of rows of text that can fit in the buffer
float buffer_display_lines(TextBuffer *buffer);
/// returns the number of columns of text that can fit in the buffer
float buffer_display_cols(TextBuffer *buffer);
void buffer_scroll(TextBuffer *buffer, double dx, double dy);
/// returns the screen position of the character at the given position in the buffer.
vec2 buffer_pos_to_pixels(TextBuffer *buffer, BufferPos pos);
/// convert pixel coordinates to a position in the buffer, selecting the closest character.
/// returns false if the position is not inside the buffer, but still sets *pos to the closest character.
bool buffer_pixels_to_pos(TextBuffer *buffer, vec2 pixel_coords, BufferPos *pos);
/// scroll to `pos`, scrolling as little as possible while maintaining scrolloff.
void buffer_scroll_to_pos(TextBuffer *buffer, BufferPos pos);
/// scroll in such a way that this position is in the center of the screen
void buffer_scroll_center_pos(TextBuffer *buffer, BufferPos pos);
void buffer_scroll_to_cursor(TextBuffer *buffer);
/// scroll so that the cursor is in the center of the buffer's rectangle.
void buffer_center_cursor(TextBuffer *buffer);
/// returns the number of characters successfully moved by.
i64 buffer_pos_move_left(TextBuffer *buffer, BufferPos *pos, i64 by);
/// returns the number of characters successfully moved by.
i64 buffer_pos_move_right(TextBuffer *buffer, BufferPos *pos, i64 by);
/// returns the number of lines successfully moved by.
i64 buffer_pos_move_up(TextBuffer *buffer, BufferPos *pos, i64 by);
/// returns the number of lines successfully moved by.
i64 buffer_pos_move_down(TextBuffer *buffer, BufferPos *pos, i64 by);
void buffer_cursor_move_to_pos(TextBuffer *buffer, BufferPos pos);
/// returns the number of characters successfully moved by.
i64 buffer_cursor_move_left(TextBuffer *buffer, i64 by);
/// returns the number of characters successfully moved by.
i64 buffer_cursor_move_right(TextBuffer *buffer, i64 by);
/// returns the number of lines successfully moved by.
i64 buffer_cursor_move_up(TextBuffer *buffer, i64 by);
/// returns the number of lines successfully moved by.
i64 buffer_cursor_move_down(TextBuffer *buffer, i64 by);
/// returns the number of blank lines successfully moved by.
i64 buffer_pos_move_up_blank_lines(TextBuffer *buffer, BufferPos *pos, i64 by);
/// returns the number of blank lines successfully moved by.
i64 buffer_pos_move_down_blank_lines(TextBuffer *buffer, BufferPos *pos, i64 by);
/// returns the number of blank lines successfully moved by.
i64 buffer_cursor_move_up_blank_lines(TextBuffer *buffer, i64 by);
/// returns the number of blank lines successfully moved by.
i64 buffer_cursor_move_down_blank_lines(TextBuffer *buffer, i64 by);
/// returns the number of words successfully moved by.
i64 buffer_pos_move_words(TextBuffer *buffer, BufferPos *pos, i64 nwords);
/// returns the number of words successfully moved by.
i64 buffer_pos_move_left_words(TextBuffer *buffer, BufferPos *pos, i64 nwords);
/// returns the number of words successfully moved by.
i64 buffer_pos_move_right_words(TextBuffer *buffer, BufferPos *pos, i64 nwords);
/// returns the number of words successfully moved by.
i64 buffer_cursor_move_left_words(TextBuffer *buffer, i64 nwords);
/// returns the number of words successfully moved by.
i64 buffer_cursor_move_right_words(TextBuffer *buffer, i64 nwords);
/// move cursor to "previous" position (i.e. \ref CMD_PREVIOUS_POSITION)
void buffer_cursor_move_to_prev_pos(TextBuffer *buffer);
/// Returns a string of word characters (see is32_word) around the position,
/// or an empty string if neither of the characters to the left and right of the cursor are word characters.
/// NOTE: The string is invalidated when the buffer is changed!!!
/// The return value should NOT be freed.
String32 buffer_word_at_pos(TextBuffer *buffer, BufferPos pos);
/// Get the word at the cursor.
/// NOTE: The string is invalidated when the buffer is changed!!!
/// The return value should NOT be freed.
String32 buffer_word_at_cursor(TextBuffer *buffer);
/// Get a UTF-8 string consisting of the word at the cursor.
/// The return value should be freed.
char *buffer_word_at_cursor_utf8(TextBuffer *buffer);
/// Used for \ref CMD_INCREMENT_NUMBER and \ref CMD_DECREMENT_NUMBER
///
/// Moves `*ppos` to the start of the (new) number.
/// returns false if there was no number at `*ppos`.
bool buffer_change_number_at_pos(TextBuffer *buffer, BufferPos *ppos, i64 by);
/// Used for \ref CMD_INCREMENT_NUMBER and \ref CMD_DECREMENT_NUMBER
void buffer_change_number_at_cursor(TextBuffer *buffer, i64 argument);
/// Buffer position corresponding to the start of line `line` (0-indexed).
BufferPos buffer_pos_start_of_line(TextBuffer *buffer, u32 line);
/// Buffer position corresponding to the end of line `line` (0-indexed).
BufferPos buffer_pos_end_of_line(TextBuffer *buffer, u32 line);
/// Move cursor to the start of the line, like the Home key does.
void buffer_cursor_move_to_start_of_line(TextBuffer *buffer);
/// Move cursor to the end of the line, like the End key does.
void buffer_cursor_move_to_end_of_line(TextBuffer *buffer);
/// Move cursor to the start of the file, like Ctrl+Home does.
void buffer_cursor_move_to_start_of_file(TextBuffer *buffer);
/// Move cursor to the end of the file, like Ctrl+End does.
void buffer_cursor_move_to_end_of_file(TextBuffer *buffer);
/// Get the LSPDocumentID corresponding to the file this buffer contains.
/// The return value is only useful if `buffer_lsp(buffer) != NULL`.
LSPDocumentID buffer_lsp_document_id(TextBuffer *buffer);
/// Get LSPPosition corresponding to position in buffer.
LSPPosition buffer_pos_to_lsp_position(TextBuffer *buffer, BufferPos pos);
/// Get LSPDocumentPosition corresponding to position in buffer.
LSPDocumentPosition buffer_pos_to_lsp_document_position(TextBuffer *buffer, BufferPos pos);
/// Convert LSPPosition to BufferPos.
BufferPos buffer_pos_from_lsp(TextBuffer *buffer, LSPPosition lsp_pos);
/// Get the cursor position as an LSPPosition.
LSPPosition buffer_cursor_pos_as_lsp_position(TextBuffer *buffer);
/// Get the cursor position as an LSPDocumentPosition.
LSPDocumentPosition buffer_cursor_pos_as_lsp_document_position(TextBuffer *buffer);
/// Put text at a position. All text insertion should eventually go through this function.
BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 str);
/// Insert a single character at a position.
void buffer_insert_char_at_pos(TextBuffer *buffer, BufferPos pos, char32_t c);
/// Set the selection to between `buffer->cursor_pos` and `pos`,
/// and move the cursor to `pos`.
void buffer_select_to_pos(TextBuffer *buffer, BufferPos pos);
/// Like shift+left, move cursor `nchars` chars to the left, selecting everything in between.
void buffer_select_left(TextBuffer *buffer, i64 nchars);
/// Like shift+right, move cursor `nchars` chars to the right, selecting everything in between.
void buffer_select_right(TextBuffer *buffer, i64 nchars);
/// Like shift+down, move cursor `nchars` lines down, selecting everything in between.
void buffer_select_down(TextBuffer *buffer, i64 nchars);
/// Like shift+up, move cursor `nchars` lines up, selecting everything in between.
void buffer_select_up(TextBuffer *buffer, i64 nchars);
/// Move the cursor `by` lines down, selecting everything in between.
void buffer_select_down_blank_lines(TextBuffer *buffer, i64 by);
/// Move the cursor `by` lines up, selecting everything in between.
void buffer_select_up_blank_lines(TextBuffer *buffer, i64 by);
/// Move the cursor `nwords` words left, selecting everything in between.
void buffer_select_left_words(TextBuffer *buffer, i64 nwords);
/// Move the cursor `nwords` words right, selecting everything in between.
void buffer_select_right_words(TextBuffer *buffer, i64 nwords);
/// Like Shift+Home, move cursor to start of line and select everything in between.
void buffer_select_to_start_of_line(TextBuffer *buffer);
/// Like Shift+End, move cursor to end of line and select everything in between.
void buffer_select_to_end_of_line(TextBuffer *buffer);
/// Like Ctrl+Shift+Home, move cursor to start of file and select everything in between.
void buffer_select_to_start_of_file(TextBuffer *buffer);
/// Like Ctrl+Shift+End, move cursor to end of file and select everything in between.
void buffer_select_to_end_of_file(TextBuffer *buffer);
/// select the word the cursor is inside of
void buffer_select_word(TextBuffer *buffer);
/// select the line the cursor is currently on
void buffer_select_line(TextBuffer *buffer);
/// select all of the buffer's contents
void buffer_select_all(TextBuffer *buffer);
/// Remove current selection.
void buffer_deselect(TextBuffer *buffer);
/// Scroll up by `npages` pages
void buffer_page_up(TextBuffer *buffer, i64 npages);
/// Scroll down by `npages` pages
void buffer_page_down(TextBuffer *buffer, i64 npages);
/// Scroll up by `npages` pages, selecting everything in between
void buffer_select_page_up(TextBuffer *buffer, i64 npages);
/// Scroll down by `npages` pages, selecting everything in between
void buffer_select_page_down(TextBuffer *buffer, i64 npages);
/// Delete `nchars` characters starting from `pos`.
/// All text deletion should eventually go through this function.
void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars);
/// Delete characters between the two positions.
/// The order of `p1` and `p2` is irrelevant.
i64 buffer_delete_chars_between(TextBuffer *buffer, BufferPos p1, BufferPos p2);
/// Delete current selection.
i64 buffer_delete_selection(TextBuffer *buffer);
/// Insert UTF-32 text at the cursor, and move the cursor to the end of it.
void buffer_insert_text_at_cursor(TextBuffer *buffer, String32 str);
/// Insert a single character at the cursor, and move the cursor past it.
void buffer_insert_char_at_cursor(TextBuffer *buffer, char32_t c);
/// Insert UTF-8 text at the position.
void buffer_insert_utf8_at_pos(TextBuffer *buffer, BufferPos pos, const char *utf8);
/// Insert UTF-8 text at the cursor, and move the cursor to the end of it.
void buffer_insert_utf8_at_cursor(TextBuffer *buffer, const char *utf8);
/// Insert a "tab" at the cursor position, and move the cursor past it.
/// This inserts spaces if ted is configured to indent with spaces.
void buffer_insert_tab_at_cursor(TextBuffer *buffer);
/// Insert a newline at the cursor position.
/// If `buffer` is a line buffer, this "submits" the buffer.
/// If not, this auto-indents the next line, and moves the cursor to it.
void buffer_newline(TextBuffer *buffer);
/// Delete `nchars` characters after the cursor.
void buffer_delete_chars_at_cursor(TextBuffer *buffer, i64 nchars);
/// Delete `nchars` characters before *pos, and set *pos to just before the deleted characters.
/// Returns the number of characters actually deleted.
i64 buffer_backspace_at_pos(TextBuffer *buffer, BufferPos *pos, i64 nchars);
/// Delete `nchars` characters before the cursor position, and set the cursor position accordingly.
/// Returns the number of characters actually deleted.
i64 buffer_backspace_at_cursor(TextBuffer *buffer, i64 nchars);
/// Delete `nwords` words after the position.
void buffer_delete_words_at_pos(TextBuffer *buffer, BufferPos pos, i64 nwords);
/// Delete `nwords` words after the cursor.
void buffer_delete_words_at_cursor(TextBuffer *buffer, i64 nwords);
/// Delete `nwords` words before *pos, and set *pos to just before the deleted words.
/// Returns the number of words actually deleted.
void buffer_backspace_words_at_pos(TextBuffer *buffer, BufferPos *pos, i64 nwords);
/// Delete `nwords` words before the cursor position, and set the cursor position accordingly.
/// Returns the number of words actually deleted.
void buffer_backspace_words_at_cursor(TextBuffer *buffer, i64 nwords);
/// Undo `ntimes` times
void buffer_undo(TextBuffer *buffer, i64 ntimes);
/// Redo `ntimes` times
void buffer_redo(TextBuffer *buffer, i64 ntimes);
/// Start a new "edit chain". Undoing once after an edit chain will undo everything in the chain.
void buffer_start_edit_chain(TextBuffer *buffer);
/// End the edit chain.
void buffer_end_edit_chain(TextBuffer *buffer);
/// Copy the current selection to the clipboard.
void buffer_copy(TextBuffer *buffer);
/// Copy the current selection to the clipboard, and delete it.
void buffer_cut(TextBuffer *buffer);
/// Insert the clipboard contents at the cursor position.
void buffer_paste(TextBuffer *buffer);
/// Load the file `path`. If `path` is not an absolute path,
/// this function will fail.
bool buffer_load_file(TextBuffer *buffer, const char *path);
/// Reloads the file loaded in the buffer.
void buffer_reload(TextBuffer *buffer);
/// has this buffer been changed by another program since last save?
bool buffer_externally_changed(TextBuffer *buffer);
/// Clear `buffer`, and set its path to `path`.
/// if `path` is NULL, this will turn `buffer` into an untitled buffer.
void buffer_new_file(TextBuffer *buffer, const char *path);
/// Save the buffer to its current filename. This will rewrite the entire file,
/// even if there are no unsaved changes.
bool buffer_save(TextBuffer *buffer);
/// save, but with a different path
bool buffer_save_as(TextBuffer *buffer, const char *new_filename);
/// index of first line that will be displayed on screen
u32 buffer_first_rendered_line(TextBuffer *buffer);
/// index of last line that will be displayed on screen
u32 buffer_last_rendered_line(TextBuffer *buffer);
/// go to the definition/declaration/etc of the word at the cursor.
void buffer_goto_word_at_cursor(TextBuffer *buffer, GotoType type);
/// process a mouse click.
/// returns true if the event was consumed.
bool buffer_handle_click(Ted *ted, TextBuffer *buffer, vec2 click, u8 times);
/// render the buffer in the given rectangle
void buffer_render(TextBuffer *buffer, Rect r);
/// indent the given lines
void buffer_indent_lines(TextBuffer *buffer, u32 first_line, u32 last_line);
/// de-indent the given lines
void buffer_dedent_lines(TextBuffer *buffer, u32 first_line, u32 last_line);
/// indent the selected lines
void buffer_indent_selection(TextBuffer *buffer);
/// de-indent the selected lines
void buffer_dedent_selection(TextBuffer *buffer);
/// indent the line the cursor is on
void buffer_indent_cursor_line(TextBuffer *buffer);
/// de-indent the line the cursor is on
void buffer_dedent_cursor_line(TextBuffer *buffer);
/// comment the lines from `first_line` to `last_line`
void buffer_comment_lines(TextBuffer *buffer, u32 first_line, u32 last_line);
/// uncomment the lines from `first_line` to `last_line`
void buffer_uncomment_lines(TextBuffer *buffer, u32 first_line, u32 last_line);
/// comment the lines from `first_line` to `last_line`, or uncomment them if they're all commented.
void buffer_toggle_comment_lines(TextBuffer *buffer, u32 first_line, u32 last_line);
/// comment the selected lines, or uncomment them if they're all commented
void buffer_toggle_comment_selection(TextBuffer *buffer);
/// highlight an \ref LSPRange in this buffer.
///
/// make sure to call \ref gl_geometry_draw after this
void buffer_highlight_lsp_range(TextBuffer *buffer, LSPRange range, ColorSetting color);
/// returns true if `p1` and `p2` are equal
bool buffer_pos_eq(BufferPos p1, BufferPos p2);
/// returns `-1` if `p1` comes before `p2`
///
/// `+1` if `p1` comes after `p2`
///
/// `0`  if `p1` = `p2`
///
/// faster than \ref buffer_pos_diff (constant time)
int buffer_pos_cmp(BufferPos p1, BufferPos p2);
/// returns "`p2 - p1`", that is, the number of characters between `p1` and `p2`.
i64 buffer_pos_diff(TextBuffer *buffer, BufferPos p1, BufferPos p2);

// === build.c ===
/// clear build errors and stop
void build_stop(Ted *ted);
/// call before adding anything to the build queue
void build_queue_start(Ted *ted);
/// add a command to the build queue. call build_queue_start before this.
void build_queue_command(Ted *ted, const char *command);
/// call this after calling build_queue_start, build_queue_command.
/// make sure you set ted->build_dir before running this!
void build_queue_finish(Ted *ted);
/// set up the build output buffer.
void build_setup_buffer(Ted *ted);
/// run a single command in the build window.
/// make sure you set ted->build_dir before running this!
void build_start_with_command(Ted *ted, const char *command);
/// figure out which build command to run, and run it.
void build_start(Ted *ted);
/// go to next build error
void build_next_error(Ted *ted);
/// go to previous build error
void build_prev_error(Ted *ted);
/// find build errors in build buffer.
void build_check_for_errors(Ted *ted);
void build_frame(Ted *ted, float x1, float y1, float x2, float y2);

// === colors.c ===
void color_init(void);
ColorSetting color_setting_from_str(const char *str);
const char *color_setting_to_str(ColorSetting s);
Status color_from_str(const char *str, u32 *color);
/// which color setting should be used for the given symbol kind.
/// this is the color used in the autocomplete selector, for example.
ColorSetting color_for_symbol_kind(SymbolKind kind);
/// perform SRC_ALPHA, ONE_MINUS_SRC_ALPHA blending with `bg` and `fg`.
u32 color_blend(u32 bg, u32 fg);
/// multiply color's alpha value by `opacity`.
u32 color_apply_opacity(u32 color, float opacity);

// === command.c ===
void command_init(void);
Command command_from_str(const char *str);
const char *command_to_str(Command c);
void command_execute(Ted *ted, Command c, i64 argument);
void command_execute_ex(Ted *ted, Command c, CommandArgument argument, CommandContext context);

// === config.c ===
/// first, we read all config files, then we parse them.
/// this is because we want less specific settings (e.g. settings applied
/// to all languages instead of one particular language) to be applied first,
/// then more specific settings are based off of those.
///
/// EXAMPLE:
/// ```
///   ---config file 1---
///     [Javascript.core]
///     syntax-highlighting = off
///     (inherits tab-width = 4)
///     [CSS.core]
///     tab-width = 2 (overrides tab-width = 4)
///   ---config file 2---
///     [core]
///     tab-width = 4
/// ```
void config_read(Ted *ted, ConfigPart **pparts, const char *filename);
void config_parse(Ted *ted, ConfigPart **pparts);
void config_free(Ted *ted);
/// returns the best guess for the root directory of the project containing `path`
/// (which should be an absolute path).
/// the return value should be freed.
char *settings_get_root_dir(Settings *settings, const char *path);
/// how well does this settings context fit the given path and language?
/// the context with the highest score will be chosen.
long context_score(const char *path, Language lang, const SettingsContext *context);

// === find.c ===
/// which buffer will be searched?
TextBuffer *find_search_buffer(Ted *ted);
/// height of the find/find+replace menu in pixels
float find_menu_height(Ted *ted);
/// update find results.
/// if `force` is true, the results will be updated even if the pattern & flags have not been changed.
void find_update(Ted *ted, bool force);
/// replace the match we are currently highlighting, or do nothing if there is no highlighted match
void find_replace(Ted *ted);
/// go to next find result
void find_next(Ted *ted);
/// go to previous find result
void find_prev(Ted *ted);
/// replace all matches
void find_replace_all(Ted *ted);
void find_menu_frame(Ted *ted, Rect menu_bounds);
/// open the find/find+replace menu.
void find_open(Ted *ted, bool replace);
/// close the find/find+replace menu.
void find_close(Ted *ted);

// === gl.c ===
/// set by main()
extern float gl_window_width, gl_window_height;
/// set by main()
extern int gl_version_major, gl_version_minor;

/// macro trickery to avoid having to write every GL function multiple times
#define gl_for_each_proc(do)\
	do(DRAWARRAYS, DrawArrays)\
	do(GENTEXTURES, GenTextures)\
	do(DELETETEXTURES, DeleteTextures)\
	do(GENERATEMIPMAP, GenerateMipmap)\
	do(TEXIMAGE2D, TexImage2D)\
	do(BINDTEXTURE, BindTexture)\
	do(TEXPARAMETERI, TexParameteri)\
	do(GETERROR, GetError)\
	do(GETINTEGERV, GetIntegerv)\
	do(ENABLE, Enable)\
	do(DISABLE, Disable)\
	do(BLENDFUNC, BlendFunc)\
	do(VIEWPORT, Viewport)\
	do(CLEARCOLOR, ClearColor)\
	do(CLEAR, Clear)\
	do(FINISH, Finish)\
	do(CREATESHADER, CreateShader)\
	do(DELETESHADER, DeleteShader)\
	do(CREATEPROGRAM, CreateProgram)\
	do(SHADERSOURCE, ShaderSource)\
	do(GETSHADERIV, GetShaderiv)\
	do(GETSHADERINFOLOG, GetShaderInfoLog)\
	do(COMPILESHADER, CompileShader)\
	do(CREATEPROGRAM, CreateProgram)\
	do(DELETEPROGRAM, DeleteProgram)\
	do(ATTACHSHADER, AttachShader)\
	do(LINKPROGRAM, LinkProgram)\
	do(GETPROGRAMIV, GetProgramiv)\
	do(GETPROGRAMINFOLOG, GetProgramInfoLog)\
	do(USEPROGRAM, UseProgram)\
	do(GETATTRIBLOCATION, GetAttribLocation)\
	do(GETUNIFORMLOCATION, GetUniformLocation)\
	do(GENBUFFERS, GenBuffers)\
	do(DELETEBUFFERS, DeleteBuffers)\
	do(BINDBUFFER, BindBuffer)\
	do(BUFFERDATA, BufferData)\
	do(VERTEXATTRIBPOINTER, VertexAttribPointer)\
	do(ENABLEVERTEXATTRIBARRAY, EnableVertexAttribArray)\
	do(DISABLEVERTEXATTRIBARRAY, DisableVertexAttribArray)\
	do(GENVERTEXARRAYS, GenVertexArrays)\
	do(DELETEVERTEXARRAYS, DeleteVertexArrays)\
	do(BINDVERTEXARRAY, BindVertexArray)\
	do(ACTIVETEXTURE, ActiveTexture)\
	do(UNIFORM1F, Uniform1f)\
	do(UNIFORM2F, Uniform2f)\
	do(UNIFORM3F, Uniform3f)\
	do(UNIFORM4F, Uniform4f)\
	do(UNIFORM1I, Uniform1i)\
	do(UNIFORM2I, Uniform2i)\
	do(UNIFORM3I, Uniform3i)\
	do(UNIFORM4I, Uniform4i)\
	do(UNIFORMMATRIX4FV, UniformMatrix4fv)\
	do(DEBUGMESSAGECALLBACK, DebugMessageCallback)\
	do(DEBUGMESSAGECONTROL, DebugMessageControl)\

#define gl_declare_proc(upper, lower) extern PFNGL##upper##PROC gl##lower;
gl_for_each_proc(gl_declare_proc)
#undef gl_declare_proc

/// get addresses of GL functions
void gl_get_procs(void);
/// create a new reference-counted shader-array-buffer object.
GlRcSAB *gl_rc_sab_new(GLuint shader, GLuint array, GLuint buffer);
/// increase reference count on `s`.
void gl_rc_sab_incref(GlRcSAB *s);
/// decrease reference count on `*ps`, and set `*ps` to NULL if the reference count is 0.
void gl_rc_sab_decref(GlRcSAB **ps);
/// create a new reference-counted texture.
GlRcTexture *gl_rc_texture_new(GLuint texture);
/// increase reference count on `t`.
void gl_rc_texture_incref(GlRcTexture *t);
/// decrease reference count on `*t`, and set `*t` to NULL if the reference count is 0.
void gl_rc_texture_decref(GlRcTexture **pt);
/// create and compile a shader
GLuint gl_compile_shader(char error_buf[256], const char *code, GLenum shader_type);
/// create new shader program from shaders
GLuint gl_link_program(char error_buf[256], GLuint *shaders, size_t count);
/// create a shader program from vertex shader and fragment shader source
GLuint gl_compile_and_link_shaders(char error_buf[256], const char *vshader_code, const char *fshader_code);
/// prints a debug message if `attrib` is not found
GLuint gl_attrib_location(GLuint program, const char *attrib);
/// prints a debug message if `uniform` is not found
GLint gl_uniform_location(GLuint program, const char *uniform);
/// initialize geometry stuff
void gl_geometry_init(void);
/// queue a filled rectangle with the given color.
void gl_geometry_rect(Rect r, u32 color_rgba);
/// queue the border of a rectangle with the given color.
void gl_geometry_rect_border(Rect r, float border_thickness, u32 color);
/// draw all queued geometry
void gl_geometry_draw(void);
/// create an OpenGL texture object from an image file.
GLuint gl_load_texture_from_image(const char *path);

// === ide-autocomplete.c ===
/// open autocomplete
/// trigger should either be a character (e.g. '.') or one of the TRIGGER_* constants.
void autocomplete_open(Ted *ted, uint32_t trigger);
void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *response);
/// select the completion the cursor is on,
/// or select the phantom completion if there is one.
void autocomplete_select_completion(Ted *ted);
/// scroll completion list
void autocomplete_scroll(Ted *ted, i32 by);
/// move cursor to next completion
void autocomplete_next(Ted *ted);
/// move cursor to previous completion
void autocomplete_prev(Ted *ted);
/// close completion menu
void autocomplete_close(Ted *ted);
void autocomplete_frame(Ted *ted);

// === ide-definitions.c ===
/// go to the definition of `name`.
/// if `lsp` is NULL, tags will be used.
/// Note: the document position is required for LSP requests because of overloading (where the name
/// alone isn't sufficient)
void definition_goto(Ted *ted, LSP *lsp, const char *name, LSPDocumentPosition pos, GotoType type);
/// cancel the last go-to-definition / find symbols request.
void definition_cancel_lookup(Ted *ted);
void definitions_process_lsp_response(Ted *ted, LSP *lsp, const LSPResponse *response);
/// open the definitions menu
void definitions_selector_open(Ted *ted);
/// update the definitions menu
void definitions_selector_update(Ted *ted);
void definitions_selector_render(Ted *ted, Rect bounds);
/// close the definitions menu
void definitions_selector_close(Ted *ted);
void definitions_frame(Ted *ted);

// === ide-highlights.c ===
void highlights_close(Ted *ted);
void highlights_process_lsp_response(Ted *ted, LSPResponse *response);
void highlights_frame(Ted *ted);

// === ide-hover.c ===
void hover_close(Ted *ted);
void hover_process_lsp_response(Ted *ted, LSPResponse *response);
void hover_frame(Ted *ted, double dt);

// === ide-signature-help.c ===
/// figure out new signature help
void signature_help_retrigger(Ted *ted);
/// open signature help. `trigger` should either be the trigger character (e.g. ',')
/// or one of the TRIGGER_* constants.
void signature_help_open(Ted *ted, uint32_t trigger);
bool signature_help_is_open(Ted *ted);
void signature_help_close(Ted *ted);
void signature_help_process_lsp_response(Ted *ted, const LSPResponse *response);
void signature_help_frame(Ted *ted);

// === ide-usages.c ===
/// cancel the last "find usages" request
void usages_cancel_lookup(Ted *ted);
/// find usages for word under the cursor in the active buffer.
void usages_find(Ted *ted);
void usages_process_lsp_response(Ted *ted, const LSPResponse *response);
void usages_frame(Ted *ted);

// === macro.c ===
void macro_start_recording(Ted *ted, u32 index);
void macro_stop_recording(Ted *ted);
void macro_add(Ted *ted, Command command, CommandArgument argument);
void macro_execute(Ted *ted, u32 index);
void macros_free(Ted *ted);

// === menu.c ===
void menu_close(Ted *ted);
void menu_open(Ted *ted, Menu menu);
/// process a :escape command (by default this happens when the escape key is pressed)
void menu_escape(Ted *ted);
/// get width of menu in pixels
float menu_get_width(Ted *ted);
/// get rectangle which menu takes up
Rect menu_rect(Ted *ted);
void menu_update(Ted *ted);
void menu_render(Ted *ted);
/// move to next/previous command
void menu_shell_move(Ted *ted, int direction);
/// move to previous command
void menu_shell_up(Ted *ted);
/// move to next command
void menu_shell_down(Ted *ted);

// === node.c ===
void node_switch_to_tab(Ted *ted, Node *node, u16 new_tab_index);
/// go to the `n`th next tab (e.g. `n=1` goes to the next tab)
/// going past the end of the tabs will "wrap around" to the first one.
void node_tab_next(Ted *ted, Node *node, i64 n);
/// go to the `n`th previous tab (e.g. `n=1` goes to the previous tab)
/// going before the first tab will "wrap around" to the last one.
void node_tab_prev(Ted *ted, Node *node, i64 n);
/// switch to a specific tab. if `tab` is out of range, nothing happens.
void node_tab_switch(Ted *ted, Node *node, i64 tab);
/// swap the position of two tabs
void node_tabs_swap(Node *node, u16 tab1, u16 tab2);
void node_free(Node *node);
/// returns index of parent in ted->nodes, or -1 if this is the root node.
i32 node_parent(Ted *ted, u16 node_idx);
/// join this node with its sibling
void node_join(Ted *ted, Node *node);
/// close a node, WITHOUT checking for unsaved changes
void node_close(Ted *ted, u16 node_idx);
/// close tab, WITHOUT checking for unsaved changes!
/// returns true if the node is still open
bool node_tab_close(Ted *ted, Node *node, u16 index);
void node_frame(Ted *ted, Node *node, Rect r);
/// make a split
void node_split(Ted *ted, Node *node, bool vertical);
/// switch to the other side of the current split.
void node_split_switch(Ted *ted);
/// swap the two sides of the current split.
void node_split_swap(Ted *ted);

// === session.c ===
void session_write(Ted *ted);
void session_read(Ted *ted);

// === syntax.c ===
/// register a new language for `ted`.
///
/// this should be done before loading configs so language-specific settings are recognized properly.
void syntax_register_language(const LanguageInfo *info);
/// register ted's built-in languages.
void syntax_register_builtin_languages(void);
/// returns `true` if `language` is a valid language ID
bool language_is_valid(Language language);
/// read language name from `str`. returns `LANG_NONE` if `str` is invalid.
Language language_from_str(const char *str);
/// convert language to string
const char *language_to_str(Language language);
/// get the color setting associated with the given syntax highlighting type
ColorSetting syntax_char_type_to_color_setting(SyntaxCharType t);
/// returns ')' for '(', etc., or 0 if c is not an opening bracket
char32_t syntax_matching_bracket(Language lang, char32_t c);
/// returns true for opening brackets, false for closing brackets/non-brackets
bool syntax_is_opening_bracket(Language lang, char32_t c);
/// This is the main syntax highlighting function. It will determine which colors to use for each character.
/// Rather than returning colors, it returns a character type (e.g. comment) which can be converted to a color.
/// To highlight multiple lines, start out with a zeroed SyntaxState, and pass a pointer to it each time.
/// You can set char_types to NULL if you just want to advance the state, and don't care about the character types.
void syntax_highlight(SyntaxState *state, Language lang, const char32_t *line, u32 line_len, SyntaxCharType *char_types);

// === tags.c ===
void tags_generate(Ted *ted, bool run_in_build_window);
/// find all tags beginning with the given prefix, returning them into `*out`, writing at most out_size entries.
/// you may pass NULL for `out`, in which case just the number of matching tags is returned
/// (still maxing out at `out_size`).
/// each element in `out` should be freed when you're done with them.
size_t tags_beginning_with(Ted *ted, const char *prefix, char **out, size_t out_size, bool error_if_tags_does_not_exist);
/// go to the definition of the given tag
bool tag_goto(Ted *ted, const char *tag);
/// get all tags in the tags file as SymbolInfos.
SymbolInfo *tags_get_symbols(Ted *ted);

// === ted.c ===
/// for fatal errors
void die(PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2);
/// returns `true` if the given key is down
bool ted_is_key_down(Ted *ted, SDL_Keycode key);
/// returns `true` if the given \ref KeyCombo is down
bool ted_is_key_combo_down(Ted *ted, KeyCombo key_combo);
/// returns `true` if either ctrl key is down
bool ted_is_ctrl_down(Ted *ted);
/// returns `true` if either shift key is down
bool ted_is_shift_down(Ted *ted);
/// returns `true` if either alt key is down
bool ted_is_alt_down(Ted *ted);
/// see \ref KEY_MODIFIER_CTRL, etc.
u32 ted_get_key_modifier(Ted *ted);
/// display a message to the user
void ted_set_message(Ted *ted, MessageType type, PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(3, 4);
/// display an error to the user
void ted_error(Ted *ted, PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
/// display a warning to the user
void ted_warn(Ted *ted, PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
/// display information to the user
void ted_info(Ted *ted, PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
/// for information that should be logged
void ted_log(Ted *ted, PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
/// set error to "out of memory" message.
void ted_out_of_mem(Ted *ted);
/// allocate memory, producing an error message and returning NULL on failure
void *ted_malloc(Ted *ted, size_t size);
/// allocate memory, producing an error message and returning NULL on failure
void *ted_calloc(Ted *ted, size_t n, size_t size);
/// allocate memory, producing an error message and returning NULL on failure
void *ted_realloc(Ted *ted, void *p, size_t new_size);
/// Check the various places a ted data file could be
/// (i.e. look for it in the local and global data directories),
/// and return the full path.
Status ted_get_file(Ted const *ted, const char *name, char *out, size_t outsz);
/// get full path relative to ted->cwd.
void ted_path_full(Ted *ted, const char *relpath, char *abspath, size_t abspath_size);
/// set ted->active_buffer to something nice
void ted_reset_active_buffer(Ted *ted);
/// set ted's error message to the buffer's error.
void ted_error_from_buffer(Ted *ted, TextBuffer *buffer);
/// Returns the buffer containing the file at `path`, or NULL if there is none.
TextBuffer *ted_get_buffer_with_file(Ted *ted, const char *path);
/// save all buffers
bool ted_save_all(Ted *ted);
/// reload all buffers from their files
void ted_reload_all(Ted *ted);
/// Load all the fonts ted will use.
void ted_load_fonts(Ted *ted);
/// Get likely root directory of project containing `path`.
/// The returned value should be freed.
char *ted_get_root_dir_of(Ted *ted, const char *path);
/// Get the root directory of the project containing the active buffer's file,
/// or `ted->cwd` if no file is open.
/// The returned value should be freed.
char *ted_get_root_dir(Ted *ted);
/// the settings of the active buffer, or the default settings if there is no active buffer
Settings *ted_active_settings(Ted *ted);
/// Get the settings for a file at the given path in the given language.
Settings *ted_get_settings(Ted *ted, const char *path, Language language);
/// Get LSP by ID. Returns NULL if there is no LSP with that ID.
LSP *ted_get_lsp_by_id(Ted *ted, LSPID id);
/// Get LSP which should be used for the given path and language.
/// If no running LSP server would cover the path and language, a new one is
/// started if possible.
/// Returns NULL on failure (e.g. there is no LSP server
/// specified for the given path and language).
LSP *ted_get_lsp(Ted *ted, const char *path, Language language);
/// Get the LSP of the active buffer/ted->cwd.
/// Returns NULL if there is no such server.
LSP *ted_active_lsp(Ted *ted);
/// get the value of the given color setting, according to `ted_active_settings(ted)`.
u32 ted_active_color(Ted *ted, ColorSetting color);
/// open the given file, or switch to it if it's already open.
/// returns true on success.
bool ted_open_file(Ted *ted, const char *filename);
/// create a new buffer for the file `filename`, or open it if it's already open.
/// if `filename` is NULL, this creates an untitled buffer.
/// returns true on success.
bool ted_new_file(Ted *ted, const char *filename);
/// returns the index of an available buffer, or -1 if none are available 
i32 ted_new_buffer(Ted *ted);
/// Returns the index of an available node, or -1 if none are available 
i32 ted_new_node(Ted *ted);
/// Opposite of ted_new_buffer
/// Make sure you set active_buffer to something else if you delete it!
void ted_delete_buffer(Ted *ted, u16 index);
/// save all changes to all buffers with unsaved changes.
bool ted_save_all(Ted *ted);
/// sets the active buffer to this buffer, and updates active_node, etc. accordingly
/// you can pass NULL to buffer to make it so no buffer is active.
void ted_switch_to_buffer(Ted *ted, TextBuffer *buffer);
/// switch to this node
void ted_node_switch(Ted *ted, Node *node);
/// load ted.cfg files
void ted_load_configs(Ted *ted, bool reloading);
/// handle a key press
void ted_press_key(Ted *ted, SDL_Keycode keycode, SDL_Keymod modifier);
/// get the buffer and buffer position where the mouse is.
/// returns false if the mouse is not in a buffer.
Status ted_get_mouse_buffer_pos(Ted *ted, TextBuffer **pbuffer, BufferPos *ppos);
/// make the cursor red for a bit to indicate an error (e.g. no autocompletions)
void ted_flash_error_cursor(Ted *ted);
/// go to `path` at line `line` and index `index`, opening a new buffer if necessary.
/// if `is_lsp` is set to true, `index` is interpreted as a UTF-16 offset rather than a UTF-32 offset.
void ted_go_to_position(Ted *ted, const char *path, u32 line, u32 index, bool is_lsp);
/// go to this LSP document position, opening a new buffer containing the file if necessary.
void ted_go_to_lsp_document_position(Ted *ted, LSP *lsp, LSPDocumentPosition position);
/// cancel this LSP request. also zeroes *request
/// if *request is zeroed, this does nothing.
void ted_cancel_lsp_request(Ted *ted, LSPServerRequestID *request);
/// how tall is a line buffer?
float ted_line_buffer_height(Ted *ted);
/// check for orphaned nodes and node cycles
void ted_check_for_node_problems(Ted *ted);
/// convert LSPWindowMessageType to MessageType
MessageType ted_message_type_from_lsp(LSPWindowMessageType type);
/// get colors to use for message box
void ted_color_settings_for_message_type(MessageType type, ColorSetting *bg_color, ColorSetting *border_color);

// === ui.c ===
/// move selector cursor up by `n` entries
void selector_up(Ted *ted, Selector *s, i64 n);
/// move selector cursor down by `n` entries
void selector_down(Ted *ted, Selector *s, i64 n);
/// sort entries alphabetically
void selector_sort_entries_by_name(Selector *s);
/// returns a null-terminated UTF-8 string of the entry selected, or NULL if none was.
/// also, sel->cursor will be set to the index of the entry, even if the mouse was used.
/// you should call free() on the return value.
char *selector_update(Ted *ted, Selector *s);
/// NOTE: also renders the line buffer
void selector_render(Ted *ted, Selector *s);
void file_selector_free(FileSelector *fs);
/// returns the name of the selected file, or NULL if none was selected.
/// the returned pointer should be freed.
char *file_selector_update(Ted *ted, FileSelector *fs);
void file_selector_render(Ted *ted, FileSelector *fs);
vec2 button_get_size(Ted *ted, const char *text);
void button_render(Ted *ted, Rect button, const char *text, u32 color);
/// returns true if the button was clicked on.
bool button_update(Ted *ted, Rect button);
/// returns selected option, or POPUP_NONE if none was selected
PopupOption popup_update(Ted *ted, u32 options);
void popup_render(Ted *ted, u32 options, const char *title, const char *body);
vec2 checkbox_frame(Ted *ted, bool *value, const char *label, vec2 pos);


#ifdef __cplusplus
} // extern "C"
#endif

#endif