Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/edit/benches/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use edit::helpers::*;
use edit::{buffer, hash, json, oklab, simd, unicode};
use stdext::arena::{self, scratch_arena};
use stdext::collections::BVec;
use stdext::float::parse_f64_approx;
use stdext::glob;
use stdext::unicode::Utf8Chars;

Expand Down Expand Up @@ -136,6 +137,13 @@ fn bench_buffer(c: &mut Criterion) {
});
}

fn bench_float(c: &mut Criterion) {
c.benchmark_group("float::parse_f64_approx")
.bench_function("123", |b| b.iter(|| parse_f64_approx(black_box(b"123"))))
.bench_function("123.456", |b| b.iter(|| parse_f64_approx(black_box(b"123.456"))))
.bench_function("123.456e3", |b| b.iter(|| parse_f64_approx(black_box(b"123.456e3"))));
}

fn bench_glob(c: &mut Criterion) {
// Same benchmark as in glob-match
const PATH: &str = "foo/bar/foo/bar/foo/bar/foo/bar/foo/bar.txt";
Expand Down Expand Up @@ -282,6 +290,7 @@ fn bench(c: &mut Criterion) {
arena::init(128 * MEBI).unwrap();

bench_buffer(c);
bench_float(c);
bench_glob(c);
bench_hash(c);
bench_json(c);
Expand Down
1 change: 1 addition & 0 deletions crates/edit/src/bin/edit/apperr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use edit::{buffer, icu};

#[derive(Debug)]
pub enum Error {
SettingsInvalid(&'static str),
Io(io::Error),
Icu(icu::Error),
}
Expand Down
13 changes: 9 additions & 4 deletions crates/edit/src/bin/edit/documents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use edit::lsh::{FILE_ASSOCIATIONS, Language, process_file_associations};
use edit::{path, sys};

use crate::apperr;
use crate::settings::Settings;
use crate::state::DisplayablePathBuf;

pub struct Document {
Expand Down Expand Up @@ -92,10 +93,14 @@ impl Document {
return lang;
}

if let Some(path) = &self.path
&& let Some(lang) = process_file_associations(FILE_ASSOCIATIONS, path)
{
return Some(lang);
if let Some(path) = &self.path {
let settings = Settings::borrow();
if let Some(lang) = process_file_associations(&settings.file_associations, path) {
return Some(lang);
}
if let Some(lang) = process_file_associations(FILE_ASSOCIATIONS, path) {
return Some(lang);
}
}

None
Expand Down
22 changes: 20 additions & 2 deletions crates/edit/src/bin/edit/draw_menubar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use edit::tui::*;
use stdext::arena_format;

use crate::localization::*;
use crate::settings::Settings;
use crate::state::*;

pub fn draw_menubar(ctx: &mut Context, state: &mut State) {
Expand Down Expand Up @@ -51,10 +52,27 @@ fn draw_menu_file(ctx: &mut Context, state: &mut State) {
if ctx.menubar_menu_button(loc(LocId::FileSaveAs), 'A', vk::NULL) {
state.wants_file_picker = StateFilePicker::SaveAs;
}
if ctx.menubar_menu_button(loc(LocId::FileClose), 'C', kbmod::CTRL | vk::W) {
state.wants_close = true;
}
if let path = Settings::borrow().path.as_path()
&& !path.as_os_str().is_empty()
&& ctx.menubar_menu_button(loc(LocId::FilePreferences), 'P', vk::NULL)
{
match state.documents.add_file_path(path) {
Ok(doc) => {
if let mut tb = doc.buffer.borrow_mut()
&& tb.text_length() == 0
{
Settings::bootstrap(&mut tb);
}
}
Err(err) => error_log_add(ctx, state, err),
}
}
if state.documents.active().is_some()
&& ctx.menubar_menu_button(loc(LocId::FileClose), 'C', kbmod::CTRL | vk::W)
{
state.wants_close = true;
}
if ctx.menubar_menu_button(loc(LocId::FileExit), 'X', kbmod::CTRL | vk::Q) {
state.wants_exit = true;
}
Expand Down
7 changes: 7 additions & 0 deletions crates/edit/src/bin/edit/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod draw_filepicker;
mod draw_menubar;
mod draw_statusbar;
mod localization;
mod settings;
mod state;

use std::borrow::Cow;
Expand All @@ -32,6 +33,8 @@ use stdext::arena::{self, Arena, scratch_arena};
use stdext::arena_format;
use stdext::collections::{BString, BVec};

use crate::settings::Settings;

#[cfg(target_pointer_width = "32")]
const SCRATCH_ARENA_CAPACITY: usize = 128 * MEBI;
#[cfg(target_pointer_width = "64")]
Expand Down Expand Up @@ -72,6 +75,10 @@ fn run() -> apperr::Result<()> {
return Ok(());
}

if let Err(err) = Settings::reload() {
state.add_error(err);
}

// This will reopen stdin if it's redirected (which may fail) and switch
// the terminal to raw mode which prevents the user from pressing Ctrl+C.
// `handle_args` may want to print a help message (must not fail),
Expand Down
119 changes: 119 additions & 0 deletions crates/edit/src/bin/edit/settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use std::path::PathBuf;

use edit::buffer::TextBuffer;
use edit::cell::{Ref, SemiRefCell};
use edit::json;
use edit::lsh::{LANGUAGES, Language};
use stdext::arena::{read_to_string, scratch_arena};
use stdext::arena_format;

use crate::apperr;

pub struct Settings {
pub path: PathBuf,
pub file_associations: Vec<(String, &'static Language)>,
}

struct SettingsCell(SemiRefCell<Settings>);
unsafe impl Sync for SettingsCell {}
static SETTINGS: SettingsCell = SettingsCell(SemiRefCell::new(Settings::new()));

impl Settings {
/// Fills the given settings.json text buffer with some initial contents for convenience.
pub fn bootstrap(tb: &mut TextBuffer) {
tb.set_crlf(false);
tb.write_raw(b"{\n}\n");
tb.cursor_move_to_logical(Default::default());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm some way for the cursor to go inside the {} ? is that silly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also considered that, but VS Code puts it at 0,0 and I'll keep that for consistency.

tb.mark_as_clean();
}

const fn new() -> Self {
Settings { path: PathBuf::new(), file_associations: Vec::new() }
}

pub fn borrow() -> Ref<'static, Settings> {
SETTINGS.0.borrow()
}

pub fn reload() -> apperr::Result<()> {
let s = &mut *SETTINGS.0.borrow_mut();

// Reset all members if we had been loaded previously.
if !s.path.as_os_str().is_empty() {
*s = Settings::new();
}

s.load()
}

fn load(&mut self) -> apperr::Result<()> {
self.path = match settings_json_path() {
Some(p) => p,
None => return Ok(()),
};

let scratch = scratch_arena(None);
let str = match read_to_string(&scratch, &self.path) {
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(err) => return Err(err.into()),
Ok(str) => str,
};
let Ok(json) = json::parse(&scratch, &str) else {
return Err(apperr::Error::SettingsInvalid("Invalid JSON"));
};
let Some(root) = json.as_object() else {
return Err(apperr::Error::SettingsInvalid("Non-object root"));
};

if let Some(f) = root.get_object("files.associations") {
for &(mut key, ref value) in f.iter() {
if !key.contains('/') {
key = arena_format!(&*scratch, "**/{key}").leak();
}

let Some(id) = value.as_str() else {
return Err(apperr::Error::SettingsInvalid("files.associations"));
};
let Some(language) = LANGUAGES.iter().find(|lang| lang.id == id) else {
return Err(apperr::Error::SettingsInvalid("language ID"));
};

self.file_associations.push((key.to_string(), language));
}
}

Ok(())
}
}

fn settings_json_path() -> Option<PathBuf> {
let mut config_dir = config_dir()?;
config_dir.push("settings.json");
Some(config_dir)
}

fn config_dir() -> Option<PathBuf> {
fn var_path(key: &str) -> Option<PathBuf> {
std::env::var_os(key).map(PathBuf::from)
}

fn push(mut path: PathBuf, suffix: &str) -> PathBuf {
path.push(suffix);
path
}

#[cfg(target_os = "windows")]
{
var_path("APPDATA").map(|p| push(p, "Microsoft\\Edit"))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw when i said "edit sys crate" i actually thought edit itself had its own system specific crate, different from the generic sys one in this repo. I ask mostly because if I were to add UEFI, I would want this to return None without having to put uefi-specific things all over the code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good point!

}
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
var_path("HOME").map(|p| push(p, "Library/Application Support/com.microsoft.edit"))
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))]
{
var_path("XDG_CONFIG_HOME")
.or_else(|| var_path("HOME").map(|p| push(p, ".config")))
.map(|p| push(p, "msedit"))
}
}
21 changes: 16 additions & 5 deletions crates/edit/src/bin/edit/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ impl From<apperr::Error> for FormatApperr {
impl std::fmt::Display for FormatApperr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
apperr::Error::SettingsInvalid(what) => {
write!(f, "{}{}", loc(LocId::SettingsInvalid), what)
}
apperr::Error::Icu(icu::ICU_MISSING_ERROR) => f.write_str(loc(LocId::ErrorIcuMissing)),
apperr::Error::Icu(ref err) => err.fmt(f),
apperr::Error::Io(ref err) => err.fmt(f),
Expand Down Expand Up @@ -226,6 +229,18 @@ impl State {
exit: false,
})
}

pub fn add_error(&mut self, err: apperr::Error) -> bool {
let msg = format!("{}", FormatApperr::from(err));
if msg.is_empty() {
return false;
}

self.error_log[self.error_log_index] = msg;
self.error_log_index = (self.error_log_index + 1) % self.error_log.len();
self.error_log_count = self.error_log.len().min(self.error_log_count + 1);
true
}
}

pub fn draw_add_untitled_document(ctx: &mut Context, state: &mut State) {
Expand All @@ -235,11 +250,7 @@ pub fn draw_add_untitled_document(ctx: &mut Context, state: &mut State) {
}

pub fn error_log_add(ctx: &mut Context, state: &mut State, err: apperr::Error) {
let msg = format!("{}", FormatApperr::from(err));
if !msg.is_empty() {
state.error_log[state.error_log_index] = msg;
state.error_log_index = (state.error_log_index + 1) % state.error_log.len();
state.error_log_count = state.error_log.len().min(state.error_log_count + 1);
if state.add_error(err) {
ctx.needs_rerender();
}
}
Expand Down
6 changes: 4 additions & 2 deletions crates/edit/src/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,14 @@ impl TextBuffer {
self.buffer.generation()
}

/// Force the buffer to be dirty.
/// Force the buffer to be dirty (needs to be saved to disk).
pub fn mark_as_dirty(&mut self) {
self.last_save_generation = self.buffer.generation().wrapping_sub(1);
}

fn mark_as_clean(&mut self) {
/// Force the buffer to be clean (has been saved to disk).
/// Use this with caution. It's called automatically on write().
pub fn mark_as_clean(&mut self) {
self.last_save_generation = self.buffer.generation();
}

Expand Down
2 changes: 1 addition & 1 deletion crates/edit/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ impl<'a, 'i> Parser<'a, 'i> {
self.pos += 1;
}

if let Ok(num) = self.input[start..self.pos].parse::<f64>()
if let Some(num) = stdext::float::parse_f64_approx(&self.bytes[start..self.pos])
&& num.is_finite()
{
Ok(Value::Number(num))
Expand Down
Loading
Loading