Merge branch 'trunk' into docs-work

This commit is contained in:
Chadtech 2021-04-18 18:02:06 -04:00 committed by GitHub
commit 11ffc707b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 812 additions and 266 deletions

1
.llvmenv Normal file
View File

@ -0,0 +1 @@
10.0.0

View File

@ -7,7 +7,6 @@ use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::env;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
@ -21,6 +20,18 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
));
}
pub enum BuildOutcome {
NoProblems,
OnlyWarnings,
Errors,
}
pub struct BuiltFile {
pub binary_path: PathBuf,
pub outcome: BuildOutcome,
pub total_time: Duration,
}
pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple,
@ -29,7 +40,7 @@ pub fn build_file<'a>(
opt_level: OptLevel,
emit_debug_info: bool,
link_type: LinkType,
) -> Result<PathBuf, LoadingProblem<'a>> {
) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
@ -184,26 +195,23 @@ pub fn build_file<'a>(
todo!("gracefully handle error after `rustc` spawned");
});
let link_end = link_start.elapsed().unwrap();
let linking_time = link_start.elapsed().unwrap();
if emit_debug_info {
println!("Finished linking in {} ms\n", link_end.as_millis());
println!("Finished linking in {} ms\n", linking_time.as_millis());
}
let total_time = compilation_start.elapsed().unwrap();
// If the cmd errored out, return the Err.
cmd_result?;
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
// TODO change this to report whether there were errors or warnings!
let outcome = BuildOutcome::NoProblems;
let total_end = compilation_start.elapsed().unwrap();
println!(
"🎉 Built {} in {} ms",
generated_filename.to_str().unwrap(),
total_end.as_millis()
);
Ok(binary_path)
Ok(BuiltFile {
binary_path,
total_time,
outcome,
})
}

View File

