diff --git a/.gitignore b/.gitignore index 2966d26ee3..0028d22320 100644 --- a/.gitignore +++ b/.gitignore @@ -25,9 +25,8 @@ editor/benches/resources/25000000_lines.roc editor/benches/resources/50000_lines.roc editor/benches/resources/500_lines.roc -# file editor creates when none is selected -UntitledApp.roc -NewRocProject +# file editor creates when no arg is passed +new-roc-project # rust cache (sccache folder) sccache_dir diff --git a/cli/src/main.rs b/cli/src/main.rs index cd9cac0752..2bb7cd223d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -28,7 +28,7 @@ fn main() -> io::Result<()> { } None => { - launch_editor(&[])?; + launch_editor(None)?; Ok(0) } @@ -60,16 +60,13 @@ If you're building the compiler from source you'll want to do `cargo run [FILE]` .subcommand_matches(CMD_EDIT) .unwrap() .values_of_os(DIRECTORY_OR_FILES) + .map(|mut values| values.next()) { - None => { - launch_editor(&[])?; + Some(Some(os_str)) => { + launch_editor(Some(&Path::new(os_str)))?; } - Some(values) => { - let paths = values - .map(|os_str| Path::new(os_str)) - .collect::>(); - - launch_editor(&paths)?; + _ => { + launch_editor(None)?; } } @@ -156,8 +153,8 @@ fn roc_files_recursive>( } #[cfg(feature = "editor")] -fn launch_editor(filepaths: &[&Path]) -> io::Result<()> { - roc_editor::launch(filepaths) +fn launch_editor(project_dir_path: Option<&Path>) -> io::Result<()> { + roc_editor::launch(project_dir_path) } #[cfg(not(feature = "editor"))] diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 6c0f33c81d..ed0e396b99 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1949,6 +1949,7 @@ fn update<'a>( } if module_id == state.root_id && state.goal_phase == Phase::SolveTypes { + dbg!(&state); debug_assert!(work.is_empty()); debug_assert!(state.dependencies.solved_all()); diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 405a0773a2..df7a18058b 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -20,10 +20,10 @@ use crate::graphics::{ use crate::lang::expr::Env; use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::CaretPos; -use crate::ui::util::slice_get; +use crate::ui::util::path_to_string; use bumpalo::Bump; use cgmath::Vector2; -use fs_extra::dir::{CopyOptions, copy}; +use fs_extra::dir::{CopyOptions, DirEntryAttr, DirEntryValue, copy, ls}; use pipelines::RectResources; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; @@ -31,6 +31,7 @@ use roc_load; use roc_load::file::LoadedModule; use roc_module::symbol::IdentIds; use roc_types::subs::VarStore; +use std::collections::HashSet; use std::fs::{self, File}; use std::io::Write; use std::{error::Error, io, path::Path}; @@ -52,26 +53,14 @@ use winit::{ /// The editor is actually launched from the CLI if you pass it zero arguments, /// or if you provide it 1 or more files or directories to open on launch. -pub fn launch(filepaths: &[&Path]) -> io::Result<()> { - //TODO support using multiple filepaths - let first_path_opt = if !filepaths.is_empty() { - match slice_get(0, filepaths) { - Ok(path_ref_ref) => Some(*path_ref_ref), - Err(e) => { - eprintln!("{}", e); - None - } - } - } else { - None - }; +pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> { - run_event_loop(first_path_opt).expect("Error running event loop"); + run_event_loop(project_dir_path_opt).expect("Error running event loop"); Ok(()) } -fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { +fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box> { env_logger::init(); // Open window and create a surface @@ -138,9 +127,12 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let env_arena = Bump::new(); let code_arena = Bump::new(); - let (file_path, code_str) = read_file(file_path_opt); + let (file_path_str, code_str) = read_main_roc_file(project_dir_path_opt); + println!("Loading file {}...", file_path_str); - let loaded_module = load_module(file_path); + let file_path = Path::new(&file_path_str); + + let loaded_module = load_module(&file_path); let mut var_store = VarStore::default(); let dep_idents = IdentIds::exposed_builtins(8); @@ -404,27 +396,69 @@ fn begin_render_pass<'a>( }) } -fn read_file(file_path_opt: Option<&Path>) -> (&Path, String) { - if let Some(file_path) = file_path_opt { - let file_as_str = std::fs::read_to_string(file_path) - .unwrap_or_else(|_| panic!("Failed to read from provided file path: {:?}", file_path)); +type PathStr = String; + +fn read_main_roc_file(project_dir_path_opt: Option<&Path>) -> (PathStr, String) { + if let Some(project_dir_path) = project_dir_path_opt { + + let mut ls_config = HashSet::new(); + ls_config.insert(DirEntryAttr::FullName); + + let dir_items = ls(project_dir_path, &ls_config) + .unwrap_or_else(|err| panic!("Failed to list items in project directory: {:?}", err)) + .items; + + let file_names_2d: Vec> = + dir_items + .iter() + .map(|info_hash_map| info_hash_map.values().map( + |dir_entry_value| + if let DirEntryValue::String(file_name) = dir_entry_value { + Some(file_name) + } else { + None + } + ) + .filter_map(|x| x) // remove None + .collect()) + .collect(); + + let roc_file_names: Vec<&String> = + file_names_2d + .into_iter() + .flatten() + .filter(|file_name| file_name.contains(".roc")) + .collect(); + + let project_dir_path_str = path_to_string(project_dir_path); + + if let Some(&roc_file_name) = roc_file_names.first() { + let full_roc_file_path_str = vec![project_dir_path_str.clone(), "/".to_owned(), roc_file_name.clone()].join(""); + let file_as_str = std::fs::read_to_string(&Path::new(&full_roc_file_path_str)) + .unwrap_or_else(|err| panic!("In the provided project {:?}, I found the roc file {}, but I failed to read it: {}", &project_dir_path_str, &full_roc_file_path_str, err)); + + (full_roc_file_path_str, file_as_str) + } else { + init_new_roc_project(&project_dir_path_str) + } - (file_path, file_as_str) } else { - init_new_roc_project() + init_new_roc_project("new-roc-project") } } // returns path and content of app file -fn init_new_roc_project() -> (&'static Path, String) { +fn init_new_roc_project(project_dir_path_str: &str) -> (PathStr, String) { let orig_platform_path = Path::new("./examples/hello-world/platform"); - let project_dir_path = Path::new("./NewRocProject"); + let project_dir_path = Path::new(project_dir_path_str); - let roc_file_path = Path::new("./NewRocProject/UntitledApp.roc"); + let roc_file_path_str = vec![project_dir_path_str, "/UntitledApp.roc"].join(""); + let roc_file_path = Path::new("./new-roc-project/UntitledApp.roc"); - let project_platform_path = Path::new("./NewRocProject/platform"); + let project_platform_path_str = vec![project_dir_path_str, "/platform"].join(""); + let project_platform_path = Path::new(&project_platform_path_str); if !project_dir_path.exists(){ fs::create_dir(project_dir_path).expect("Failed to create dir for roc project."); @@ -434,7 +468,7 @@ fn init_new_roc_project() -> (&'static Path, String) { let code_str = create_roc_file_if_not_exists(project_dir_path, roc_file_path); - (roc_file_path, code_str) + (roc_file_path_str, code_str) } @@ -476,7 +510,7 @@ fn copy_roc_platform_if_not_exists(orig_platform_path: &Path, project_platform_p Are you at the root of the roc repository?"#, orig_platform_path ); - } else { + } else if !project_platform_path.exists() { copy(orig_platform_path, project_dir_path, &CopyOptions::new()).unwrap_or_else(|err|{ panic!(r#"No roc file path was passed to the editor, so I wanted to create a new roc project and roc projects require a platform, I tried to copy the platform at {:?} to {:?} but it failed: {}"#, diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 5838098767..9eac8e95d0 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -551,8 +551,10 @@ impl<'a> EdModel<'a> { } } - fn replace_selected_expr_with_blank(&mut self) -> EdResult<()> { - let expr_mark_node_id_opt = if let Some(sel_block) = &self.selected_block_opt { + // Replaces selected expression with blank. + // If no expression is selected, this function will select one to guide the user to using backspace in a projectional editing way + fn backspace(&mut self) -> EdResult<()> { + if let Some(sel_block) = &self.selected_block_opt { let expr2_level_mark_node = self.mark_node_pool.get(sel_block.mark_node_id); let newlines_at_end = expr2_level_mark_node.get_newlines_at_end(); @@ -581,13 +583,8 @@ impl<'a> EdModel<'a> { } } - Some(sel_block.mark_node_id) - } else { - None - }; + let expr_mark_node_id = sel_block.mark_node_id; - // have to split the previous `if` up to prevent borrowing issues - if let Some(expr_mark_node_id) = expr_mark_node_id_opt { let caret_pos = self.get_caret(); EdModel::insert_between_line( @@ -598,9 +595,11 @@ impl<'a> EdModel<'a> { &mut self.grid_node_map, &mut self.code_lines, )?; - } - self.set_sel_none(); + self.set_sel_none(); + } else { + self.select_expr()?; + }; Ok(()) } @@ -1120,7 +1119,7 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult // On Linux, '\u{8}' is backspace, // on macOS '\u{7f}'. - ed_model.replace_selected_expr_with_blank()?; + ed_model.backspace()?; InputOutcome::Accepted } diff --git a/editor/src/lang/parse.rs b/editor/src/lang/parse.rs index e7487f35d4..9c7253bb75 100644 --- a/editor/src/lang/parse.rs +++ b/editor/src/lang/parse.rs @@ -88,7 +88,7 @@ impl AppHeader { // TODO don't use mock struct and actually parse string pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> Self { AppHeader { - app_name: "\"untitled_app\"".to_owned(), + app_name: "\"untitled-app\"".to_owned(), packages_base: "\"platform\"".to_owned(), imports: vec![], provides: vec!["main".to_owned()], diff --git a/editor/src/lib.rs b/editor/src/lib.rs index f94ac4ccc8..f6f3053840 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -17,6 +17,6 @@ mod window; use std::io; use std::path::Path; -pub fn launch(filepaths: &[&Path]) -> io::Result<()> { - editor::main::launch(filepaths) +pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> { + editor::main::launch(project_dir_path_opt) }