ded

Dramatic EDitor
Index Commits Files Refs README LICENSE
src/main.c (14241B)
   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