@ -1,12 +1,13 @@
#[macro_use]
extern crate clap;
use build::{build_file, BuildOutcome, BuiltFile};
use bumpalo::Bump;
use clap::ArgMatches;
use clap::{App, Arg};
use clap::{App, AppSettings, Arg, ArgMatches};
use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
use std::process;
@ -16,18 +17,25 @@ use target_lexicon::Triple;
pub mod build;
pub mod repl;
pub static FLAG_DEBUG: &str = "debug";
pub static FLAG_OPTIMIZE: &str = "optimize";
pub static FLAG_ROC_FILE: &str = "ROC_FILE";
pub static DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const CMD_RUN: &str = "run";
pub const CMD_BUILD: &str = "build";
pub const CMD_REPL: &str = "repl";
pub const CMD_EDIT: &str = "edit";
pub const CMD_DOCS: &str = "docs";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const ROC_FILE: &str = "ROC_FILE";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
pub fn build_app<'a>() -> App<'a> {
App::new("roc")
.version(crate_version!())
.subcommand(App::new("build")
.subcommand(App::new(CMD_BUILD)
.about("Build a program")
.arg(
Arg::with_name(FLAG_ROC_FILE)
Arg::with_name(ROC_FILE)
.help("The .roc file to build")
.required(true),
)
@ -44,13 +52,9 @@ pub fn build_app<'a>() -> App<'a> {
.required(false),
)
)
.subcommand(App::new("run")
.subcommand(App::new(CMD_RUN)
.about("Build and run a program")
.arg(
Arg::with_name(FLAG_ROC_FILE)
.help("The .roc file to build and run")
.required(true),
)
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
@ -63,11 +67,21 @@ pub fn build_app<'a>() -> App<'a> {
.help("Store LLVM debug information in the generated program")
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file of an app to build and run")
.required(true),
)
.arg(
Arg::with_name(ARGS_FOR_APP)
.help("Arguments to pass into the app being run")
.multiple(true),
)
)
.subcommand(App::new("repl")
.subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(App::new("edit")
.subcommand(App::new(CMD_EDIT)
.about("Launch the Roc editor")
.arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
@ -77,7 +91,7 @@ pub fn build_app<'a>() -> App<'a> {
)
)
.subcommand(
App::new("docs")
App::new(CMD_DOCS)
.about("Generate documentation for Roc modules")
.arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
@ -97,10 +111,18 @@ pub fn docs(files: Vec<PathBuf>) {
)
}
pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
let arena = Bump::new();
let filename = matches.value_of(FLAG_ROC_FILE).unwrap();
pub enum BuildConfig {
BuildOnly,
BuildAndRun { roc_file_arg_index: usize },
}
pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use BuildConfig::*;
let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap();
let original_cwd = std::env::current_dir()?;
let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
OptLevel::Optimize
} else {
@ -130,7 +152,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
}
});
let res_binary_path = build::build_file(
let res_binary_path = build_file(
&arena,
target,
src_dir,
@ -141,23 +163,77 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
);
match res_binary_path {
Ok(binary_path) => {
if run_after_build {
// Run the compiled app
Command::new(binary_path)
.spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing");
Ok(BuiltFile {
binary_path,
outcome,
total_time,
}) => {
match config {
BuildOnly => {
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
println!(
"🎉 Built {} in {} ms",
generated_filename.to_str().unwrap(),
total_time.as_millis()
);
// Return a nonzero exit code if there were problems
let status_code = match outcome {
BuildOutcome::NoProblems => 0,
BuildOutcome::OnlyWarnings => 1,
BuildOutcome::Errors => 2,
};
Ok(status_code)
}
BuildAndRun { roc_file_arg_index } => {
let mut cmd = Command::new(binary_path);
// Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like:
//
// roc run app.roc foo bar baz
//
// ...and have it so that app.roc will receive only `foo`,
// `bar`, and `baz` as its arguments.
for (index, arg) in std::env::args().enumerate() {
if index > roc_file_arg_index {
cmd.arg(arg);
}
}
// Run the compiled app
let exit_status = cmd
.current_dir(original_cwd)
.spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing when roc run spawns a subprocess for the compiled app");
// `roc run` exits with the same status code as the app it ran.
//
// If you want to know whether there were compilation problems
// via status code, use either `roc build` or `roc check` instead!
match exit_status.code() {
Some(code) => Ok(code),
None => {
todo!("TODO gracefully handle the roc run subprocess terminating with a signal.");
}
}
}
}
}
Err(LoadingProblem::FormattedReport(report)) => {
print!("{}", report);
Ok(1)
}
Err(other) => {
panic!("build_file failed with error:\n{:?}", other);
}
}
Ok(())
}

View File

@ -1,4 +1,7 @@
use roc_cli::{build, build_app, docs, repl, DIRECTORY_OR_FILES};
use roc_cli::{
build, build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_DOCS, CMD_EDIT, CMD_REPL, CMD_RUN,
DIRECTORY_OR_FILES, ROC_FILE,
};
use std::io;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
@ -6,38 +9,58 @@ use target_lexicon::Triple;
fn main() -> io::Result<()> {
let matches = build_app().get_matches();
match matches.subcommand_name() {
None => roc_editor::launch(&[]),
Some("build") => build(
let exit_code = match matches.subcommand_name() {
None => {
roc_editor::launch(&[])?;
// rustc couldn't infer the error type here
Result::<i32, io::Error>::Ok(0)
}
Some(CMD_BUILD) => Ok(build(
&Triple::host(),
matches.subcommand_matches("build").unwrap(),
false,
),
Some("run") => build(
&Triple::host(),
matches.subcommand_matches("run").unwrap(),
true,
),
Some("repl") => repl::main(),
Some("edit") => {
matches.subcommand_matches(CMD_BUILD).unwrap(),
BuildConfig::BuildOnly,
)?),
Some(CMD_RUN) => {
let subcmd_matches = matches.subcommand_matches(CMD_RUN).unwrap();
let roc_file_arg_index = subcmd_matches.index_of(ROC_FILE).unwrap() + 1; // Not sure why this +1 is necessary, but it is!
Ok(build(
&Triple::host(),
subcmd_matches,
BuildConfig::BuildAndRun { roc_file_arg_index },
)?)
}
Some(CMD_REPL) => {
repl::main()?;
// Exit 0 if the repl exited normally
Ok(0)
}
Some(CMD_EDIT) => {
match matches
.subcommand_matches("edit")
.subcommand_matches(CMD_EDIT)
.unwrap()
.values_of_os(DIRECTORY_OR_FILES)
{
None => roc_editor::launch(&[]),
None => {
roc_editor::launch(&[])?;
}
Some(values) => {
let paths = values
.map(|os_str| Path::new(os_str))
.collect::<Vec<&Path>>();
roc_editor::launch(&paths)
roc_editor::launch(&paths)?;
}
}
// Exit 0 if the editor exited normally
Ok(0)
}
Some("docs") => {
Some(CMD_DOCS) => {
let values = matches
.subcommand_matches("docs")
.subcommand_matches(CMD_DOCS)
.unwrap()
.values_of_os(DIRECTORY_OR_FILES)
.unwrap();
@ -48,8 +71,10 @@ fn main() -> io::Result<()> {
docs(paths);
Ok(())
Ok(0)
}
_ => unreachable!(),
}
}?;
std::process::exit(exit_code);
}

View File

@ -41,8 +41,8 @@
</main>
<footer>
<p>Made by people who like to make nice things.</p>
<p>© 2020-present</p>
<p>© 2020</p>
</footer>
</body>
</html>
</html>

View File

@ -19,7 +19,6 @@ roc_unify = { path = "../compiler/unify" }
roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
roc_solve = { path = "../compiler/solve" }
# 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
ven_graph = { path = "../vendor/pathfinding" }
im = "15" # im and im-rc should always have the same version!
im-rc = "15" # im and im-rc should always have the same version!

View File

@ -8,7 +8,7 @@ sudo apt install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
- Run the following from the roc folder:
```
cargo run edit examples/hello-world/Hello.roc
cargo run edit
```
## Troubleshooting

View File

@ -63,6 +63,28 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* [Sourcetrail](https://www.sourcetrail.com/) nice tree-like source explorer.
* [Unisonweb](https://www.unisonweb.org), definition based [editor](https://twitter.com/shojberg/status/1364666092598288385) as opposed to file based.
### Voice Interaction Related
* We should label as many things as possible and expose jumps to those labels as shortkeys.
* Update without user interaction. e.g. autosave.
* Could be efficient way to communicate with smart assistant.
* You don't have to remember complex keyboard shortcuts if you can describe actions to execute them. Examples:
* Add latest datetime package to dependencies.
* Generate unit test for this function.
* Show edit history for this function.
* Adjusting settings: switch to light theme, increase font size...
* Use (context specific) voice command state machine to assist Machine Learning voice recognition model.
* Nice special use case: using voice to code while on treadmill desk.
#### Inspiration
* Voice control and eye tracking with [Talon](https://github.com/Gauteab/talon-tree-sitter-service)
* [Seminar about programming by voice](https://www.youtube.com/watch?v=G8B71MbA9u4)
* [Talon voice commands in elm](https://github.com/Gauteab/talon-tree-sitter-service)
* Mozilla DeepSpeech model runs fast, works pretty well for actions but would need additional training for code input.
Possible to reuse [Mozilla common voice](https://github.com/common-voice/common-voice) for creating more "spoken code" data.
### Productivity features
* When refactoring;
@ -81,7 +103,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* File history timeline view. Show timeline with commits that changed this file, the number of lines added and deleted as well as which user made the changes. Arrow navigation should allow you to quickly view different versions of the file.
* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to apppear, after which you click to apply the fix you want :( .
* Regex-like find and substitution based on plain english description and example (replacement). i.e. replace all `[` between double quotes with `{`. [Inspiration](https://alexmoltzau.medium.com/english-to-regex-thanks-to-gpt-3-13f03b68236e).
* Show productivity tips based on behavior. i.e. if the user is scrolling through the error bar and clicking on the next error several times, show a tip with "go to next error" shortcut.
#### Autocomplete
- Use more space for autocomplete options:
@ -117,16 +139,6 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* A simpler start for this idea without user data gathering: how the user a code snippet that is most similar to what they are currently writing. Snippets can be aggregated from examples, tests, docstrings at zero cost to the package/platform authors.
* See [codata](https://www.codota.com/code/java/classes/okhttp3.OkHttpClient) for inspiration on a snippet/example finder.
* Fuzzy natural language based setting adjustment in search bar or with voice input: increase font size, enable autosave, switch to light theme...
* Voice input:
* Good for accessibility.
* https://www.youtube.com/watch?v=Ffa3cXM7bjc is interesting for inspiration.
* Could be efficient way to communicate with smart assistant.
* Describe actions to execute them, examples:
* Add latest datetime package to dependencies.
* Generate unit test for this function.
* Show edit history for this function.
* Mozilla DeepSpeech model runs fast, works pretty well for actions but would need additional training for code input.
Possible to reuse [Mozilla common voice](https://github.com/common-voice/common-voice) for creating more "spoken code" data.
* Detect deviation of best practices, example case: alert developer when they are defining a color inline (rgb(30,30,30)) while all colors have been previously imported from a single file. See also [Codota](https://www.codota.com).

View File

@ -1,4 +1,5 @@
use crate::ui::text::lines::Lines;
use crate::ui::text::selection::Selection;
use crate::ui::ui_error::UIResult;
use crate::ui::util::slice_get;
use crate::ui::util::slice_get_mut;
@ -47,6 +48,18 @@ impl CodeLines {
Ok(())
}
pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> {
if selection.is_on_same_line() {
let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?;
line_ref.drain(selection.start_pos.column..selection.end_pos.column);
} else {
// TODO support multiline selections
}
Ok(())
}
}
impl Lines for CodeLines {

View File

@ -1,4 +1,5 @@
use crate::editor::slow_pool::MarkNodeId;
use crate::ui::ui_error::UIResult;
use colored::*;
use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu};
@ -71,6 +72,11 @@ pub enum EdError {
backtrace: Backtrace,
},
#[snafu(display(
"MissingSelection: ed_model.selected_expr2_id was Some(NodeId<Expr2>) but ed_model.caret_w_sel_vec did not contain any Some(Selection)."
))]
MissingSelection { backtrace: Backtrace },
#[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))]
NestedNodeMissingChild {
node_id: MarkNodeId,
@ -96,6 +102,15 @@ pub enum EdError {
#[snafu(display("NodeWithoutAttributes: expected to have a node with attributes. This is a Nested MarkupNode, only Text and Blank nodes have attributes."))]
NodeWithoutAttributes { backtrace: Backtrace },
#[snafu(display(
"NodeIdNotInGridNodeMap: MarkNodeId {} was not found in ed_model.grid_node_map.",
node_id
))]
NodeIdNotInGridNodeMap {
node_id: MarkNodeId,
backtrace: Backtrace,
},
#[snafu(display(
"OutOfBounds: index {} was out of bounds for {} with length {}.",
index,
@ -195,3 +210,10 @@ impl From<UIError> for EdError {
dummy_res.context(UIErrorBacktrace { msg }).unwrap_err()
}
}
pub fn from_ui_res<T>(ui_res: UIResult<T>) -> EdResult<T> {
match ui_res {
Ok(t) => Ok(t),
Err(ui_err) => Err(EdError::from(ui_err)),
}
}

View File

@ -1,9 +1,17 @@
use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::NestedNodeWithoutChildren;
use crate::editor::ed_error::NodeIdNotInGridNodeMap;
use crate::editor::mvc::ed_model::EdModel;
use crate::editor::slow_pool::MarkNodeId;
use crate::editor::util::first_last_index_of;
use crate::editor::util::index_of;
use crate::lang::ast::Expr2;
use crate::lang::pool::NodeId;
use crate::ui::text::selection::Selection;
use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::UIResult;
use crate::ui::util::{slice_get, slice_get_mut};
use snafu::OptionExt;
use std::fmt;
#[derive(Debug)]
@ -50,6 +58,18 @@ impl GridNodeMap {
Ok(())
}
pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> {
if selection.is_on_same_line() {
let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?;
line_ref.drain(selection.start_pos.column..selection.end_pos.column);
} else {
// TODO support multiline
}
Ok(())
}
/*pub fn new_line(&mut self) {
self.lines.push(vec![])
}*/
@ -72,6 +92,161 @@ impl GridNodeMap {
Ok(caret_pos.column - first_node_index)
}
pub fn node_exists_at_pos(&self, pos: TextPos) -> bool {
if pos.line < self.lines.len() {
// safe unwrap because we checked the length
let line = self.lines.get(pos.line).unwrap();
pos.column < line.len()
} else {
false
}
}
// get position of first occurence of node_id if get_first_pos, else get the last occurence
pub fn get_node_position(&self, node_id: MarkNodeId, get_first_pos: bool) -> EdResult<TextPos> {
let mut last_pos_opt = None;
for (line_index, line) in self.lines.iter().enumerate() {
for (col_index, iter_node_id) in line.iter().enumerate() {
if node_id == *iter_node_id && get_first_pos {
return Ok(TextPos {
line: line_index,
column: col_index,
});
} else if node_id == *iter_node_id {
last_pos_opt = Some(TextPos {
line: line_index,
column: col_index,
})
} else if let Some(last_pos) = last_pos_opt {
return Ok(last_pos);
}
}
}
if let Some(last_pos) = last_pos_opt {
Ok(last_pos)
} else {
NodeIdNotInGridNodeMap { node_id }.fail()
}
}
// retruns start and end pos of Expr2, relevant AST node and MarkNodeId of the corresponding MarkupNode
pub fn get_expr_start_end_pos(
&self,
caret_pos: TextPos,
ed_model: &EdModel,
) -> EdResult<(TextPos, TextPos, NodeId<Expr2>, MarkNodeId)> {
let line = slice_get(caret_pos.line, &self.lines)?;
let node_id = slice_get(caret_pos.column, line)?;
let node = ed_model.markup_node_pool.get(*node_id);
if node.is_nested() {
let (start_pos, end_pos) = self.get_nested_start_end_pos(*node_id, ed_model)?;
Ok((start_pos, end_pos, node.get_ast_node_id(), *node_id))
} else {
let (first_node_index, last_node_index) = first_last_index_of(*node_id, line)?;
let curr_node_id = slice_get(first_node_index, line)?;
let curr_ast_node_id = ed_model
.markup_node_pool
.get(*curr_node_id)
.get_ast_node_id();
let mut expr_start_index = first_node_index;
let mut expr_end_index = last_node_index;
// we may encounter ast id's of children of the current node
let mut pos_extra_subtract = 0;
for i in (0..first_node_index).rev() {
let prev_pos_node_id = slice_get(i, line)?;
let prev_ast_node_id = ed_model
.markup_node_pool
.get(*prev_pos_node_id)
.get_ast_node_id();
if prev_ast_node_id == curr_ast_node_id {
if pos_extra_subtract > 0 {
expr_start_index -= pos_extra_subtract + 1;
pos_extra_subtract = 0;
} else {
expr_start_index -= 1;
}
} else {
pos_extra_subtract += 1;
}
}
// we may encounter ast id's of children of the current node
let mut pos_extra_add = 0;
for i in last_node_index..line.len() {
let next_pos_node_id = slice_get(i, line)?;
let next_ast_node_id = ed_model
.markup_node_pool
.get(*next_pos_node_id)
.get_ast_node_id();
if next_ast_node_id == curr_ast_node_id {
if pos_extra_add > 0 {
expr_end_index += pos_extra_add + 1;
pos_extra_add = 0;
} else {
expr_end_index += 1;
}
} else {
pos_extra_add += 1;
}
}
Ok((
TextPos {
line: caret_pos.line,
column: expr_start_index,
},
TextPos {
line: caret_pos.line,
column: expr_end_index,
},
curr_ast_node_id,
*curr_node_id,
))
}
}
pub fn get_nested_start_end_pos(
&self,
nested_node_id: MarkNodeId,
ed_model: &EdModel,
) -> EdResult<(TextPos, TextPos)> {
let parent_mark_node = ed_model.markup_node_pool.get(nested_node_id);
let all_child_ids = parent_mark_node.get_children_ids();
let first_child_id = all_child_ids
.first()
.with_context(|| NestedNodeWithoutChildren {
node_id: nested_node_id,
})?;
let last_child_id = all_child_ids
.last()
.with_context(|| NestedNodeWithoutChildren {
node_id: nested_node_id,
})?;
let expr_start_pos = ed_model
.grid_node_map
.get_node_position(*first_child_id, true)?;
let expr_end_pos = ed_model
.grid_node_map
.get_node_position(*last_child_id, false)?
.increment_col();
Ok((expr_start_pos, expr_end_pos))
}
}
impl fmt::Display for GridNodeMap {

View File

@ -118,7 +118,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
format: render_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Immediate,
present_mode: wgpu::PresentMode::Mailbox,
};
let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr);
@ -228,7 +228,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
format: render_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Immediate,
present_mode: wgpu::PresentMode::Mailbox,
},
);

View File

@ -1,7 +1,6 @@
use super::app_model::AppModel;
use super::ed_update;
use crate::editor::ed_error::EdResult;
use crate::ui::text::lines::SelectableLines;
use crate::window::keyboard_input::from_winit;
use winit::event::{ModifiersState, VirtualKeyCode};
@ -44,7 +43,7 @@ pub fn pass_keydown_to_focused(
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
ed_model.handle_key_down(&modifiers, virtual_keycode)?;
ed_model.ed_handle_key_down(&modifiers, virtual_keycode)?;
}
}

View File

@ -5,7 +5,7 @@ use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::{
ed_error::EdError::ParseError,
ed_error::EdResult,
markup::attribute::{Attributes, Caret},
markup::attribute::Attributes,
markup::nodes::{expr2_to_markup, set_parent_for_all, MarkupNode},
};
use crate::graphics::primitives::rect::Rect;
@ -37,6 +37,7 @@ pub struct EdModel<'a> {
pub has_focus: bool,
// Option<MarkNodeId>: MarkupNode that corresponds to caret position, Option because this MarkNodeId is only calculated when it needs to be used.
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<MarkNodeId>)>,
pub selected_expr2_tup: Option<(NodeId<Expr2>, MarkNodeId)>,
pub show_debug_view: bool,
// EdModel is dirty if it has changed since the previous render.
pub dirty: bool,
@ -56,9 +57,7 @@ pub fn init_model<'a>(
let markup_root_id = if code_str.is_empty() {
let blank_root = MarkupNode::Blank {
ast_node_id: ast_root_id,
attributes: Attributes {
all: vec![Caret::new_attr(0)],
},
attributes: Attributes::new(),
syn_high_style: HighlightStyle::Blank,
parent_id_opt: None,
};
@ -92,6 +91,7 @@ pub fn init_model<'a>(
glyph_dim_rect_opt: None,
has_focus: true,
caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)),
selected_expr2_tup: None,
show_debug_view: false,
dirty: true,
})

View File

@ -1,6 +1,10 @@
use crate::editor::code_lines::CodeLines;
use crate::editor::ed_error::from_ui_res;
use crate::editor::ed_error::print_ui_err;
use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::MissingSelection;
use crate::editor::grid_node_map::GridNodeMap;
use crate::editor::markup::attribute::Attributes;
use crate::editor::markup::nodes;
use crate::editor::markup::nodes::MarkupNode;
use crate::editor::mvc::app_update::InputOutcome;
@ -15,6 +19,7 @@ use crate::editor::mvc::string_update::update_small_string;
use crate::editor::mvc::string_update::update_string;
use crate::editor::slow_pool::MarkNodeId;
use crate::editor::slow_pool::SlowPool;
use crate::editor::syntax_highlight::HighlightStyle;
use crate::lang::ast::Expr2;
use crate::lang::pool::NodeId;
use crate::ui::text::caret_w_select::CaretWSelect;
@ -26,6 +31,7 @@ use crate::ui::text::text_pos::TextPos;
use crate::ui::text::{lines, lines::Lines, lines::SelectableLines};
use crate::ui::ui_error::UIResult;
use crate::window::keyboard_input::Modifiers;
use snafu::OptionExt;
use winit::event::VirtualKeyCode;
use VirtualKeyCode::*;
@ -41,6 +47,7 @@ impl<'a> EdModel<'a> {
caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
caret_tup.1 = None;
}
self.selected_expr2_tup = None;
Ok(())
}
@ -136,6 +143,121 @@ impl<'a> EdModel<'a> {
self.grid_node_map.del_at_line(line_nr, index)?;
self.code_lines.del_at_line(line_nr, index)
}
// select all MarkupNodes that refer to specific ast node and its children.
pub fn select_expr(&mut self) -> EdResult<()> {
// include parent in selection if an `Expr2` was already selected
if let Some((_sel_expr2_id, mark_node_id)) = self.selected_expr2_tup {
let expr2_level_mark_node = self.markup_node_pool.get(mark_node_id);
if let Some(parent_id) = expr2_level_mark_node.get_parent_id_opt() {
let parent_mark_node = self.markup_node_pool.get(parent_id);
let ast_node_id = parent_mark_node.get_ast_node_id();
let (expr_start_pos, expr_end_pos) = self
.grid_node_map
.get_nested_start_end_pos(parent_id, self)?;
self.set_raw_sel(RawSelection {
start_pos: expr_start_pos,
end_pos: expr_end_pos,
})?;
self.set_caret(expr_start_pos);
self.selected_expr2_tup = Some((ast_node_id, parent_id));
self.dirty = true;
}
} else {
// select `Expr2` in which caret is currently positioned
let caret_pos = self.get_caret();
if self.grid_node_map.node_exists_at_pos(caret_pos) {
let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self
.grid_node_map
.get_expr_start_end_pos(self.get_caret(), &self)?;
self.set_raw_sel(RawSelection {
start_pos: expr_start_pos,
end_pos: expr_end_pos,
})?;
self.set_caret(expr_start_pos);
self.selected_expr2_tup = Some((ast_node_id, mark_node_id));
self.dirty = true;
}
}
Ok(())
}
pub fn ed_handle_key_down(
&mut self,
modifiers: &Modifiers,
virtual_keycode: VirtualKeyCode,
) -> EdResult<()> {
match virtual_keycode {
Left => from_ui_res(self.move_caret_left(modifiers)),
Up => {
if modifiers.ctrl && modifiers.shift {
self.select_expr()
} else {
from_ui_res(self.move_caret_up(modifiers))
}
}
Right => from_ui_res(self.move_caret_right(modifiers)),
Down => from_ui_res(self.move_caret_down(modifiers)),
A => {
if modifiers.ctrl {
from_ui_res(self.select_all())
} else {
Ok(())
}
}
Home => from_ui_res(self.move_caret_home(modifiers)),
End => from_ui_res(self.move_caret_end(modifiers)),
F11 => {
self.show_debug_view = !self.show_debug_view;
self.dirty = true;
Ok(())
}
_ => Ok(()),
}
}
fn replace_slected_expr_with_blank(&mut self) -> EdResult<()> {
if let Some((sel_expr2_id, mark_node_id)) = self.selected_expr2_tup {
let expr2_level_mark_node = self.markup_node_pool.get(mark_node_id);
let blank_replacement = MarkupNode::Blank {
ast_node_id: sel_expr2_id,
attributes: Attributes::new(),
syn_high_style: HighlightStyle::Blank,
parent_id_opt: expr2_level_mark_node.get_parent_id_opt(),
};
self.markup_node_pool
.replace_node(mark_node_id, blank_replacement);
let active_selection = self.get_selection().context(MissingSelection {})?;
self.code_lines.del_selection(active_selection)?;
self.grid_node_map.del_selection(active_selection)?;
let caret_pos = self.get_caret();
self.insert_between_line(
caret_pos.line,
caret_pos.column,
nodes::BLANK_PLACEHOLDER,
mark_node_id,
)?;
self.module.env.pool.set(sel_expr2_id, Expr2::Blank)
}
self.set_sel_none();
Ok(())
}
}
impl<'a> SelectableLines for EdModel<'a> {
@ -230,6 +352,7 @@ impl<'a> SelectableLines for EdModel<'a> {
fn set_sel_none(&mut self) {
self.caret_w_select_vec.first_mut().0.selection_opt = None;
self.selected_expr2_tup = None;
}
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {
@ -264,31 +387,10 @@ impl<'a> SelectableLines for EdModel<'a> {
fn handle_key_down(
&mut self,
modifiers: &Modifiers,
virtual_keycode: VirtualKeyCode,
_modifiers: &Modifiers,
_virtual_keycode: VirtualKeyCode,
) -> UIResult<()> {
match virtual_keycode {
Left => self.move_caret_left(modifiers),
Up => self.move_caret_up(modifiers),
Right => self.move_caret_right(modifiers),
Down => self.move_caret_down(modifiers),
A => {
if modifiers.ctrl {
self.select_all()
} else {
Ok(())
}
}
Home => self.move_caret_home(modifiers),
End => self.move_caret_end(modifiers),
F11 => {
self.show_debug_view = !self.show_debug_view;
self.dirty = true;
Ok(())
}
_ => Ok(()),
}
unreachable!("Use EdModel::ed_handle_key_down instead.")
}
}
@ -319,9 +421,6 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult<NodeContext<'a>>
}
pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult<InputOutcome> {
// TODO set all selections to none
// TODO nested records
let input_outcome = match received_char {
'\u{1}' // Ctrl + A
| '\u{3}' // Ctrl + C
@ -334,121 +433,70 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult
// chars that can be ignored
InputOutcome::Ignored
}
'\u{8}' | '\u{7f}' => {
// On Linux, '\u{8}' is backspace,
// on macOS '\u{7f}'.
ed_model.replace_slected_expr_with_blank()?;
InputOutcome::Accepted
}
ch => {
let curr_mark_node_id_res = ed_model.get_curr_mark_node_id();
if let Ok(curr_mark_node_id) = curr_mark_node_id_res {
let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id);
let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?;
let outcome =
match curr_mark_node_id_res {
Ok(curr_mark_node_id) => {
let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id);
let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?;
let ast_node_id = curr_mark_node.get_ast_node_id();
let ast_node_ref = ed_model.module.env.pool.get(ast_node_id);
let ast_node_id = curr_mark_node.get_ast_node_id();
let ast_node_ref = ed_model.module.env.pool.get(ast_node_id);
if let Expr2::Blank {..} = ast_node_ref {
match ch {
'"' => {
start_new_string(ed_model)?
},
'{' => {
start_new_record(ed_model)?
}
_ => InputOutcome::Ignored
}
} else if let Some(prev_mark_node_id) = prev_mark_node_id_opt{
if prev_mark_node_id == curr_mark_node_id {
match ast_node_ref {
Expr2::SmallStr(old_arr_str) => {
update_small_string(
&ch, old_arr_str, ed_model
)?
}
Expr2::Str(old_pool_str) => {
update_string(
&ch.to_string(), old_pool_str, ed_model
)?
}
Expr2::InvalidLookup(old_pool_str) => {
update_invalid_lookup(
&ch.to_string(),
old_pool_str,
curr_mark_node_id,
ast_node_id,
ed_model
)?
}
Expr2::EmptyRecord => {
// prev_mark_node_id and curr_mark_node_id should be different to allow creating field at current caret position
InputOutcome::Ignored
}
Expr2::Record{ record_var:_, fields } => {
if curr_mark_node.get_content()?.chars().all(|chr| chr.is_ascii_alphanumeric()){
update_record_field(
&ch.to_string(),
ed_model.get_caret(),
curr_mark_node_id,
fields,
ed_model,
)?
} else {
InputOutcome::Ignored
if let Expr2::Blank {..} = ast_node_ref {
match ch {
'"' => {
start_new_string(ed_model)?
},
'{' => {
start_new_record(ed_model)?
}
_ => InputOutcome::Ignored
}
_ => InputOutcome::Ignored
}
} else if ch.is_ascii_alphanumeric() { // prev_mark_node_id != curr_mark_node_id
let prev_ast_node_id =
ed_model
.markup_node_pool
.get(prev_mark_node_id)
.get_ast_node_id();
let prev_node_ref = ed_model.module.env.pool.get(prev_ast_node_id);
match prev_node_ref {
Expr2::InvalidLookup(old_pool_str) => {
update_invalid_lookup(
&ch.to_string(),
old_pool_str,
prev_mark_node_id,
prev_ast_node_id,
ed_model
)?
}
Expr2::Record{ record_var:_, fields } => {
let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id);
if (curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE || curr_mark_node.get_content()? == nodes::COLON) &&
prev_mark_node.is_all_alphanumeric()? {
update_record_field(
&ch.to_string(),
ed_model.get_caret(),
prev_mark_node_id,
fields,
ed_model,
)?
} else if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE && curr_mark_node.is_all_alphanumeric()? {
update_record_field(
&ch.to_string(),
ed_model.get_caret(),
curr_mark_node_id,
fields,
ed_model,
)?
} else {
InputOutcome::Ignored
}
}
_ => {
} else if let Some(prev_mark_node_id) = prev_mark_node_id_opt{
if prev_mark_node_id == curr_mark_node_id {
match ast_node_ref {
Expr2::SmallStr(old_arr_str) => {
update_small_string(
&ch, old_arr_str, ed_model
)?
}
Expr2::Str(old_pool_str) => {
update_string(
&ch.to_string(), old_pool_str, ed_model
)?
}
Expr2::InvalidLookup(old_pool_str) => {
update_invalid_lookup(
&ch.to_string(),
old_pool_str,
curr_mark_node_id,
ast_node_id,
ed_model
)?
}
Expr2::EmptyRecord => {
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool);
if ch.is_ascii_alphabetic() && ch.is_ascii_lowercase() {
update_empty_record(
// prev_mark_node_id and curr_mark_node_id should be different to allow creating field at current caret position
InputOutcome::Ignored
}
Expr2::Record{ record_var:_, fields } => {
if curr_mark_node.get_content()?.chars().all(|chr| chr.is_ascii_alphanumeric()){
update_record_field(
&ch.to_string(),
prev_mark_node_id,
sibling_ids,
ed_model
ed_model.get_caret(),
curr_mark_node_id,
fields,
ed_model,
)?
} else {
InputOutcome::Ignored
@ -456,30 +504,99 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult
}
_ => InputOutcome::Ignored
}
} else if ch.is_ascii_alphanumeric() { // prev_mark_node_id != curr_mark_node_id
let prev_ast_node_id =
ed_model
.markup_node_pool
.get(prev_mark_node_id)
.get_ast_node_id();
let prev_node_ref = ed_model.module.env.pool.get(prev_ast_node_id);
match prev_node_ref {
Expr2::InvalidLookup(old_pool_str) => {
update_invalid_lookup(
&ch.to_string(),
old_pool_str,
prev_mark_node_id,
prev_ast_node_id,
ed_model
)?
}
Expr2::Record{ record_var:_, fields } => {
let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id);
if (curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE || curr_mark_node.get_content()? == nodes::COLON) &&
prev_mark_node.is_all_alphanumeric()? {
update_record_field(
&ch.to_string(),
ed_model.get_caret(),
prev_mark_node_id,
fields,
ed_model,
)?
} else if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE && curr_mark_node.is_all_alphanumeric()? {
update_record_field(
&ch.to_string(),
ed_model.get_caret(),
curr_mark_node_id,
fields,
ed_model,
)?
} else {
InputOutcome::Ignored
}
}
_ => {
match ast_node_ref {
Expr2::EmptyRecord => {
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool);
if ch.is_ascii_alphabetic() && ch.is_ascii_lowercase() {
update_empty_record(
&ch.to_string(),
prev_mark_node_id,
sibling_ids,
ed_model
)?
} else {
InputOutcome::Ignored
}
}
_ => InputOutcome::Ignored
}
}
}
} else if *ch == ':' {
let mark_parent_id_opt = curr_mark_node.get_parent_id_opt();
if let Some(mark_parent_id) = mark_parent_id_opt {
let parent_ast_id = ed_model.markup_node_pool.get(mark_parent_id).get_ast_node_id();
update_record_colon(ed_model, parent_ast_id)?
} else {
InputOutcome::Ignored
}
} else {
InputOutcome::Ignored
}
}
} else if *ch == ':' {
let mark_parent_id_opt = curr_mark_node.get_parent_id_opt();
if let Some(mark_parent_id) = mark_parent_id_opt {
let parent_ast_id = ed_model.markup_node_pool.get(mark_parent_id).get_ast_node_id();
update_record_colon(ed_model, parent_ast_id)?
} else {
// Not supporting any Expr2 right now that would allow prepending at the start of a line
InputOutcome::Ignored
}
} else {
},
Err(e) => {
print_ui_err(&e);
InputOutcome::Ignored
}
};
} else {
// Not supporting any Expr2 right now that would allow prepending at the start of a line
InputOutcome::Ignored
if let InputOutcome::Accepted = outcome {
ed_model.set_sel_none();
}
} else {
InputOutcome::Ignored
}
outcome
}
};

View File

@ -5,7 +5,9 @@ use crate::editor::render_ast::build_code_graphics;
use crate::editor::render_debug::build_debug_graphics;
use crate::graphics::primitives::rect::Rect;
use crate::ui::text::caret_w_select::make_caret_rect;
use crate::ui::text::caret_w_select::make_selection_rect;
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::text::selection::Selection;
use crate::ui::ui_error::MissingGlyphDims;
use cgmath::Vector2;
use snafu::OptionExt;
@ -70,20 +72,39 @@ pub fn build_selection_graphics(
let char_width = glyph_dim_rect.width;
let char_height = glyph_dim_rect.height;
let y_offset = 0.1 * char_height;
for caret_w_sel in caret_w_select_vec {
let caret_row = caret_w_sel.caret_pos.line as f32;
let caret_col = caret_w_sel.caret_pos.column as f32;
let top_left_x = txt_coords.x + caret_col * char_width;
let top_left_y = txt_coords.y + caret_row * char_height + y_offset;
let top_left_y = txt_coords.y + caret_row * char_height + 0.1 * char_height;
if let Some(selection) = caret_w_sel.selection_opt {
let Selection { start_pos, end_pos } = selection;
let sel_rect_x = txt_coords.x + ((start_pos.column as f32) * char_width);
let sel_rect_y = txt_coords.y + char_height * (start_pos.line as f32) + y_offset;
let width =
((end_pos.column as f32) * char_width) - ((start_pos.column as f32) * char_width);
rects.push(make_selection_rect(
sel_rect_x,
sel_rect_y,
width,
&glyph_dim_rect,
&config.ed_theme.ui_theme,
));
}
rects.push(make_caret_rect(
top_left_x,
top_left_y,
&glyph_dim_rect,
&config.ed_theme.ui_theme,
))
));
}
Ok(rects)

View File

@ -40,7 +40,7 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
ast_node_id,
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(),
parent_id_opt: Some(curr_mark_node_id), // current node will be replace with nested one
parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one
};
let left_bracket_node_id = mark_node_pool.add(left_bracket_node);
@ -50,7 +50,7 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
ast_node_id,
syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(),
parent_id_opt: Some(curr_mark_node_id), // current node will be replace with nested one
parent_id_opt: Some(curr_mark_node_id), // current node will be replaced with nested one
};
let right_bracket_node_id = mark_node_pool.add(right_bracket_node);

View File

@ -33,6 +33,9 @@ impl SlowPool {
pub fn replace_node(&mut self, node_id: MarkNodeId, new_node: MarkupNode) {
self.nodes[node_id] = new_node;
// TODO delete children of old node, this requires SlowPool to be changed to
// make sure the indexes still make sense after removal/compaction
}
}

View File

@ -31,3 +31,38 @@ pub fn index_of<T: ::std::fmt::Debug + std::cmp::Eq>(elt: T, slice: &[T]) -> EdR
Ok(index)
}
// returns the index of the first occurence of element and index of the last occurence
pub fn first_last_index_of<T: ::std::fmt::Debug + std::cmp::Eq>(
elt: T,
slice: &[T],
) -> EdResult<(usize, usize)> {
let mut first_index_opt = None;
let mut last_index_opt = None;
for (index, list_elt) in slice.iter().enumerate() {
if *list_elt == elt {
if first_index_opt.is_none() {
first_index_opt = Some(index);
last_index_opt = Some(index);
} else {
last_index_opt = Some(index)
}
} else if last_index_opt.is_some() {
break;
}
}
if let (Some(first_index), Some(last_index)) = (first_index_opt, last_index_opt) {
Ok((first_index, last_index))
} else {
let elt_str = format!("{:?}", elt);
let collection_str = format!("{:?}", slice);
IndexOfFailed {
elt_str,
collection_str,
}
.fail()
}
}

View File

@ -53,17 +53,17 @@ pub fn create_render_pipeline(
entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
format: color_format,
color_blend: wgpu::BlendState::REPLACE,
color_blend: wgpu::BlendState {
operation: wgpu::BlendOperation::Add,
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
},
alpha_blend: wgpu::BlendState::REPLACE,
write_mask: wgpu::ColorWrite::ALL,
}],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multisample: wgpu::MultisampleState::default(),
})
}

