diff --git a/FOCUS-CHANGELOG.txt b/FOCUS-CHANGELOG.txt index f4085ad0e..936fd41de 100644 --- a/FOCUS-CHANGELOG.txt +++ b/FOCUS-CHANGELOG.txt @@ -4,7 +4,6 @@ - Settings per file type - Add the ability to assign syntax to a buffer - Basic language definition files for the highlighting of languages we don't yet support -- Config files for directories (i.e. adding a .focus-config file to your directory and have Focus load it) - Identifier search - A little bit of semantic highlighting for Jai - Backup unsaved changes @@ -28,6 +27,10 @@ + Basic JS highlighting (thanks @simonvallz) + It is now possible to use variables in build and run commands: %FILE%, %FILE_DIR%, %FILE_NAME%, %FILE_NAME_NO_EXTENSION%, %BUILD_WORKING_DIR%, %RUN_WORKING_DIR% (see more information in the default config) + New options: `window_width`, `window_height`, `window_x`, `window_y`. Can be used to configure the initial size of the editor window. + + Focus now allows auto loading projects at startup in one of the following ways: + + By passing a project name as a parameter: `focus -project "Project Name"` or `focus -project path/to/project.focus-config`. + + By passing in a directory path which contains a file named `.focus-config`. This file will be loaded as a Focus project. + + By launching Focus from a directory containing a file named `.focus-config`. + Bug fixes: + C highlighting now supports identifiers which start with a UTF8 letter + Other changes: diff --git a/src/config.jai b/src/config.jai index eb2d4003b..aedd109c3 100644 --- a/src/config.jai +++ b/src/config.jai @@ -34,13 +34,12 @@ load_global_config :: (fallback_to_default_on_failure := false, force := false) } } -load_project_config :: (project: string = "", force := false) -> success: bool, changed: bool, there_were_warnings: bool { - if !project && !current_project_name { - log_error("Attempted to load an empty project config. This is a bug."); - return false, false, false; - } - project_name := ifx project then project else current_project_name; - config_path := tprint("%/%.focus-config", projects_dir, project_name); +load_project_config_from_path :: (file_path: string, force := false) -> success: bool, changed: bool, there_were_warnings: bool { + assert(file_path != "", "Attempting to load project config from an empty path. This is a bug."); + + config_path := copy_string(file_path,, temp); + path_overwrite_separators(config_path, #char "/"); + start_watching_file_if_not_already(config_path); loaded_config, changed := load_config(config_path, *project_config, force); @@ -63,8 +62,6 @@ load_project_config :: (project: string = "", force := false) -> success: bool, free_loaded_config(*project_config); project_config = loaded_config; - current_project_name = copy_string(project_name); - return true, changed, loaded_config.there_were_warnings; } else { project_config.hash = loaded_config.hash; @@ -73,6 +70,20 @@ load_project_config :: (project: string = "", force := false) -> success: bool, } } +load_project_config :: (project_name: string = "", force := false) -> success: bool, changed: bool, there_were_warnings: bool { + if !project_name && !(project_config.loaded && project_config.path) { + log_error("Attempted to load an empty project config. This is a bug."); + return false, false, false; + } + + config_path := project_config.path; + if project_name then config_path = tprint("%/%.focus-config", projects_dir, project_name); + + success, changed, there_were_warnings := load_project_config_from_path(config_path, force); + + return success, changed, there_were_warnings; +} + apply_config :: () { // Make sure the values are within the acceptable range old_tab_size := TAB_SIZE; @@ -148,16 +159,16 @@ refresh_config :: (path: string) { add_success_message("Global config changes have been applied", dismiss_in_seconds = 3, tag = .config); } if project_config.loaded { - success, changed, there_were_warnings = load_project_config(current_project_name, force = true); + success, changed, there_were_warnings = load_project_config_from_path(project_config.path, force = true); if success && changed && !there_were_warnings { - add_success_message("Config changes for project '%' have been applied", current_project_name, dismiss_in_seconds = 3, tag = .config); + add_success_message("Config changes for project '%' have been applied", get_current_project_name(), dismiss_in_seconds = 3, tag = .config); } } } else if platform_path_equals(path, project_config.path) { success, changed, there_were_warnings := load_project_config(); if success && changed && !there_were_warnings { clear_user_messages(.config); - add_success_message("Config changes for project '%' have been applied", current_project_name, dismiss_in_seconds = 3, tag = .config); + add_success_message("Config changes for project '%' have been applied", get_current_project_name(), dismiss_in_seconds = 3, tag = .config); } } else { log_error("Attempted to refresh config from file %, but it is not a global config or a current project config", path); @@ -298,6 +309,20 @@ apply_style :: (parsed: Parsed_Config) { platform_set_border_color(); } +get_current_project_name :: () -> string { + if !project_config.loaded return ""; + + path, basename := path_decomp(project_config.path); + + // If the file name is .focus-config, return that + if basename && basename != ".focus-config" return basename; + + // If the file name is .focus-config, return the name of the parent dir + parent_dir := trim_right(path, "/\\"); + _, dir_name := path_decomp(parent_dir); + return dir_name; +} + #scope_file load_config :: (file_path: string, existing: *Loaded_Config, force := false) -> Loaded_Config, changed := true { @@ -309,7 +334,7 @@ load_config :: (file_path: string, existing: *Loaded_Config, force := false) -> file_data, success_read := read_entire_file(file_path); if !success_read { - error_msg := tprint("Couldn't read the config file file '%'", file_path); + error_msg := tprint("Couldn't read the config file '%'", file_path); log_error(error_msg); add_user_error(error_msg); return loaded_config; @@ -433,9 +458,6 @@ font_cache: Table(string, string); // path -> file data // Currently active config config: Config; -// If nonempty, then there's an active project -current_project_name: string; - // Info about currently loaded configs, useful when reloading etc. global_config: Loaded_Config; project_config: Loaded_Config; diff --git a/src/config_parser.jai b/src/config_parser.jai index 8c90df73c..da6421740 100644 --- a/src/config_parser.jai +++ b/src/config_parser.jai @@ -573,7 +573,14 @@ parse_style_line :: (using parser: *Config_Parser, line: string) -> success: boo if value != "default" { file_data, success := read_entire_file(theme_file); if !success return true, tprint("Couldn't read theme file '%'", theme_file); + + migrated, new_file_data := maybe_migrate_config(theme_file, file_data); + if migrated { + add_success_message("Theme file '%' has been migrated to version [%]", theme_file, CURRENT_CONFIG_VERSION); + file_data = new_file_data; + } theme_parse_result = parse_config(value, theme_file, file_data, as_theme = true); + if theme_parse_result.success && migrated then write_entire_file(theme_file, new_file_data); } else { theme_parse_result = parse_config(value, "default", DEFAULT_CONFIG_FILE_DATA); } diff --git a/src/editors.jai b/src/editors.jai index 5070ceeec..ef2b2416a 100644 --- a/src/editors.jai +++ b/src/editors.jai @@ -1366,6 +1366,7 @@ make_editor_active :: (editor_id: s64) { update_window_title :: (buffer_id: s64 = -1) { title := window_generic_title; + current_project_name := get_current_project_name(); if project_config.loaded && current_project_name then title = tprint("% - %", current_project_name, title); if buffer_id >= 0 { buffer := open_buffers[buffer_id]; diff --git a/src/main.jai b/src/main.jai index f1f7b72e9..8dd253cc3 100644 --- a/src/main.jai +++ b/src/main.jai @@ -2,9 +2,6 @@ main :: () { #if DEBUG { UA :: #import "Unmapping_Allocator"; context.allocator = UA.get_unmapping_allocator(); - - // OA :: #import "Overwriting_Allocator"; - // context.allocator = OA.get_overwriting_allocator(); } focus_allocator = context.allocator; // to be used as a default allocator where we need it @@ -41,8 +38,6 @@ main :: () { init_file_watcher(); init_buffers(); load_global_config(fallback_to_default_on_failure = true); - init_workspace(); - init_build_system(); window_x, window_y, window_width, window_height = platform_get_centered_window_dimensions(config.settings.open_on_the_biggest_monitor); @@ -54,41 +49,79 @@ main :: () { window_generic_title = ifx DEBUG then "Focus (debug mode)" else "Focus"; platform_create_window(); - init_session_with_no_guidance :: () #expand { - session = maybe_load_previous_session(); - if session.project { - success := load_project_config(session.project); - if !success then log_error("Couldn't load project config '%'", session.project); - } - } - + // Process command line parameters focus_executable_args = get_command_line_arguments(); - if focus_executable_args.count >= 2 { - session_initialized := false; - for < i: focus_executable_args.count - 1..1 { - if is_directory(focus_executable_args[i]) { - if !session_initialized { - session = start_fresh_session(); - session_initialized = true; + args := focus_executable_args; + if args.count == 3 && args[1] == "-project" { + // - `focus.exe -project "Project Name"` or `focus.exe -project ` will load a project. No other arguments are allowed. + project := args[2]; + success: bool; + if ends_with(project, ".focus-config") { + success = load_project_config_from_path(project); + } else { + success = load_project_config(project); + } + if !success then add_user_error("Couldn't load project config '%' provided in the command line arguments", project); + } + else if args.count >= 2 { + // - `focus.exe ...` will open files or add directories to the workspace + dirs_from_args: [..] string; + dirs_from_args.allocator = temp; + + for path : array_view(args, 1, args.count - 1) { + if is_directory(path) { + dir := trim_right(path, "\\/"); + project_config_path := tprint("%/.focus-config", dir); + if !project_config.loaded && file_exists(project_config_path) { + success := load_project_config_from_path(project_config_path); + if !success then add_user_error("Tried to load project config from % but couldn't due to errors", project_config_path); + } else { + array_add(*dirs_from_args, dir); + } + } else if file_exists(path) { + editors_open_file(path); + } else { + if path == "-project" { + add_user_error("The '-project' command line argument is used incorrectly.\nCorrect usage: focus -project .\nNo other arguments are allowed in this case."); + } else if starts_with(path, "-") { + add_user_error("Invalid command line parameter '%'.", path); + } else { + add_user_error("Invalid command line parameter '%'.\nAttempted to load it as a file or a directory, but it doesn't exist.", path); } - add_directory_to_workspace(focus_executable_args[i], index = 0); } } - for i: 1..focus_executable_args.count - 1 { - if file_exists(focus_executable_args[i]) && !is_directory(focus_executable_args[i]) { - if !session_initialized { - session = start_fresh_session(); - session_initialized = true; - } - editors_open_file(focus_executable_args[i]); + + for < dir : dirs_from_args add_directory_to_workspace(dir, index = 0); + } + + // Maybe load project from a .focus-config file in the current directory + if !project_config.loaded { + if file_exists(".focus-config") { + project_config_path, success := get_absolute_path(".focus-config"); + if success { + success := load_project_config_from_path(project_config_path); + if !success then add_user_error("Tried to load project config from % but couldn't due to errors", project_config_path); } } - if !session_initialized init_session_with_no_guidance(); + } + + // Maybe load previous session project if we haven't loaded anything yet + if !project_config.loaded { + session = maybe_load_previous_session(); + if session.project { + success := load_project_config(session.project); + if !success then add_user_error("Couldn't load project % from previous session", session.project); + } } else { - init_session_with_no_guidance(); + session = start_fresh_session(); } + if project_config.loaded then update_window_title(); + + init_workspace(); + init_build_system(); + // This may generate window resize events if DPI/scaling settings need to be // applied, so make sure to only call it after the window is created. platform_apply_config();