File module with read, fmt, and write functionality for the editor

This commit is contained in:
Chadtech 2020-12-04 00:14:32 -05:00
commit 1326219415
9 changed files with 234 additions and 5 deletions

1
Cargo.lock generated
View File

@ -2569,6 +2569,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_fmt",
"roc_gen",
"roc_load",
"roc_module",

View File

@ -23,6 +23,8 @@ roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_gen = { path = "../compiler/gen" }
roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
im = "14" # im and im-rc should always have the same version!

View File

@ -27,6 +27,7 @@ These are potentially inspirational resources for the editor's design.
* [Sketch-n-Sketch: Interactive SVG Programming with Direct Manipulation](https://youtu.be/YuGVC8VqXz0) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/)
* [Xi](https://xi-editor.io/) modern text editor with concurrent editing (related to [Druid](https://github.com/linebender/druid))
* [Self](https://selflanguage.org/) programming language
* [Primitive](https://primitive.io/) code exploration in Virtual Reality
### Debugging
@ -72,6 +73,9 @@ Thoughts and ideas possibly taken from above inspirations or separate.
* Maybe easier to manually trigger a test related to exactly what code you're writing
* Ability to generate unit tests for a selected function in context menu
* A table should appear to enter input and expected output pairs quickly
* Ability to show import connection within project visually
* This could be done by drawing connections between files or functions in the tree view. This would make it easier for people to get their bearings in new big projects.
* Connections could also be drawn between functions that call each other in the tree view. The connections could be animated to show the execution flow of the program.
* "Error mode" where the editor jumps you to the next error
* Similar in theory to diff tools that jump you to the next merge conflict
* dependency recommendation

View File

@ -1,6 +1,98 @@
pub struct File {
path: String,
content: String,
use crate::file::ReadError::DoesntHaveRocExtension;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_parse::ast::{Attempting, Def, Module};
use roc_parse::module::module_defs;
use roc_parse::parser;
use roc_parse::parser::Parser;
use roc_region::all::Located;
use std::ffi::OsStr;
use std::path::Path;
use std::{fs, io};
#[derive(Debug)]
pub struct File<'a> {
path: &'a Path,
module_header: Module<'a>,
content: Vec<'a, Located<Def<'a>>>,
}
impl File {}
#[derive(Debug)]
pub enum ReadError {
Read(std::io::Error),
ParseDefs(parser::Fail),
ParseHeader(parser::Fail),
DoesntHaveRocExtension,
}
impl<'a> File<'a> {
pub fn read(path: &'a Path, arena: &'a Bump) -> Result<File<'a>, ReadError> {
if path.extension() != Some(OsStr::new("roc")) {
return Err(DoesntHaveRocExtension);
}
let bytes = fs::read(path).map_err(ReadError::Read)?;
let allocation = arena.alloc(bytes);
let module_parse_state = parser::State::new(allocation, Attempting::Module);
let parsed_module = roc_parse::module::header().parse(&arena, module_parse_state);
match parsed_module {
Ok((module, state)) => {
let parsed_defs = module_defs().parse(&arena, state);
match parsed_defs {
Ok((defs, _)) => Ok(File {
path,
module_header: module,
content: defs,
}),
Err((error, _)) => Err(ReadError::ParseDefs(error)),
}
}
Err((error, _)) => Err(ReadError::ParseHeader(error)),
}
}
pub fn fmt(&self) -> String {
let arena = Bump::new();
let mut formatted_file = String::new();
let mut module_header_buf = bumpalo::collections::String::new_in(&arena);
fmt_module(&mut module_header_buf, &self.module_header);
formatted_file.push_str(module_header_buf.as_str());
for def in &self.content {
let mut def_buf = bumpalo::collections::String::new_in(&arena);
fmt_def(&mut def_buf, &def.value, 0);
formatted_file.push_str(def_buf.as_str());
}
formatted_file
}
pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> {
let formatted_file = self.fmt();
fs::write(write_path, formatted_file)
}
pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> {
self.fmt_then_write_to(
self.path
.with_file_name(new_name)
.with_extension("roc")
.as_path(),
)
}
pub fn fmt_then_write(&self) -> io::Result<()> {
self.fmt_then_write_to(self.path)
}
}

View File

@ -22,7 +22,7 @@ use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
use winit::event_loop::ControlFlow;
pub mod ast;
mod file;
pub mod file;
mod rect;
pub mod text_state;
mod vertex;

View File

@ -0,0 +1,17 @@
interface Simple
exposes [
v
]
imports []
v : Str
v = "Value!"

View File

@ -0,0 +1,8 @@
interface Simple
exposes [
v
]
imports []
v : Str
v = "Value!"

View File

@ -0,0 +1,68 @@
interface Storage
exposes [
Storage,
decoder,
get,
listener,
set
]
imports [
Map.{ Map },
Json.Decode.{ Decoder } as Decode
Json.Encode as Encode
Ports.FromJs as FromJs
Ports.ToJs as ToJs
]
################################################################################
## TYPES ##
################################################################################
Storage : [
@Storage (Map Str Decode.Value)
]
################################################################################
## API ##
################################################################################
get : Storage, Str, Decoder a -> [ Ok a, NotInStorage, DecodeError Decode.Error ]*
get = \key, decoder, @Storage map ->
when Map.get map key is
Ok json ->
Decode.decodeValue decoder json
Err NotFound ->
NotInStorage
set : Encode.Value, Str -> Effect {}
set json str =
ToJs.type "setStorage"
|> ToJs.setFields [
Field "key" (Encode.str str),
Field "value" json
]
|> ToJs.send
decoder : Decoder Storage
decoder =
Decode.mapType Decode.value
|> Decode.map \map -> @Storage map
################################################################################
## PORTS INCOMING ##
################################################################################
listener : (Storage -> msg) -> FromJs.Listener msg
listener toMsg =
FromJs.listen "storageUpdated"
(Decode.map decoder toMsg)

37
editor/tests/test_file.rs Normal file
View File

@ -0,0 +1,37 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
#[cfg(test)]
mod test_file {
use bumpalo::Bump;
use roc_editor::file::File;
use std::path::Path;
#[test]
fn read_storage() {
let simple_module_path = Path::new("./tests/modules/Simple.roc");
let arena = Bump::new();
let file = File::read(simple_module_path, &arena)
.expect("Could not read Simple.roc in test_file test");
assert_eq!(
file.fmt(),
indoc!(
r#"
interface Simple
exposes [
v
]
imports []
v : Str
v = "Value!""#
)
);
}
}