View File

@ -18,11 +18,17 @@ pub enum Constraint {
// And(Vec<Constraint>),
}
pub fn constrain_expr(env: &mut Env, expr: &Expr2, expected: Expected<Type2>) -> Constraint {
pub fn constrain_expr(
env: &mut Env,
expr: &Expr2,
expected: Expected<Type2>,
region: Region,
) -> Constraint {
use Constraint::*;
match expr {
Expr2::Str(_) => Eq(str_type(env.pool), expected, Category::Str, Region::zero()),
Expr2::EmptyRecord => Eq(Type2::EmptyRec, expected, Category::Record, region),
Expr2::Str(_) => Eq(str_type(env.pool), expected, Category::Str, region),
_ => todo!("implement constaints for {:?}", expr),
}
}

View File

@ -112,6 +112,21 @@ pub fn make_caret_rect(
}
}
pub fn make_selection_rect(
sel_rect_x: f32,
sel_rect_y: f32,
width: f32,
glyph_dim_rect: &Rect,
ui_theme: &UITheme,
) -> Rect {
Rect {
top_left_coords: (sel_rect_x, sel_rect_y).into(),
height: glyph_dim_rect.height,
width,
color: ui_theme.select_highlight,
}
}
#[cfg(test)]
pub mod test_caret_w_select {
use crate::ui::text::caret_w_select::CaretWSelect;

View File

@ -30,6 +30,12 @@ pub struct Selection {
pub end_pos: TextPos,
}
impl Selection {
pub fn is_on_same_line(&self) -> bool {
self.start_pos.line == self.end_pos.line
}
}
pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult<Selection> {
validate_selection(raw_sel.start_pos, raw_sel.end_pos)
}

View File

@ -6,6 +6,15 @@ pub struct TextPos {
pub column: usize,
}
impl TextPos {
pub fn increment_col(&self) -> TextPos {
TextPos {
line: self.line,
column: self.column + 1,
}
}
}
impl Ord for TextPos {
fn cmp(&self, other: &Self) -> Ordering {
(self.line, self.column).cmp(&(other.line, other.column))

View File

@ -1,4 +1,4 @@
use gr_colors::{from_hsb, RgbaTup};
use gr_colors::{from_hsba, RgbaTup};
use serde::{Deserialize, Serialize};
use crate::graphics::colors as gr_colors;
@ -22,7 +22,7 @@ impl Default for UITheme {
dark_brand: DARK_BRAND_COL,
text: gr_colors::WHITE,
caret: gr_colors::WHITE,
select_highlight: from_hsb(240, 55, 100),
select_highlight: from_hsba(240, 55, 100, 0.3),
}
}
}

View File

@ -1,3 +1,4 @@
#[derive(Debug)]
pub struct Modifiers {
pub shift: bool,
pub ctrl: bool,

View File

@ -86,6 +86,7 @@ fn infer_eq(actual: &str, expected_str: &str) {
&mut env,
&expr,
Expected::NoExpectation(Type2::Variable(var)),
Region::zero(),
);
let Env {
@ -129,3 +130,15 @@ fn constrain_str() {
"Str",
)
}
#[test]
fn constrain_empty_record() {
infer_eq(
indoc!(
r#"
{}
"#
),
"{}",
)
}

2
examples/cli/Echo.roc Normal file → Executable file
View File

@ -1,3 +1,5 @@
#!/usr/bin/env roc run
app "echo"
packages { base: "platform" }
imports [ base.Task.{ await }, base.Stdout, base.Stdin ]

View File

@ -2,7 +2,6 @@
use roc_std::{alloca, RocCallResult, RocResult, RocStr};
use std::alloc::Layout;
use std::time::SystemTime;
extern "C" {
#[link_name = "roc__mainForHost_1_exposed"]
@ -92,12 +91,10 @@ unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const
#[no_mangle]
pub fn rust_main() -> isize {
println!("Running Roc closure");
let start_time = SystemTime::now();
let size = unsafe { roc_main_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
let answer = unsafe {
unsafe {
let buffer = std::alloc::alloc(layout);
roc_main(buffer);
@ -130,15 +127,6 @@ pub fn rust_main() -> isize {
}
}
};
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc closure took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0,
// truncate the answer, so stdout is not swamped
answer
);
// Exit code
0