summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/array.cpp186
-rw-r--r--src/array.h29
-rw-r--r--src/definitions.h16
-rw-r--r--src/main.cpp416
-rw-r--r--src/main_windows.cpp372
-rw-r--r--src/memory_bucket.cpp68
-rw-r--r--src/memory_bucket.h28
-rw-r--r--src/mutex.cpp102
-rw-r--r--src/mutex.h42
-rw-r--r--src/platform.h35
-rw-r--r--src/search.cpp237
-rw-r--r--src/search.h64
12 files changed, 1595 insertions, 0 deletions
diff --git a/src/array.cpp b/src/array.cpp
new file mode 100644
index 0000000..b6e0a9b
--- /dev/null
+++ b/src/array.cpp
@@ -0,0 +1,186 @@
+#include "array.h"
+
+array array_create(int entry_size)
+{
+ array new_array;
+ new_array.length = 0;
+ new_array.reserved_length = 0;
+ new_array.entry_size = entry_size;
+ new_array.data = 0;
+ new_array.reserve_jump = 1;
+ new_array.mutex = mutex_create_recursive();
+
+ return new_array;
+}
+
+int array_push(array *array, void *data)
+{
+ ASSERT(array);
+ ASSERT(data);
+ ASSERT(array->reserve_jump >= 1);
+
+ mutex_lock(&array->mutex);
+ array->length++;
+
+ if (!array->data)
+ {
+ array->data = malloc(array->entry_size * array->reserve_jump);
+ array->reserved_length = array->reserve_jump;
+ }
+
+ if (array->reserved_length < array->length)
+ {
+ array->reserved_length += array->reserve_jump;
+ array->data = realloc(array->data, (array->reserved_length*array->entry_size));
+ }
+
+ memcpy((char*)array->data + ((array->length-1) * array->entry_size),
+ data, array->entry_size);
+
+ int result = array->length -1;
+ mutex_unlock(&array->mutex);
+ return result;
+}
+
+int array_push_size(array *array, void *data, int data_size)
+{
+ ASSERT(array);
+ ASSERT(data);
+ ASSERT(array->reserve_jump >= 1);
+
+ mutex_lock(&array->mutex);
+ array->length++;
+
+ if (!array->data)
+ {
+ array->data = malloc(array->entry_size * array->reserve_jump);
+ array->reserved_length = array->reserve_jump;
+ }
+
+ if (array->reserved_length < array->length)
+ {
+ array->reserved_length += array->reserve_jump;
+ array->data = realloc(array->data, (array->reserved_length*array->entry_size));
+ }
+
+ memcpy((char*)array->data + ((array->length-1) * array->entry_size),
+ data, data_size);
+
+ // fill remaining space with 0
+ if (array->entry_size > data_size)
+ {
+ int remaining = array->entry_size - data_size;
+ memset((char*)array->data + ((array->length-1) * array->entry_size) + data_size,
+ 0, remaining);
+ }
+
+ int result = array->length -1;
+ mutex_unlock(&array->mutex);
+ return result;
+}
+
+void array_reserve(array *array, int reserve_count)
+{
+ ASSERT(array);
+
+ mutex_lock(&array->mutex);
+ int reserved_count = array->reserved_length - array->length;
+ reserve_count -= reserved_count;
+
+ if (reserve_count > 0)
+ {
+ array->reserved_length += reserve_count;
+
+ if (array->data)
+ {
+ array->data = realloc(array->data, (array->reserved_length*array->entry_size));
+ }
+ else
+ {
+ array->data = malloc(array->reserved_length*array->entry_size);
+ }
+ }
+ mutex_unlock(&array->mutex);
+}
+
+void array_remove_at(array *array, int at)
+{
+ ASSERT(array);
+ ASSERT(at >= 0);
+ ASSERT(at < array->length);
+
+ mutex_lock(&array->mutex);
+ if (array->length > 1)
+ {
+ int offset = at * array->entry_size;
+ int size = (array->length - at - 1) * array->entry_size;
+ memcpy((char*)array->data + offset,
+ (char*)array->data + offset + array->entry_size,
+ size);
+
+ //array->data = realloc(array->data, array->length * array->entry_size);
+ }
+
+ array->length--;
+ mutex_unlock(&array->mutex);
+}
+
+void array_remove(array *array, void *ptr)
+{
+ mutex_lock(&array->mutex);
+ int offset = (char*)ptr - (char*)array->data;
+ int at = offset / array->entry_size;
+ array_remove_at(array, at);
+ mutex_unlock(&array->mutex);
+}
+
+void array_remove_by(array *array, void *data)
+{
+ ASSERT(array);
+
+ mutex_lock(&array->mutex);
+ for (int i = 0; i < array->length; i++)
+ {
+ void *d = array_at(array, i);
+ if (memcmp(d, data, array->entry_size) == 0)
+ {
+ array_remove_at(array, i);
+ return;
+ }
+ }
+ mutex_unlock(&array->mutex);
+}
+
+void *array_at(array *array, int at)
+{
+ mutex_lock(&array->mutex);
+ ASSERT(array);
+ ASSERT(at >= 0);
+ ASSERT(at < array->length);
+
+ void *result = (char*)array->data + (at * array->entry_size);
+ mutex_unlock(&array->mutex);
+ return result;
+}
+
+void array_destroy(array *array)
+{
+ ASSERT(array);
+ free(array->data);
+ mutex_destroy(&array->mutex);
+}
+
+array array_copy(array *arr)
+{
+ array new_array;
+ new_array.length = arr->length;
+ new_array.reserved_length = arr->reserved_length;
+ new_array.entry_size = arr->entry_size;
+ new_array.data = malloc(new_array.entry_size*new_array.reserved_length);
+ new_array.mutex = mutex_create();
+
+ mutex_lock(&arr->mutex);
+ memcpy(new_array.data, arr->data, new_array.entry_size*new_array.reserved_length);
+ mutex_unlock(&arr->mutex);
+ return new_array;
+} \ No newline at end of file
diff --git a/src/array.h b/src/array.h
new file mode 100644
index 0000000..f00c220
--- /dev/null
+++ b/src/array.h
@@ -0,0 +1,29 @@
+#ifndef INCLUDE_ARRAY
+#define INCLUDE_ARRAY
+
+#define ASSERT(e_) {if(!(e_)){*(int*)0=0;}}
+
+#include "mutex.h"
+
+typedef struct t_array
+{
+ int length;
+ int reserved_length;
+ int entry_size;
+ int reserve_jump;
+ void *data;
+ mutex mutex;
+} array;
+
+array array_create(int entry_size);
+int array_push(array *array, void *data);
+int array_push_size(array *array, void *data, int data_size);
+void array_remove_at(array *array, int at);
+void array_remove(array *array, void *ptr);
+void array_remove_by(array *array, void *data);
+void *array_at(array *array, int at);
+void array_destroy(array *array);
+void array_reserve(array *array, int reserve_count);
+array array_copy(array *array);
+
+#endif \ No newline at end of file
diff --git a/src/definitions.h b/src/definitions.h
new file mode 100644
index 0000000..2bf70ae
--- /dev/null
+++ b/src/definitions.h
@@ -0,0 +1,16 @@
+#ifndef INCLUDE_EMBEDDED_RESOURCES
+#define INCLUDE_EMBEDDED_RESOURCES
+
+#define TS_VERSION "v1.0.0"
+
+// Find these with dumpbin [objfile] /SYMBOLS
+extern "C"
+{
+extern unsigned char _binary_LICENSE_start[];
+extern unsigned char _binary_LICENSE_end[];
+
+extern unsigned char _binary_misc_logo_64_png_start[];
+extern unsigned char _binary_misc_logo_64_png_end[];
+}
+
+#endif \ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..df1dfe2
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,416 @@
+#include "../imgui/imgui.h"
+#include "../imgui/imgui_spectrum.h"
+#include "../imgui/imgui_impl_opengl3_loader.h"
+#include "definitions.h"
+#include "search.h"
+#include "platform.h"
+#include "../utf8.h"
+#include <stdio.h>
+
+typedef struct t_ts_image {
+ GLuint id;
+ int width;
+ int height;
+} ts_image;
+
+ts_image img_logo;
+
+#define SEARCH_BUFFER_SIZE 2048
+
+search_result* current_search_result = nullptr;
+
+char path_buffer[SEARCH_BUFFER_SIZE];
+char filter_buffer[SEARCH_BUFFER_SIZE];
+char query_buffer[SEARCH_BUFFER_SIZE];
+
+bool open_settings_window = false;
+bool open_about_window = false;
+
+int thread_count = 4;
+int current_locale_index = 0;
+int locales_count = 2;
+char* locales[] = {
+ "English",
+ "Dutch"
+};
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "../stb_image.h"
+
+// Simple helper function to load an image into a OpenGL texture with common settings
+bool LoadTexture(unsigned char* data, unsigned long size, GLuint* out_texture, int* out_width, int* out_height)
+{
+ // Load from file
+ int image_width = 0;
+ int image_height = 0;
+ unsigned char* image_data = stbi_load_from_memory(data, size, &image_width, &image_height, NULL, 4);
+ if (image_data == NULL) {
+ printf("Failed to load %s\n", stbi_failure_reason());
+ return false;
+ }
+
+ // Create a OpenGL texture identifier
+ GLuint image_texture;
+ glGenTextures(1, &image_texture);
+ glBindTexture(GL_TEXTURE_2D, image_texture);
+
+ // Setup filtering parameters for display
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ // Upload pixels into texture
+#if defined(GL_UNPACK_ROW_LENGTH) && !defined(__EMSCRIPTEN__)
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+#endif
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
+ stbi_image_free(image_data);
+
+ *out_texture = image_texture;
+ *out_width = image_width;
+ *out_height = image_height;
+
+ return true;
+}
+
+static ts_image _ts_load_image(unsigned char* data, unsigned long size) {
+ int w = 0;
+ int h = 0;
+ GLuint id = 0;
+ bool ret = LoadTexture(data, size, &id, &w, &h);
+
+ return ts_image {id, w, h};
+}
+
+static void _ts_search_file(found_file* ref, file_content content, search_result* result) {
+ if (content.content && !content.file_error)
+ {
+ array text_matches = array_create(sizeof(text_match));
+ int search_len = strlen(result->search_text);
+ if (string_contains_ex((char*)content.content, result->search_text, &text_matches))
+ {
+ mutex_lock(&result->matches.mutex);
+ for (int i = 0; i < text_matches.length; i++)
+ {
+ text_match *m = (text_match *)array_at(&text_matches, i);
+
+ file_match file_match;
+ file_match.file = ref;
+ file_match.line_nr = m->line_nr;
+ file_match.word_match_offset = m->word_offset;
+ file_match.word_match_length = m->word_match_len;
+ file_match.line_info = (char*)malloc(MAX_INPUT_LENGTH);
+
+ int text_pad_lr = 25;
+ if (file_match.word_match_offset > text_pad_lr) {
+ m->line_start += file_match.word_match_offset - text_pad_lr;
+ file_match.word_match_offset = text_pad_lr;
+ }
+ int total_len = text_pad_lr + search_len + text_pad_lr;
+
+ snprintf(file_match.line_info, MAX_INPUT_LENGTH, "%.*s", total_len, m->line_start);
+ for (int i = 0; i < total_len; i++) {
+ if (file_match.line_info[i] == '\n') file_match.line_info[i] = ' ';
+ if (file_match.line_info[i] == '\t') file_match.line_info[i] = ' ';
+ if (file_match.line_info[i] == '\r') file_match.line_info[i] = ' ';
+ if (file_match.line_info[i] == '\x0B') file_match.line_info[i] = ' ';
+ }
+
+ array_push_size(&result->matches, &file_match, sizeof(file_match));
+ ref->match_count++;
+ result->match_count = result->matches.length;
+ }
+ mutex_unlock(&result->matches.mutex);
+ }
+
+ array_destroy(&text_matches);
+ }
+}
+
+static void* _ts_search_thread(void* args) {
+ search_result* new_result = (search_result *)args;
+
+ keep_going:;
+ while (new_result->file_list_read_cursor < new_result->files.length)
+ {
+ mutex_lock(&new_result->files.mutex);
+ int read_cursor = new_result->file_list_read_cursor++;
+ new_result->file_count++;
+ mutex_unlock(&new_result->files.mutex);
+
+ if (read_cursor >= new_result->files.length) continue;
+
+ found_file* f = (found_file*)array_at(&new_result->files, read_cursor);
+ file_content content = platform_read_file(f->path, "rb");
+
+ _ts_search_file(f, content, new_result);
+
+ free(content.content);
+ }
+
+ if (!new_result->done_finding_files)
+ goto keep_going;
+
+ new_result->completed_match_threads++;
+
+ return 0;
+}
+
+static void _ts_start_search() {
+ search_result* new_result = create_empty_search_result();
+ snprintf(new_result->directory_to_search, MAX_INPUT_LENGTH, "%s", path_buffer);
+ snprintf(new_result->search_text, MAX_INPUT_LENGTH, "%s", query_buffer);
+
+
+ platform_list_files(new_result);
+ //new_result->max_thread_count
+ for (int i = 0; i < 1; i++) {
+ thread thr = thread_start(_ts_search_thread, new_result);
+ thread_detach(&thr);
+ }
+
+ current_search_result = new_result;
+}
+
+static void _ts_create_popups() {
+ ImGuiIO& io = ImGui::GetIO();
+ if (open_settings_window) {
+ ImGui::OpenPopup("Text-Search settings");
+ ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), ImGuiCond_Always, ImVec2(0.5f,0.5f));
+ }
+
+ // Settings window
+ if (ImGui::BeginPopupModal("Text-Search settings", NULL, ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove)) {
+ ImGui::SetWindowSize({300, 0});
+ ImGui::DragInt("Threads", &thread_count, 1.0f, 1, 64);
+ ImGui::Combo("Language", &current_locale_index, locales, locales_count);
+
+ ImGui::Dummy({0, 70});
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
+ if (ImGui::Button("Close")) {
+ open_settings_window = false;
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::PopStyleVar();
+
+ ImGui::EndPopup();
+ }
+
+ if (open_about_window) {
+ ImGui::OpenPopup("About Text-Search");
+ ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), ImGuiCond_Always, ImVec2(0.5f,0.5f));
+ }
+
+ // About window
+ if (ImGui::BeginPopupModal("About Text-Search", NULL, ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove)) {
+ ImGui::SetWindowSize({600, 0});
+
+ //ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 64 - 10);
+ //ImGui::Image((void*)(intptr_t)img_logo.id, {64, 64});
+
+ char* license = (char*)_binary_LICENSE_start;
+ int license_length = _binary_LICENSE_end - _binary_LICENSE_start;
+ ImGui::Text("%.*s", license_length, license);
+
+ ImGui::Dummy({0, 70});
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
+ if (ImGui::Button("Close")) {
+ open_about_window = false;
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::CalcTextSize(TS_VERSION).x - 15);
+ ImGui::Text(TS_VERSION);
+ ImGui::PopStyleVar();
+
+ ImGui::EndPopup();
+ }
+}
+
+static int _ts_create_menu() {
+ int menu_bar_h = 0;
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGui::Spectrum::Color(0xDDDDDD));
+ ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1.0f);
+ if (ImGui::BeginMenuBar())
+ {
+ if (ImGui::BeginMenu("File"))
+ {
+ ImGui::MenuItem("Open", "CTRL+O");
+ ImGui::MenuItem("Save", "CTRL+S");
+ ImGui::Separator();
+ ImGui::MenuItem("Exit", "CTRL+Q");
+ ImGui::EndMenu();
+ }
+ if (ImGui::BeginMenu("Program"))
+ {
+ if (ImGui::MenuItem("Settings")) {
+ open_settings_window = true;
+ }
+ if (ImGui::MenuItem("About")) {
+ open_about_window = true;
+ }
+
+ ImGui::EndMenu();
+ }
+
+ menu_bar_h = 27;
+ ImGui::EndMenuBar();
+ }
+ ImGui::PopStyleVar();
+ ImGui::PopStyleColor();
+
+ _ts_create_popups();
+
+ return menu_bar_h;
+}
+
+void ts_load_images() {
+ snprintf(path_buffer, MAX_INPUT_LENGTH, "%s", "C:\\Users\\aldri\\Desktop\\Vault\\Projects\\allegro5");
+ snprintf(filter_buffer, MAX_INPUT_LENGTH, "%s", "*.h");
+ snprintf(query_buffer, MAX_INPUT_LENGTH, "%s", "test");
+
+ int size = _binary_misc_logo_64_png_end - _binary_misc_logo_64_png_start;
+ unsigned char* data = (unsigned char *)_binary_misc_logo_64_png_start;
+ img_logo = _ts_load_image(data, size);
+}
+
+void ts_create_gui(int window_w, int window_h) {
+ static float f = 0.0f;
+ static int counter = 0;
+ int window_pad = 50;
+ int textbox_area_height = 80;
+ int statusbar_area_height = 30;
+ int result_area_height = window_h - textbox_area_height - statusbar_area_height - window_pad;
+
+ ImGui::SetNextWindowSize({(float)window_w, (float)window_h});
+ ImGui::SetNextWindowPos({0, 0});
+
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(5, 5));
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0.f, 0.f));
+ ImGui::Begin("text-search", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoScrollbar|
+ ImGuiWindowFlags_MenuBar);
+ ImGui::PopStyleVar();
+
+ float menu_bar_h = _ts_create_menu();
+
+ float pos_y = 0;
+
+ pos_y += menu_bar_h + 15;
+
+ { // Search boxes
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(5, 5));
+
+ float offset = 15.0f;
+ float separator_w = 10.0f;
+ float frame_w = window_w/2.5f - offset - separator_w/2.0f;
+ ImGui::SetNextWindowPos({offset, pos_y});
+ ImGui::BeginChild("search-boxes", ImVec2(frame_w, textbox_area_height), false);
+ {
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
+ ImGui::PushItemWidth(-1);
+ ImGui::InputTextWithHint("path-ti", "Path", path_buffer, 4000);
+ ImGui::PopItemWidth();
+
+ ImGui::PushItemWidth(-1);
+ ImGui::InputTextWithHint("query", "Query", query_buffer, 4000);
+ ImGui::PopItemWidth();
+ ImGui::PopStyleVar();
+ }
+ ImGui::EndChild();
+
+ ImGui::SetNextWindowPos({offset + frame_w + separator_w, pos_y});
+ ImGui::BeginChild("search-boxes2", ImVec2(frame_w, textbox_area_height), false);
+ {
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
+ ImGui::PushItemWidth(-1);
+ ImGui::InputTextWithHint("filter-ti", "Filter", filter_buffer, 4000);
+ ImGui::PopItemWidth();
+ ImGui::PopStyleVar();
+
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
+ if (ImGui::Button("Search")) {
+ _ts_start_search();
+ }
+ ImGui::PopStyleVar();
+ }
+ ImGui::EndChild();
+
+ ImGui::PopStyleVar();
+ }
+ pos_y += textbox_area_height + 7;
+
+ { // Results
+ ImGui::SetNextWindowPos({5, pos_y});
+
+ if (ImGui::BeginTable("results-table", 3, ImGuiTableFlags_BordersH|ImGuiTableFlags_ScrollY|ImGuiTableFlags_RowBg|ImGuiTableFlags_SizingFixedFit,
+ {(float)window_w-7.0f, (float)result_area_height}))
+ {
+ int nr_w = 50;
+ int line_w = 120;
+ int file_w = ImGui::GetWindowWidth() - line_w - nr_w;
+ ImGui::TableSetupColumn("", ImGuiTableColumnFlags_NoHeaderLabel, nr_w);
+ ImGui::TableSetupColumn("File", 0, file_w);
+ ImGui::TableSetupColumn("Match", 0, line_w);
+ ImGui::TableHeadersRow();
+
+ int itemcount = current_search_result == 0 ? 0 : current_search_result->matches.length;
+ found_file* prev_file = nullptr;
+ for (int item = 0; item < itemcount; item++)
+ {
+ file_match *file = (file_match *)array_at(&current_search_result->matches, item);
+
+ if (prev_file != file->file) {
+ prev_file = file->file;
+ char match_info_txt[20];
+
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::TableHeader("");
+
+ ImGui::TableNextColumn();
+ ImGui::TableHeader(file->file->path);
+
+ ImGui::TableNextColumn();
+ snprintf(match_info_txt, 20, "%d match(es)", file->file->match_count);
+ ImGui::TableHeader(match_info_txt);
+ }
+
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::Text("#%d", item+1);
+
+ ImGui::TableNextColumn();
+ ImGui::Text("%.*s", file->word_match_offset, file->line_info);
+ ImGui::SameLine();
+ ImGui::TextColored({255,0,0,255}, "%.*s", file->word_match_length, file->line_info + file->word_match_offset);
+ ImGui::SameLine();
+ ImGui::TextUnformatted(file->line_info + file->word_match_offset + file->word_match_length);
+
+ ImGui::TableNextColumn();
+ ImGui::Text("line %d", file->line_nr);
+ }
+ ImGui::EndTable();
+ }
+ }
+ pos_y += result_area_height;
+
+ { // Statusbar
+ ImGui::SetNextWindowPos({0, pos_y});
+
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 6));
+ ImGui::BeginChild("search-statusbar", ImVec2(window_w, statusbar_area_height), ImGuiChildFlags_None, ImGuiWindowFlags_None);
+ ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 7.0f);
+ ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.0f);
+ if (current_search_result) ImGui::Text("Found %d matches in %d files", current_search_result->match_count, current_search_result->file_count);
+ else ImGui::Text("No search completed");
+
+ ImGui::SameLine();
+
+ ImGui::SetCursorPosX(window_w - 10.0f - ImGui::CalcTextSize("no search completed").x);
+ ImGui::Text("no search completed");
+ ImGui::EndChild();
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::PopStyleVar();
+
+ ImGui::End();
+} \ No newline at end of file
diff --git a/src/main_windows.cpp b/src/main_windows.cpp
new file mode 100644
index 0000000..3a1d2e8
--- /dev/null
+++ b/src/main_windows.cpp
@@ -0,0 +1,372 @@
+// Dear ImGui: standalone example application for Win32 + OpenGL 3
+
+// Learn about Dear ImGui:
+// - FAQ https://dearimgui.com/faq
+// - Getting Started https://dearimgui.com/getting-started
+// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// This is provided for completeness, however it is strongly recommended you use OpenGL with SDL or GLFW.
+
+#include <stdio.h>
+
+#include "../imgui/imgui.h"
+#include "../imgui/imgui_spectrum.h"
+#include "../imgui/imgui_impl_opengl3.h"
+#include "../imgui/imgui_impl_win32.h"
+#include "../utf8.h"
+#include "platform.h"
+#include "mutex.h"
+#include "array.h"
+#include "memory_bucket.h"
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <GL/GL.h>
+#include <tchar.h>
+
+#define IDI_LOGO 123
+
+void ts_create_gui(int window_w, int window_h);
+void ts_load_images();
+
+// Data stored per platform window
+struct WGL_WindowData { HDC hDC; };
+
+// Data
+static HGLRC g_hRC;
+static WGL_WindowData g_MainWindow;
+static int g_Width;
+static int g_Height;
+
+// Forward declarations of helper functions
+bool CreateDeviceWGL(HWND hWnd, WGL_WindowData* data);
+void CleanupDeviceWGL(HWND hWnd, WGL_WindowData* data);
+void ResetDeviceWGL();
+LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+// Main code
+int main(int, char**)
+{
+ // Create application window
+ //ImGui_ImplWin32_EnableDpiAwareness();
+ WNDCLASSEXW wc = { sizeof(wc), CS_OWNDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr };
+ ::RegisterClassExW(&wc);
+ HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Text-Search", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr);
+
+ // Initialize OpenGL
+ if (!CreateDeviceWGL(hwnd, &g_MainWindow))
+ {
+ CleanupDeviceWGL(hwnd, &g_MainWindow);
+ ::DestroyWindow(hwnd);
+ ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
+ return 1;
+ }
+ wglMakeCurrent(g_MainWindow.hDC, g_hRC);
+
+ // Show the window
+ ::ShowWindow(hwnd, SW_SHOWDEFAULT);
+ ::UpdateWindow(hwnd);
+
+ HICON hicoCaption = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_LOGO));
+ SendMessage(hwnd, WM_SETICON, ICON_BIG,
+ reinterpret_cast<LPARAM>(hicoCaption));
+
+ // Setup Dear ImGui context
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImGuiIO& io = ImGui::GetIO(); (void)io;
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
+ io.IniFilename = NULL;
+
+ // Setup Dear ImGui style
+ ImGui::Spectrum::StyleColorsSpectrum();
+ ImGui::Spectrum::LoadFont(18.0f);
+ //ImGui::StyleColorsLight();
+
+ // Setup Platform/Renderer backends
+ ImGui_ImplWin32_InitForOpenGL(hwnd);
+ ImGui_ImplOpenGL3_Init();
+
+ ts_load_images();
+
+ // Load Fonts
+ // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
+ // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
+ // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
+ // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
+ // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering.
+ // - Read 'docs/FONTS.md' for more instructions and details.
+ // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
+ //io.Fonts->AddFontDefault();
+ //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f);
+ //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
+ //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
+ //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
+ //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());
+ //IM_ASSERT(font != nullptr);
+
+ ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
+ bool done = false;
+ while (!done)
+ {
+ MSG msg;
+ while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
+ {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ if (msg.message == WM_QUIT)
+ done = true;
+ }
+ if (done)
+ break;
+
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplWin32_NewFrame();
+ ImGui::NewFrame();
+
+ ts_create_gui(g_Width, g_Height);
+
+ // Rendering
+ ImGui::Render();
+ glViewport(0, 0, g_Width, g_Height);
+ glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
+ // Present
+ ::SwapBuffers(g_MainWindow.hDC);
+ }
+
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplWin32_Shutdown();
+ ImGui::DestroyContext();
+
+ CleanupDeviceWGL(hwnd, &g_MainWindow);
+ wglDeleteContext(g_hRC);
+ ::DestroyWindow(hwnd);
+ ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
+
+ return 0;
+}
+
+// Helper functions
+bool CreateDeviceWGL(HWND hWnd, WGL_WindowData* data)
+{
+ HDC hDc = ::GetDC(hWnd);
+ PIXELFORMATDESCRIPTOR pfd = { 0 };
+ pfd.nSize = sizeof(pfd);
+ pfd.nVersion = 1;
+ pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+ pfd.iPixelType = PFD_TYPE_RGBA;
+ pfd.cColorBits = 32;
+
+ const int pf = ::ChoosePixelFormat(hDc, &pfd);
+ if (pf == 0)
+ return false;
+ if (::SetPixelFormat(hDc, pf, &pfd) == FALSE)
+ return false;
+ ::ReleaseDC(hWnd, hDc);
+
+ data->hDC = ::GetDC(hWnd);
+ if (!g_hRC)
+ g_hRC = wglCreateContext(data->hDC);
+ return true;
+}
+
+void CleanupDeviceWGL(HWND hWnd, WGL_WindowData* data)
+{
+ wglMakeCurrent(nullptr, nullptr);
+ ::ReleaseDC(hWnd, data->hDC);
+}
+
+// Forward declare message handler from imgui_impl_win32.cpp
+extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+// Win32 message handler
+// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
+// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
+// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
+// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
+LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
+ return true;
+
+ switch (msg)
+ {
+ case WM_GETMINMAXINFO:
+ {
+ MINMAXINFO *minmax = (MINMAXINFO *)lParam;
+ minmax->ptMinTrackSize.x = 800;
+ minmax->ptMinTrackSize.y = 600;
+ break;
+ }
+ case WM_SIZE:
+ if (wParam != SIZE_MINIMIZED)
+ {
+ g_Width = LOWORD(lParam);
+ g_Height = HIWORD(lParam);
+ }
+ return 0;
+ case WM_SYSCOMMAND:
+ if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
+ return 0;
+ break;
+ case WM_DESTROY:
+ ::PostQuitMessage(0);
+ return 0;
+ }
+ return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+}
+
+file_content platform_read_file(char *path, const char *mode)
+{
+ file_content result;
+ result.content = 0;
+ result.content_length = 0;
+ result.file_error = 0;
+
+ FILE *file = fopen(path, mode);
+ if (!file)
+ {
+ if (errno == EMFILE)
+ result.file_error = FILE_ERROR_TOO_MANY_OPEN_FILES_PROCESS;
+ else if (errno == ENFILE)
+ result.file_error = FILE_ERROR_TOO_MANY_OPEN_FILES_SYSTEM;
+ else if (errno == EACCES)
+ result.file_error = FILE_ERROR_NO_ACCESS;
+ else if (errno == EPERM)
+ result.file_error = FILE_ERROR_NO_ACCESS;
+ else if (errno == ENOENT)
+ result.file_error = FILE_ERROR_NOT_FOUND;
+ else if (errno == ECONNABORTED)
+ result.file_error = FILE_ERROR_CONNECTION_ABORTED;
+ else if (errno == ECONNREFUSED)
+ result.file_error = FILE_ERROR_CONNECTION_REFUSED;
+ else if (errno == ENETDOWN)
+ result.file_error = FILE_ERROR_NETWORK_DOWN;
+ else
+ {
+ result.file_error = FILE_ERROR_GENERIC;
+ printf("ERROR: %d\n", errno);
+ }
+
+ goto done_failure;
+ }
+
+ fseek(file, 0 , SEEK_END);
+ int length = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ int length_to_alloc = length+1;
+
+ result.content = malloc(length_to_alloc);
+ if (!result.content) goto done;
+
+ memset(result.content, 0, length);
+ int read_result = fread(result.content, 1, length, file);
+ if (read_result == 0 && length != 0)
+ {
+ free(result.content);
+ result.content = 0;
+ return result;
+ }
+
+ result.content_length = read_result;
+
+ ((char*)result.content)[length] = 0;
+
+ done:
+ fclose(file);
+ done_failure:
+ return result;
+}
+
+static void *_list_files_thread(void *args)
+{
+ search_result *info = (search_result *)args;
+ platform_list_files_block(info);
+ info->done_finding_files = true;
+ return 0;
+}
+
+void platform_list_files(search_result* result)
+{
+ thread thr = thread_start(_list_files_thread, (void*)result);
+ thread_detach(&thr);
+}
+
+void platform_list_files_block(search_result* result, wchar_t* start_dir)
+{
+ // Utf8 to wchar str
+ wchar_t* search_dir = (wchar_t*)malloc(MAX_INPUT_LENGTH);
+ if (start_dir == nullptr) {
+ MultiByteToWideChar(CP_UTF8, 0, result->directory_to_search, strlen(result->directory_to_search), search_dir, MAX_INPUT_LENGTH);
+ }
+ else {
+ wcscpy(search_dir, start_dir);
+ }
+
+ // Append wildcard
+ wchar_t* search_dir_fix = (wchar_t*)malloc(MAX_INPUT_LENGTH);
+ wcscpy(search_dir_fix, search_dir);
+ wcscat(search_dir_fix, L"\\*");
+
+ WIN32_FIND_DATAW file_info;
+ HANDLE handle = FindFirstFileW(search_dir_fix, &file_info);
+
+ if (handle == INVALID_HANDLE_VALUE)
+ {
+ return;
+ }
+
+ do
+ {
+ //if (*is_cancelled) break;
+ const wchar_t *name = (const wchar_t *)file_info.cFileName;
+
+ // symbolic link is not allowed..
+ if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
+ continue;
+
+
+ if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ if ((wcscmp(name, L".") == 0) || (wcscmp(name, L"..") == 0))
+ continue;
+
+ wchar_t* subdir_buffer_path = (wchar_t*)malloc(MAX_INPUT_LENGTH);
+ wcscpy(subdir_buffer_path, search_dir);
+ wcscat(subdir_buffer_path, L"\\");
+ wcscat(subdir_buffer_path, name);
+ platform_list_files_block(result, subdir_buffer_path);
+ }
+ else if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) ||
+ (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) ||
+ (file_info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ||
+ (file_info.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
+ (file_info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ||
+ (file_info.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
+ {
+ wchar_t complete_file_path[MAX_INPUT_LENGTH];
+ wcscpy(complete_file_path, search_dir);
+ wcscat(complete_file_path, L"\\");
+ wcscat(complete_file_path, name);
+
+ found_file f;
+ f.path = (utf8_int8_t*)malloc(MAX_INPUT_LENGTH);
+ f.match_count = 0;
+ WideCharToMultiByte(CP_UTF8,0,complete_file_path,-1,(LPSTR)f.path,MAX_INPUT_LENGTH, NULL, NULL);
+
+ mutex_lock(&result->files.mutex);
+ array_push_size(&result->files, &f, sizeof(found_file));
+ mutex_unlock(&result->files.mutex);
+
+ }
+ }
+ while (FindNextFile(handle, (LPWIN32_FIND_DATAW)&file_info) != 0);
+
+ FindClose(handle);
+} \ No newline at end of file
diff --git a/src/memory_bucket.cpp b/src/memory_bucket.cpp
new file mode 100644
index 0000000..71cc79a
--- /dev/null
+++ b/src/memory_bucket.cpp
@@ -0,0 +1,68 @@
+#include "memory_bucket.h"
+
+memory_bucket memory_bucket_init(int bucket_size)
+{
+ memory_bucket collection;
+ collection.bucket_mutex = mutex_create();
+ collection.buckets = array_create(sizeof(memory_bucket_entry));
+
+ memory_bucket_entry bucket;
+ bucket.data = (char*)malloc(bucket_size);
+ bucket.length = bucket_size;
+ bucket.cursor = 0;
+ array_push(&collection.buckets, &bucket);
+ return collection;
+}
+
+void* memory_bucket_reserve(memory_bucket *bucket, int reserve_length)
+{
+ mutex_lock(&bucket->bucket_mutex);
+ memory_bucket_entry *bucket_entry = 0;
+ for (int i = 0; i < bucket->buckets.length; i++)
+ {
+ bucket_entry = (memory_bucket_entry *)array_at(&bucket->buckets, i);
+
+ if (bucket_entry->length - bucket_entry->cursor < reserve_length) continue;
+
+ void *space = bucket_entry->data+bucket_entry->cursor;
+ bucket_entry->cursor += reserve_length;
+ mutex_unlock(&bucket->bucket_mutex);
+
+ return space;
+ }
+
+ // failed to find suitable space, allocate new bucket
+ memory_bucket_entry new_bucket;
+ new_bucket.data = (char*)malloc(bucket_entry->length);
+ new_bucket.length = bucket_entry->length;
+ new_bucket.cursor = 0;
+ array_push(&bucket->buckets, &new_bucket);
+ mutex_unlock(&bucket->bucket_mutex);
+
+ return new_bucket.data;
+}
+
+inline void memory_bucket_reset(memory_bucket *bucket)
+{
+ mutex_lock(&bucket->bucket_mutex);
+ for (int i = 0; i < bucket->buckets.length; i++)
+ {
+ memory_bucket_entry *bucket_entry = (memory_bucket_entry *)array_at(&bucket->buckets, i);
+ bucket_entry->cursor = 0;
+ }
+ mutex_unlock(&bucket->bucket_mutex);
+}
+
+inline void memory_bucket_destroy(memory_bucket *bucket)
+{
+ mutex_lock(&bucket->bucket_mutex);
+ for (int i = 0; i < bucket->buckets.length; i++)
+ {
+ memory_bucket_entry *bucket_entry = (memory_bucket_entry *)array_at(&bucket->buckets, i);
+ free(bucket_entry->data);
+ }
+ array_destroy(&bucket->buckets);
+ mutex_unlock(&bucket->bucket_mutex);
+
+ mutex_destroy(&bucket->bucket_mutex);
+} \ No newline at end of file
diff --git a/src/memory_bucket.h b/src/memory_bucket.h
new file mode 100644
index 0000000..f203804
--- /dev/null
+++ b/src/memory_bucket.h
@@ -0,0 +1,28 @@
+#ifndef INCLUDE_MEMORY_BUCKET
+#define INCLUDE_MEMORY_BUCKET
+
+#define kilobytes(num) num*1000
+#define megabytes(num) kilobytes(num*1000)
+
+#include "mutex.h"
+#include "array.h"
+
+typedef struct t_memory_bucket_entry
+{
+ char *data;
+ int length;
+ int cursor;
+} memory_bucket_entry;
+
+typedef struct t_memory_bucket
+{
+ mutex bucket_mutex;
+ array buckets;
+} memory_bucket;
+
+memory_bucket memory_bucket_init(int bucket_size);
+void* memory_bucket_reserve(memory_bucket *bucket, int reserve_length);
+void memory_bucket_reset(memory_bucket *bucket);
+void memory_bucket_destroy(memory_bucket *bucket);
+
+#endif \ No newline at end of file
diff --git a/src/mutex.cpp b/src/mutex.cpp
new file mode 100644
index 0000000..089e5bd
--- /dev/null
+++ b/src/mutex.cpp
@@ -0,0 +1,102 @@
+#include "mutex.h"
+
+mutex mutex_create()
+{
+ mutex result;
+ result.mutex = CreateMutex(
+ NULL, // default security attributes
+ FALSE, // initially not owned
+ NULL); // unnamed mutex
+
+ return result;
+}
+
+mutex mutex_create_recursive()
+{
+ return mutex_create();
+}
+
+void mutex_lock(mutex *mutex)
+{
+ WaitForSingleObject(
+ mutex->mutex, // handle to mutex
+ INFINITE); // no time-out interval
+}
+
+int mutex_trylock(mutex *mutex)
+{
+ return WaitForSingleObject(mutex->mutex, 1) == WAIT_OBJECT_0;
+}
+
+void mutex_unlock(mutex *mutex)
+{
+ ReleaseMutex(mutex->mutex);
+}
+
+void mutex_destroy(mutex *mutex)
+{
+ CloseHandle(mutex->mutex);
+}
+
+
+thread thread_start(void *(*start_routine) (void *), void *arg)
+{
+ thread result;
+ result.valid = 0;
+
+ result.thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start_routine,
+ arg, 0, NULL);
+ result.valid = 1;
+
+ return result;
+}
+
+void thread_join(thread *thread)
+{
+ if (thread->valid)
+ {
+ WaitForSingleObject(thread->thread, INFINITE);
+ CloseHandle(thread->thread);
+ }
+}
+
+int thread_tryjoin(thread *thread)
+{
+ if (thread->valid)
+ {
+ int result = WaitForSingleObject(thread->thread, 0);
+ return result == WAIT_OBJECT_0;
+ }
+ return 0;
+}
+
+void thread_detach(thread *thread)
+{
+ if (thread->valid)
+ {
+ CloseHandle(thread->thread);
+ }
+}
+
+void thread_stop(thread *thread)
+{
+ if (thread->valid)
+ {
+ SuspendThread(thread->thread);
+ }
+}
+
+int thread_get_id()
+{
+ return GetCurrentThreadId();
+}
+
+void thread_exit()
+{
+ ExitThread(0);
+}
+
+void thread_sleep(int microseconds)
+{
+ Sleep(microseconds/1000);
+} \ No newline at end of file
diff --git a/src/mutex.h b/src/mutex.h
new file mode 100644
index 0000000..a061c2e
--- /dev/null
+++ b/src/mutex.h
@@ -0,0 +1,42 @@
+#ifndef INCLUDE_MUTEX
+#define INCLUDE_MUTEX
+
+#ifdef _WIN32
+#include <windows.h>
+#include <process.h> /* _beginthread, _endthread */
+#include <stddef.h>
+#include <stdlib.h>
+#include <conio.h>
+#include <synchapi.h>
+
+typedef struct t_mutex
+{
+ HANDLE mutex;
+} mutex;
+
+typedef struct t_thread
+{
+ HANDLE thread;
+ int valid;
+} thread;
+#endif
+
+thread thread_start(void *(*start_routine) (void *), void *arg);
+void thread_join(thread *thread);
+int thread_tryjoin(thread *thread);
+void thread_detach(thread *thread);
+void thread_stop(thread *thread);
+int thread_get_id();
+void thread_sleep(int microseconds);
+void thread_exit();
+
+
+mutex mutex_create_recursive();
+mutex mutex_create();
+void mutex_lock(mutex *mutex);
+int mutex_trylock(mutex *mutex);
+void mutex_unlock(mutex *mutex);
+void mutex_destroy(mutex *mutex);
+
+
+#endif \ No newline at end of file
diff --git a/src/platform.h b/src/platform.h
new file mode 100644
index 0000000..66cdcb1
--- /dev/null
+++ b/src/platform.h
@@ -0,0 +1,35 @@
+#ifndef INCLUDE_PLATFORM
+#define INCLUDE_PLATFORM
+
+#include "array.h"
+#include "memory_bucket.h"
+#include "search.h"
+#include "../utf8.h"
+
+typedef struct t_file_content
+{
+ int content_length;
+ void *content;
+ int file_error;
+} file_content;
+
+typedef enum t_file_open_error
+{
+ FILE_ERROR_TOO_MANY_OPEN_FILES_PROCESS = 1,
+ FILE_ERROR_TOO_MANY_OPEN_FILES_SYSTEM = 2,
+ FILE_ERROR_NO_ACCESS = 3,
+ FILE_ERROR_NOT_FOUND = 4,
+ FILE_ERROR_CONNECTION_ABORTED = 5,
+ FILE_ERROR_CONNECTION_REFUSED = 6,
+ FILE_ERROR_NETWORK_DOWN = 7,
+ FILE_ERROR_REMOTE_IO_ERROR = 8,
+ FILE_ERROR_STALE = 9, // NFS server file is removed/renamed
+ FILE_ERROR_GENERIC = 10,
+ FILE_ERROR_TOO_BIG = 11,
+} file_open_error;
+
+file_content platform_read_file(char *path, const char *mode);
+void platform_list_files_block(search_result* result, wchar_t* start_dir = nullptr);
+void platform_list_files(search_result* result);
+
+#endif \ No newline at end of file
diff --git a/src/search.cpp b/src/search.cpp
new file mode 100644
index 0000000..c764d71
--- /dev/null
+++ b/src/search.cpp
@@ -0,0 +1,237 @@
+#include "search.h"
+#include "platform.h"
+
+array get_filters(char *pattern)
+{
+ array result = array_create(MAX_INPUT_LENGTH);
+
+ char current_filter[MAX_INPUT_LENGTH];
+ int filter_len = 0;
+ while(*pattern)
+ {
+ char ch = *pattern;
+
+ if (ch == ',')
+ {
+ current_filter[filter_len] = 0;
+ array_push(&result, current_filter);
+ filter_len = 0;
+ }
+ else
+ {
+ if(filter_len < MAX_INPUT_LENGTH-1)
+ {
+ current_filter[filter_len++] = ch;
+ }
+ else
+ {
+ current_filter[filter_len] = ch;
+ }
+ }
+
+ pattern++;
+ }
+ current_filter[filter_len] = 0;
+ array_push(&result, current_filter);
+
+ return result;
+}
+
+int string_match(char *first, char *second)
+{
+ // If we reach at the end of both strings, we are done
+ if (*first == '\0' && *second == '\0')
+ return 1;
+
+ // Make sure that the characters after '*' are present
+ // in second string. This function assumes that the first
+ // string will not contain two consecutive '*'
+ if (*first == '*' && *(first+1) != '\0' && *second == '\0')
+ return 0;
+
+ // If the first string contains '?', or current characters
+ // of both strings string_match
+ if (*first == '?' || *first == *second)
+ return string_match(first+1, second+1);
+
+ // If there is *, then there are two possibilities
+ // a) We consider current character of second string
+ // b) We ignore current character of second string.
+ if (*first == '*')
+ return string_match(first+1, second) || string_match(first, second+1);
+ return 0;
+}
+
+
+int filter_matches(array *filters, char *string, char **matched_filter)
+{
+ for (int i = 0; i < filters->length; i++)
+ {
+ char *filter = (char *)array_at(filters, i);
+ if (string_match(filter, string))
+ {
+ *matched_filter = filter;
+ return strlen(filter);
+ }
+ }
+ return -1;
+}
+
+search_result *create_empty_search_result()
+{
+ search_result *new_result_buffer = (search_result *)malloc(sizeof(search_result));
+ new_result_buffer->completed_match_threads = 0;
+ new_result_buffer->mutex = mutex_create();
+ new_result_buffer->done_finding_files = false;
+ new_result_buffer->file_list_read_cursor = 0;
+ new_result_buffer->max_thread_count = 4;
+ new_result_buffer->match_count = 0;
+ new_result_buffer->file_count = 0;
+ new_result_buffer->max_file_size = megabytes(1000);
+
+ new_result_buffer->files = array_create(sizeof(found_file));
+ new_result_buffer->files.reserve_jump = FILE_RESERVE_COUNT;
+ array_reserve(&new_result_buffer->files, FILE_RESERVE_COUNT);
+
+ new_result_buffer->matches = array_create(sizeof(file_match));
+ new_result_buffer->matches.reserve_jump = FILE_RESERVE_COUNT;
+ array_reserve(&new_result_buffer->matches, FILE_RESERVE_COUNT);
+
+ // filter buffers
+ new_result_buffer->directory_to_search = (char*)malloc(MAX_INPUT_LENGTH);
+ new_result_buffer->search_text = (char*)malloc(MAX_INPUT_LENGTH);
+
+ return new_result_buffer;
+}
+
+bool string_is_asteriks(char *text)
+{
+ utf8_int32_t ch;
+ while((text = utf8codepoint(text, &ch)) && ch)
+ {
+ if (ch != '*') return false;
+ }
+ return true;
+}
+
+bool string_contains_ex(char *text_to_search, utf8_int8_t *text_to_find, array *text_matches)
+{
+ bool final_result = false;
+ bool is_asteriks_only = false;
+
+ // * wildcard at the start of text to find is not needed
+ if (string_is_asteriks(text_to_find))
+ {
+ is_asteriks_only = true;
+ text_to_find += strlen(text_to_find);
+ }
+
+ // remove all asteriks from start
+ utf8_int32_t br;
+ while(utf8codepoint(text_to_find, &br) && br == '*')
+ {
+ text_to_find = utf8codepoint(text_to_find, &br);
+ }
+
+ char *text_to_find_original = text_to_find;
+ bool save_info = (text_matches != 0);
+
+ utf8_int32_t text_to_search_ch = 0;
+ utf8_int32_t text_to_find_ch = 0;
+ size_t text_to_find_char_len = utf8len(text_to_find);
+
+ int line_nr_val = 1;
+ int word_offset_val = 0;
+ int word_match_len_val = 0;
+ char* line_start_ptr = text_to_search;
+
+ int index = 0;
+ while((text_to_search = utf8codepoint(text_to_search, &text_to_search_ch))
+ && text_to_search_ch)
+ {
+ word_offset_val++;
+ if (text_to_search_ch == '\n')
+ {
+ line_nr_val++;
+ word_offset_val = 0;
+ line_start_ptr = text_to_search;
+ }
+
+ utf8_int8_t *text_to_search_current_attempt = text_to_search;
+ utf8_int32_t text_to_search_current_attempt_ch = text_to_search_ch;
+
+ bool in_wildcard = false;
+
+ text_to_find = utf8codepoint(text_to_find, &text_to_find_ch);
+ //text_to_search_current_attempt = utf8codepoint(text_to_search_current_attempt,
+ //&text_to_search_current_attempt_ch);
+
+ word_match_len_val = 0;
+ while(text_to_search_current_attempt_ch)
+ {
+ // wildcard, accept any character in text to search
+ if (text_to_find_ch == '?')
+ goto continue_search;
+
+ // character matches,
+ if (text_to_find_ch == text_to_search_current_attempt_ch && in_wildcard)
+ in_wildcard = false;
+
+ // wildcard, accept any characters in text to search untill next char is found
+ if (text_to_find_ch == '*')
+ {
+ text_to_find = utf8codepoint(text_to_find, &text_to_find_ch);
+ in_wildcard = true;
+ }
+
+ // text to find has reached 0byte, word has been found
+ if (text_to_find_ch == 0)
+ {
+ done:
+ if (save_info)
+ {
+ text_match new_match;
+ new_match.line_nr = line_nr_val;
+ new_match.word_offset = word_offset_val-1;
+ new_match.word_match_len = word_match_len_val;
+ new_match.line_start = line_start_ptr;
+ new_match.line_info = 0;
+ array_push(text_matches, &new_match);
+ }
+
+ final_result = true;
+
+ if (is_asteriks_only)
+ {
+ return final_result;
+ }
+
+ break;
+ }
+
+ // character does not match, continue search
+ if (text_to_find_ch != text_to_search_current_attempt_ch && !in_wildcard)
+ break;
+
+ continue_search:
+ if (!in_wildcard)
+ text_to_find = utf8codepoint(text_to_find, &text_to_find_ch);
+
+ text_to_search_current_attempt = utf8codepoint(
+ text_to_search_current_attempt,
+ &text_to_search_current_attempt_ch);
+
+ if (!text_to_search_current_attempt_ch && !text_to_find_ch) goto done;
+
+ word_match_len_val++;
+ }
+
+ text_to_find = text_to_find_original;
+ index++;
+ }
+
+ return final_result;
+
+ set_info_and_return_failure:
+ return false;
+} \ No newline at end of file
diff --git a/src/search.h b/src/search.h
new file mode 100644
index 0000000..ebb98f3
--- /dev/null
+++ b/src/search.h
@@ -0,0 +1,64 @@
+#ifndef INCLUDE_SEARCH
+#define INCLUDE_SEARCH
+
+#define MAX_INPUT_LENGTH 4096
+#define MAX_ERROR_MESSAGE_LENGTH (MAX_INPUT_LENGTH)
+#define FILE_RESERVE_COUNT 100000
+#define ERROR_RESERVE_COUNT 100
+
+#include "array.h"
+#include "memory_bucket.h"
+#include "../utf8.h"
+
+typedef struct t_found_file
+{
+ utf8_int8_t *path;
+ int match_count;
+} found_file;
+
+typedef struct t_search_result
+{
+ // data
+ array files;
+ array matches;
+ int match_count;
+ int file_count;
+
+ // thread syncing
+ mutex mutex;
+ int completed_match_threads;
+ int done_finding_files;
+ int file_list_read_cursor;
+
+ // search query
+ utf8_int8_t *directory_to_search;
+ utf8_int8_t *search_text;
+ int max_thread_count;
+ int max_file_size;
+} search_result;
+
+typedef struct t_file_match
+{
+ found_file* file;
+ int line_nr;
+ int word_match_offset;
+ int word_match_length;
+ utf8_int8_t *line_info; // will be null when no match is found
+} file_match;
+
+typedef struct t_text_match
+{
+ int line_nr;
+ int word_offset;
+ int word_match_len;
+ char *line_start;
+ char *line_info;
+} text_match;
+
+array get_filters(char *pattern);
+int filter_matches(array *filters, char *string, char **matched_filter);
+int string_match(char *first, char *second);
+search_result *create_empty_search_result();
+bool string_contains_ex(char *text_to_search, char *text_to_find, array *text_matches);
+
+#endif \ No newline at end of file