1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <stdbool.h> 4 #include <errno.h> 5 #include <string.h> 6 7 #include <SDL2/SDL.h> 8 #define GLEW_STATIC 9 #include <GL/glew.h> 10 #define GL_GLEXT_PROTOTYPES 11 #include <SDL2/SDL_opengl.h> 12 13 #include <ft2build.h> 14 #include FT_FREETYPE_H 15 16 #include "./editor.h" 17 #include "./file_browser.h" 18 #include "./la.h" 19 #include "./free_glyph.h" 20 #include "./simple_renderer.h" 21 #include "./common.h" 22 #include "./lexer.h" 23 #include "./sv.h" 24 25 // TODO: Save file dialog 26 // Needed when ded is ran without any file so it does not know where to save. 27 28 // TODO: An ability to create a new file 29 // TODO: Jump up/down by paragraph 30 // TODO: Delete a word 31 // TODO: Delete selection 32 // TODO: Jump to the beginning/end of the line 33 34 void MessageCallback(GLenum source, 35 GLenum type, 36 GLuint id, 37 GLenum severity, 38 GLsizei length, 39 const GLchar* message, 40 const void* userParam) 41 { 42 (void) source; 43 (void) id; 44 (void) length; 45 (void) userParam; 46 fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", 47 (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), 48 type, severity, message); 49 } 50 51 static Free_Glyph_Atlas atlas = {0}; 52 static Simple_Renderer sr = {0}; 53 static Editor editor = {0}; 54 static File_Browser fb = {0}; 55 56 // TODO: display errors reported via flash_error right in the text editor window somehow 57 #define flash_error(...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while(0) 58 59 60 int main(int argc, char **argv) 61 { 62 Errno err; 63 64 FT_Library library = {0}; 65 66 FT_Error error = FT_Init_FreeType(&library); 67 if (error) { 68 fprintf(stderr, "ERROR: Could not initialize FreeType2 library\n"); 69 return 1; 70 } 71 72 // TODO: users should be able to customize the font 73 // const char *const font_file_path = "./fonts/VictorMono-Regular.ttf"; 74 const char *const font_file_path = "./fonts/iosevka-regular.ttf"; 75 76 FT_Face face; 77 error = FT_New_Face(library, font_file_path, 0, &face); 78 if (error == FT_Err_Unknown_File_Format) { 79 fprintf(stderr, "ERROR: `%s` has an unknown format\n", font_file_path); 80 return 1; 81 } else if (error) { 82 fprintf(stderr, "ERROR: Could not load file `%s`\n", font_file_path); 83 return 1; 84 } 85 86 FT_UInt pixel_size = FREE_GLYPH_FONT_SIZE; 87 // TODO: FT_Set_Pixel_Sizes does not produce good looking results 88 // We need to use something like FT_Set_Char_Size and properly set the device resolution 89 error = FT_Set_Pixel_Sizes(face, 0, pixel_size); 90 if (error) { 91 fprintf(stderr, "ERROR: Could not set pixel size to %u\n", pixel_size); 92 return 1; 93 } 94 95 if (argc > 1) { 96 const char *file_path = argv[1]; 97 err = editor_load_from_file(&editor, file_path); 98 if (err != 0) { 99 fprintf(stderr, "ERROR: Could not read file %s: %s\n", file_path, strerror(err)); 100 return 1; 101 } 102 } 103 104 const char *dir_path = "."; 105 err = fb_open_dir(&fb, dir_path); 106 if (err != 0) { 107 fprintf(stderr, "ERROR: Could not read directory %s: %s\n", dir_path, strerror(err)); 108 return 1; 109 } 110 111 if (SDL_Init(SDL_INIT_VIDEO) < 0) { 112 fprintf(stderr, "ERROR: Could not initialize SDL: %s\n", SDL_GetError()); 113 return 1; 114 } 115 116 SDL_Window *window = 117 SDL_CreateWindow("ded", 118 0, 0, 119 SCREEN_WIDTH, SCREEN_HEIGHT, 120 SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL); 121 if (window == NULL) { 122 fprintf(stderr, "ERROR: Could not create SDL window: %s\n", SDL_GetError()); 123 return 1; 124 } 125 126 { 127 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 128 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); 129 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 130 131 int major; 132 int minor; 133 SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major); 134 SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor); 135 printf("GL version %d.%d\n", major, minor); 136 } 137 138 if (SDL_GL_CreateContext(window) == NULL) { 139 fprintf(stderr, "ERROR: Could not create OpenGL context: %s\n", SDL_GetError()); 140 return 1; 141 } 142 143 if (GLEW_OK != glewInit()) { 144 fprintf(stderr, "ERROR: Could not initialize GLEW!"); 145 return 1; 146 } 147 148 glEnable(GL_BLEND); 149 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 150 151 if (GLEW_ARB_debug_output) { 152 glEnable(GL_DEBUG_OUTPUT); 153 glDebugMessageCallback(MessageCallback, 0); 154 } else { 155 fprintf(stderr, "WARNING: GLEW_ARB_debug_output is not available"); 156 } 157 158 simple_renderer_init(&sr); 159 free_glyph_atlas_init(&atlas, face); 160 161 editor.atlas = &atlas; 162 editor_retokenize(&editor); 163 164 bool quit = false; 165 bool file_browser = false; 166 while (!quit) { 167 const Uint32 start = SDL_GetTicks(); 168 SDL_Event event = {0}; 169 while (SDL_PollEvent(&event)) { 170 switch (event.type) { 171 case SDL_QUIT: { 172 quit = true; 173 } 174 break; 175 176 case SDL_KEYDOWN: { 177 if (file_browser) { 178 switch (event.key.keysym.sym) { 179 case SDLK_F3: { 180 file_browser = false; 181 } 182 break; 183 184 case SDLK_UP: { 185 if (fb.cursor > 0) fb.cursor -= 1; 186 } 187 break; 188 189 case SDLK_DOWN: { 190 if (fb.cursor + 1 < fb.files.count) fb.cursor += 1; 191 } 192 break; 193 194 case SDLK_RETURN: { 195 const char *file_path = fb_file_path(&fb); 196 if (file_path) { 197 File_Type ft; 198 err = type_of_file(file_path, &ft); 199 if (err != 0) { 200 flash_error("Could not determine type of file %s: %s", file_path, strerror(err)); 201 } else { 202 switch (ft) { 203 case FT_DIRECTORY: { 204 err = fb_change_dir(&fb); 205 if (err != 0) { 206 flash_error("Could not change directory to %s: %s", file_path, strerror(err)); 207 } 208 } 209 break; 210 211 case FT_REGULAR: { 212 // TODO: before opening a new file make sure you don't have unsaved changes 213 // And if you do, annoy the user about it. (just like all the other editors do) 214 err = editor_load_from_file(&editor, file_path); 215 if (err != 0) { 216 flash_error("Could not open file %s: %s", file_path, strerror(err)); 217 } else { 218 file_browser = false; 219 } 220 } 221 break; 222 223 case FT_OTHER: { 224 flash_error("%s is neither a regular file nor a directory. We can't open it.", file_path); 225 } 226 break; 227 228 default: 229 UNREACHABLE("unknown File_Type"); 230 } 231 } 232 } 233 } 234 break; 235 } 236 } else { 237 switch (event.key.keysym.sym) { 238 case SDLK_BACKSPACE: { 239 editor_backspace(&editor); 240 editor.last_stroke = SDL_GetTicks(); 241 } 242 break; 243 244 case SDLK_F2: { 245 if (editor.file_path.count > 0) { 246 err = editor_save(&editor); 247 if (err != 0) { 248 flash_error("Could not save currently edited file: %s", strerror(err)); 249 } 250 } else { 251 // TODO: ask the user for the path to save to in this situation 252 flash_error("Nowhere to save the text"); 253 } 254 } 255 break; 256 257 case SDLK_F3: { 258 file_browser = true; 259 } 260 break; 261 262 case SDLK_F5: { 263 simple_renderer_reload_shaders(&sr); 264 } 265 break; 266 267 case SDLK_RETURN: { 268 editor_insert_char(&editor, '\n'); 269 editor.last_stroke = SDL_GetTicks(); 270 } 271 break; 272 273 case SDLK_DELETE: { 274 editor_delete(&editor); 275 editor.last_stroke = SDL_GetTicks(); 276 } 277 break; 278 279 case SDLK_a: { 280 if (event.key.keysym.mod & KMOD_CTRL) { 281 editor.selection = true; 282 editor.select_begin = 0; 283 editor.cursor = editor.data.count; 284 } 285 } 286 break; 287 288 case SDLK_TAB: { 289 // TODO: indent on Tab instead of just inserting 4 spaces at the cursor 290 // That is insert the spaces at the beginning of the line. Shift+TAB should 291 // do unindent, that is remove 4 spaces from the beginning of the line. 292 // TODO: customizable indentation style 293 // - tabs/spaces 294 // - tab width 295 // - etc. 296 for (size_t i = 0; i < 4; ++i) { 297 editor_insert_char(&editor, ' '); 298 } 299 } 300 break; 301 302 case SDLK_c: { 303 if (event.key.keysym.mod & KMOD_CTRL) { 304 editor_clipboard_copy(&editor); 305 } 306 } 307 break; 308 309 case SDLK_v: { 310 if (event.key.keysym.mod & KMOD_CTRL) { 311 editor_clipboard_paste(&editor); 312 } 313 } 314 break; 315 316 case SDLK_UP: { 317 editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); 318 editor_move_line_up(&editor); 319 editor.last_stroke = SDL_GetTicks(); 320 } 321 break; 322 323 case SDLK_DOWN: { 324 editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); 325 editor_move_line_down(&editor); 326 editor.last_stroke = SDL_GetTicks(); 327 } 328 break; 329 330 case SDLK_LEFT: { 331 editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); 332 if (event.key.keysym.mod & KMOD_CTRL) { 333 editor_move_word_left(&editor); 334 } else { 335 editor_move_char_left(&editor); 336 } 337 editor.last_stroke = SDL_GetTicks(); 338 } 339 break; 340 341 case SDLK_RIGHT: { 342 editor_update_selection(&editor, event.key.keysym.mod & KMOD_SHIFT); 343 if (event.key.keysym.mod & KMOD_CTRL) { 344 editor_move_word_right(&editor); 345 } else { 346 editor_move_char_right(&editor); 347 } 348 editor.last_stroke = SDL_GetTicks(); 349 } 350 break; 351 } 352 } 353 } 354 break; 355 356 case SDL_TEXTINPUT: { 357 if (file_browser) { 358 // Nothing for now 359 // Once we have incremental search in the file browser this may become useful 360 } else { 361 const char *text = event.text.text; 362 size_t text_len = strlen(text); 363 for (size_t i = 0; i < text_len; ++i) { 364 editor_insert_char(&editor, text[i]); 365 } 366 editor.last_stroke = SDL_GetTicks(); 367 } 368 } 369 break; 370 } 371 } 372 373 { 374 int w, h; 375 SDL_GetWindowSize(window, &w, &h); 376 // TODO(#19): update the viewport and the resolution only on actual window change 377 glViewport(0, 0, w, h); 378 } 379 380 Vec4f bg = hex_to_vec4f(0x181818FF); 381 glClearColor(bg.x, bg.y, bg.z, bg.w); 382 glClear(GL_COLOR_BUFFER_BIT); 383 384 if (file_browser) { 385 fb_render(&fb, window, &atlas, &sr); 386 } else { 387 editor_render(window, &atlas, &sr, &editor); 388 } 389 390 SDL_GL_SwapWindow(window); 391 392 const Uint32 duration = SDL_GetTicks() - start; 393 const Uint32 delta_time_ms = 1000 / FPS; 394 if (duration < delta_time_ms) { 395 SDL_Delay(delta_time_ms - duration); 396 } 397 } 398 399 return 0; 400 } 401 402 // TODO: ability to search within file browser 403 // Very useful when you have a lot of files 404 // TODO: ability to search with the text editor 405 // TODO: ability to remove trailing whitespaces
