Merge pull request #12 from zed-industries/platform-trait
Hide platform code entirely behind App for framework users
This commit is contained in:
commit
6ea2581e3e
21 changed files with 1046 additions and 1012 deletions
|
@ -1,7 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::ColorU,
|
color::ColorU,
|
||||||
fonts::{Properties, Weight},
|
fonts::{Properties, Weight},
|
||||||
platform::{current as platform, Runner},
|
|
||||||
DebugContext, Element as _, Quad,
|
DebugContext, Element as _, Quad,
|
||||||
};
|
};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
|
@ -11,13 +10,10 @@ use simplelog::SimpleLogger;
|
||||||
fn main() {
|
fn main() {
|
||||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||||
|
|
||||||
let mut app = gpui::App::new(()).unwrap();
|
gpui::App::new(()).unwrap().run(|ctx| {
|
||||||
platform::runner()
|
ctx.platform().activate(true);
|
||||||
.on_finish_launching(move || {
|
ctx.add_window(|_| TextView);
|
||||||
app.platform().activate(true);
|
});
|
||||||
app.add_window(|_| TextView);
|
|
||||||
})
|
|
||||||
.run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TextView;
|
struct TextView;
|
||||||
|
|
508
gpui/src/app.rs
508
gpui/src/app.rs
File diff suppressed because it is too large
Load diff
|
@ -21,8 +21,8 @@ pub use executor::Task;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod json;
|
pub mod json;
|
||||||
pub mod keymap;
|
pub mod keymap;
|
||||||
pub mod platform;
|
mod platform;
|
||||||
pub use platform::Event;
|
pub use platform::{Event, PathPromptOptions};
|
||||||
pub use presenter::{
|
pub use presenter::{
|
||||||
AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,
|
AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,
|
||||||
SizeConstraint, Vector2FExt,
|
SizeConstraint, Vector2FExt,
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
use super::{BoolExt as _, Dispatcher, FontSystem, Window};
|
|
||||||
use crate::{executor, platform};
|
|
||||||
use anyhow::Result;
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{NSApplication, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString},
|
|
||||||
base::nil,
|
|
||||||
foundation::{NSArray, NSData, NSString, NSURL},
|
|
||||||
};
|
|
||||||
use objc::{msg_send, sel, sel_impl};
|
|
||||||
use std::{ffi::c_void, path::PathBuf, rc::Rc, sync::Arc};
|
|
||||||
|
|
||||||
pub struct App {
|
|
||||||
dispatcher: Arc<Dispatcher>,
|
|
||||||
fonts: Arc<FontSystem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
dispatcher: Arc::new(Dispatcher),
|
|
||||||
fonts: Arc::new(FontSystem::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl platform::App for App {
|
|
||||||
fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
|
|
||||||
self.dispatcher.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate(&self, ignoring_other_apps: bool) {
|
|
||||||
unsafe {
|
|
||||||
let app = NSApplication::sharedApplication(nil);
|
|
||||||
app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_window(
|
|
||||||
&self,
|
|
||||||
options: platform::WindowOptions,
|
|
||||||
executor: Rc<executor::Foreground>,
|
|
||||||
) -> Result<Box<dyn platform::Window>> {
|
|
||||||
Ok(Box::new(Window::open(options, executor, self.fonts())?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt_for_paths(
|
|
||||||
&self,
|
|
||||||
options: platform::PathPromptOptions,
|
|
||||||
) -> Option<Vec<std::path::PathBuf>> {
|
|
||||||
unsafe {
|
|
||||||
let panel = NSOpenPanel::openPanel(nil);
|
|
||||||
panel.setCanChooseDirectories_(options.directories.to_objc());
|
|
||||||
panel.setCanChooseFiles_(options.files.to_objc());
|
|
||||||
panel.setAllowsMultipleSelection_(options.multiple.to_objc());
|
|
||||||
panel.setResolvesAliases_(false.to_objc());
|
|
||||||
let response = panel.runModal();
|
|
||||||
if response == NSModalResponse::NSModalResponseOk {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
let urls = panel.URLs();
|
|
||||||
for i in 0..urls.count() {
|
|
||||||
let url = urls.objectAtIndex(i);
|
|
||||||
let string = url.absoluteString();
|
|
||||||
let string = std::ffi::CStr::from_ptr(string.UTF8String())
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
if let Some(path) = string.strip_prefix("file://") {
|
|
||||||
result.push(PathBuf::from(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(result)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fonts(&self) -> Arc<dyn platform::FontSystem> {
|
|
||||||
self.fonts.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quit(&self) {
|
|
||||||
unsafe {
|
|
||||||
let app = NSApplication::sharedApplication(nil);
|
|
||||||
let _: () = msg_send![app, terminate: nil];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy(&self, text: &str) {
|
|
||||||
unsafe {
|
|
||||||
let data = NSData::dataWithBytes_length_(
|
|
||||||
nil,
|
|
||||||
text.as_ptr() as *const c_void,
|
|
||||||
text.len() as u64,
|
|
||||||
);
|
|
||||||
let pasteboard = NSPasteboard::generalPasteboard(nil);
|
|
||||||
pasteboard.clearContents();
|
|
||||||
pasteboard.setData_forType(data, NSPasteboardTypeString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +1,22 @@
|
||||||
mod app;
|
|
||||||
mod atlas;
|
mod atlas;
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
mod event;
|
mod event;
|
||||||
mod fonts;
|
mod fonts;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
|
mod platform;
|
||||||
mod renderer;
|
mod renderer;
|
||||||
mod runner;
|
|
||||||
mod sprite_cache;
|
mod sprite_cache;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
use crate::platform;
|
|
||||||
pub use app::App;
|
|
||||||
use cocoa::base::{BOOL, NO, YES};
|
use cocoa::base::{BOOL, NO, YES};
|
||||||
pub use dispatcher::Dispatcher;
|
pub use dispatcher::Dispatcher;
|
||||||
pub use fonts::FontSystem;
|
pub use fonts::FontSystem;
|
||||||
pub use runner::Runner;
|
use platform::MacPlatform;
|
||||||
|
use std::rc::Rc;
|
||||||
use window::Window;
|
use window::Window;
|
||||||
|
|
||||||
pub fn app() -> impl platform::App {
|
pub fn platform() -> Rc<dyn super::Platform> {
|
||||||
App::new()
|
Rc::new(MacPlatform::new())
|
||||||
}
|
|
||||||
|
|
||||||
pub fn runner() -> impl platform::Runner {
|
|
||||||
Runner::new()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait BoolExt {
|
trait BoolExt {
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
use crate::{keymap::Keystroke, platform::Event, Menu, MenuItem};
|
use super::{BoolExt as _, Dispatcher, FontSystem, Window};
|
||||||
|
use crate::{executor, keymap::Keystroke, platform, Event, Menu, MenuItem};
|
||||||
|
use anyhow::Result;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{
|
appkit::{
|
||||||
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
|
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
|
||||||
NSEventModifierFlags, NSMenu, NSMenuItem, NSWindow,
|
NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
|
||||||
|
NSPasteboardTypeString, NSWindow,
|
||||||
},
|
},
|
||||||
base::{id, nil, selector},
|
base::{id, nil, selector},
|
||||||
foundation::{NSArray, NSAutoreleasePool, NSInteger, NSString},
|
foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSURL},
|
||||||
};
|
};
|
||||||
use ctor::ctor;
|
use ctor::ctor;
|
||||||
use objc::{
|
use objc::{
|
||||||
|
@ -15,14 +18,18 @@ use objc::{
|
||||||
runtime::{Class, Object, Sel},
|
runtime::{Class, Object, Sel},
|
||||||
sel, sel_impl,
|
sel, sel_impl,
|
||||||
};
|
};
|
||||||
|
use ptr::null_mut;
|
||||||
use std::{
|
use std::{
|
||||||
ffi::CStr,
|
cell::RefCell,
|
||||||
os::raw::{c_char, c_void},
|
ffi::{c_void, CStr},
|
||||||
|
os::raw::c_char,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
ptr,
|
ptr,
|
||||||
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RUNNER_IVAR: &'static str = "runner";
|
const MAC_PLATFORM_IVAR: &'static str = "platform";
|
||||||
static mut APP_CLASS: *const Class = ptr::null();
|
static mut APP_CLASS: *const Class = ptr::null();
|
||||||
static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
|
static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
|
||||||
|
|
||||||
|
@ -30,7 +37,7 @@ static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
|
||||||
unsafe fn build_classes() {
|
unsafe fn build_classes() {
|
||||||
APP_CLASS = {
|
APP_CLASS = {
|
||||||
let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
|
let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
|
||||||
decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
|
decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(sendEvent:),
|
sel!(sendEvent:),
|
||||||
send_event as extern "C" fn(&mut Object, Sel, id),
|
send_event as extern "C" fn(&mut Object, Sel, id),
|
||||||
|
@ -40,7 +47,7 @@ unsafe fn build_classes() {
|
||||||
|
|
||||||
APP_DELEGATE_CLASS = {
|
APP_DELEGATE_CLASS = {
|
||||||
let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
|
let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
|
||||||
decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
|
decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(applicationDidFinishLaunching:),
|
sel!(applicationDidFinishLaunching:),
|
||||||
did_finish_launching as extern "C" fn(&mut Object, Sel, id),
|
did_finish_launching as extern "C" fn(&mut Object, Sel, id),
|
||||||
|
@ -65,25 +72,37 @@ unsafe fn build_classes() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
pub struct MacPlatform {
|
||||||
pub struct Runner {
|
dispatcher: Arc<Dispatcher>,
|
||||||
finish_launching_callback: Option<Box<dyn FnOnce()>>,
|
fonts: Arc<FontSystem>,
|
||||||
become_active_callback: Option<Box<dyn FnMut()>>,
|
callbacks: RefCell<Callbacks>,
|
||||||
resign_active_callback: Option<Box<dyn FnMut()>>,
|
menu_item_actions: RefCell<Vec<String>>,
|
||||||
event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
|
|
||||||
open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
|
|
||||||
menu_command_callback: Option<Box<dyn FnMut(&str)>>,
|
|
||||||
menu_item_actions: Vec<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Runner {
|
#[derive(Default)]
|
||||||
|
struct Callbacks {
|
||||||
|
become_active: Option<Box<dyn FnMut()>>,
|
||||||
|
resign_active: Option<Box<dyn FnMut()>>,
|
||||||
|
event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
|
||||||
|
menu_command: Option<Box<dyn FnMut(&str)>>,
|
||||||
|
open_files: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
|
||||||
|
finish_launching: Option<Box<dyn FnOnce() -> ()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MacPlatform {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Self {
|
||||||
|
dispatcher: Arc::new(Dispatcher),
|
||||||
|
fonts: Arc::new(FontSystem::new()),
|
||||||
|
callbacks: Default::default(),
|
||||||
|
menu_item_actions: Default::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn create_menu_bar(&mut self, menus: &[Menu]) -> id {
|
unsafe fn create_menu_bar(&self, menus: &[Menu]) -> id {
|
||||||
let menu_bar = NSMenu::new(nil).autorelease();
|
let menu_bar = NSMenu::new(nil).autorelease();
|
||||||
self.menu_item_actions.clear();
|
let mut menu_item_actions = self.menu_item_actions.borrow_mut();
|
||||||
|
menu_item_actions.clear();
|
||||||
|
|
||||||
for menu_config in menus {
|
for menu_config in menus {
|
||||||
let menu_bar_item = NSMenuItem::new(nil).autorelease();
|
let menu_bar_item = NSMenuItem::new(nil).autorelease();
|
||||||
|
@ -140,9 +159,9 @@ impl Runner {
|
||||||
.autorelease();
|
.autorelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
let tag = self.menu_item_actions.len() as NSInteger;
|
let tag = menu_item_actions.len() as NSInteger;
|
||||||
let _: () = msg_send![item, setTag: tag];
|
let _: () = msg_send![item, setTag: tag];
|
||||||
self.menu_item_actions.push(action.to_string());
|
menu_item_actions.push(action.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,85 +176,148 @@ impl Runner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::platform::Runner for Runner {
|
impl platform::Platform for MacPlatform {
|
||||||
fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
|
fn on_become_active(&self, callback: Box<dyn FnMut()>) {
|
||||||
self.finish_launching_callback = Some(Box::new(callback));
|
self.callbacks.borrow_mut().become_active = Some(callback);
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_menu_command<F: 'static + FnMut(&str)>(mut self, callback: F) -> Self {
|
fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
|
||||||
self.menu_command_callback = Some(Box::new(callback));
|
self.callbacks.borrow_mut().resign_active = Some(callback);
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
|
fn on_event(&self, callback: Box<dyn FnMut(crate::Event) -> bool>) {
|
||||||
log::info!("become active");
|
self.callbacks.borrow_mut().event = Some(callback);
|
||||||
self.become_active_callback = Some(Box::new(callback));
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
|
fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>) {
|
||||||
self.resign_active_callback = Some(Box::new(callback));
|
self.callbacks.borrow_mut().menu_command = Some(callback);
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
|
fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>) {
|
||||||
self.event_callback = Some(Box::new(callback));
|
self.callbacks.borrow_mut().open_files = Some(callback);
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
|
fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>) {
|
||||||
self.open_files_callback = Some(Box::new(callback));
|
self.callbacks.borrow_mut().finish_launching = Some(on_finish_launching);
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_menus(mut self, menus: &[Menu]) -> Self {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
|
||||||
app.setMainMenu_(self.create_menu_bar(menus));
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(self) {
|
|
||||||
unsafe {
|
|
||||||
let self_ptr = Box::into_raw(Box::new(self));
|
|
||||||
|
|
||||||
let pool = NSAutoreleasePool::new(nil);
|
|
||||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||||
let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
|
let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
|
||||||
|
|
||||||
(*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
|
|
||||||
(*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
|
|
||||||
app.setDelegate_(app_delegate);
|
app.setDelegate_(app_delegate);
|
||||||
|
|
||||||
|
let self_ptr = self as *const Self as *const c_void;
|
||||||
|
(*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
|
||||||
|
(*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
|
||||||
|
|
||||||
|
let pool = NSAutoreleasePool::new(nil);
|
||||||
app.run();
|
app.run();
|
||||||
pool.drain();
|
pool.drain();
|
||||||
|
|
||||||
// The Runner is done running when we get here, so we can reinstantiate the Box and drop it.
|
(*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
|
||||||
Box::from_raw(self_ptr);
|
(*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn get_runner(object: &mut Object) -> &mut Runner {
|
fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
|
||||||
let runner_ptr: *mut c_void = *object.get_ivar(RUNNER_IVAR);
|
self.dispatcher.clone()
|
||||||
&mut *(runner_ptr as *mut Runner)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
|
fn activate(&self, ignoring_other_apps: bool) {
|
||||||
let event = unsafe { Event::from_native(native_event, None) };
|
unsafe {
|
||||||
|
let app = NSApplication::sharedApplication(nil);
|
||||||
|
app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(event) = event {
|
fn open_window(
|
||||||
let runner = unsafe { get_runner(this) };
|
&self,
|
||||||
if let Some(callback) = runner.event_callback.as_mut() {
|
options: platform::WindowOptions,
|
||||||
if callback(event) {
|
executor: Rc<executor::Foreground>,
|
||||||
return;
|
) -> Result<Box<dyn platform::Window>> {
|
||||||
|
Ok(Box::new(Window::open(options, executor, self.fonts())?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_for_paths(
|
||||||
|
&self,
|
||||||
|
options: platform::PathPromptOptions,
|
||||||
|
) -> Option<Vec<std::path::PathBuf>> {
|
||||||
|
unsafe {
|
||||||
|
let panel = NSOpenPanel::openPanel(nil);
|
||||||
|
panel.setCanChooseDirectories_(options.directories.to_objc());
|
||||||
|
panel.setCanChooseFiles_(options.files.to_objc());
|
||||||
|
panel.setAllowsMultipleSelection_(options.multiple.to_objc());
|
||||||
|
panel.setResolvesAliases_(false.to_objc());
|
||||||
|
let response = panel.runModal();
|
||||||
|
if response == NSModalResponse::NSModalResponseOk {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let urls = panel.URLs();
|
||||||
|
for i in 0..urls.count() {
|
||||||
|
let url = urls.objectAtIndex(i);
|
||||||
|
let string = url.absoluteString();
|
||||||
|
let string = std::ffi::CStr::from_ptr(string.UTF8String())
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
if let Some(path) = string.strip_prefix("file://") {
|
||||||
|
result.push(PathBuf::from(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(result)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fonts(&self) -> Arc<dyn platform::FontSystem> {
|
||||||
|
self.fonts.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quit(&self) {
|
||||||
|
unsafe {
|
||||||
|
let app = NSApplication::sharedApplication(nil);
|
||||||
|
let _: () = msg_send![app, terminate: nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy(&self, text: &str) {
|
||||||
|
unsafe {
|
||||||
|
let data = NSData::dataWithBytes_length_(
|
||||||
|
nil,
|
||||||
|
text.as_ptr() as *const c_void,
|
||||||
|
text.len() as u64,
|
||||||
|
);
|
||||||
|
let pasteboard = NSPasteboard::generalPasteboard(nil);
|
||||||
|
pasteboard.clearContents();
|
||||||
|
pasteboard.setData_forType(data, NSPasteboardTypeString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_menus(&self, menus: &[Menu]) {
|
||||||
|
unsafe {
|
||||||
|
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||||
|
app.setMainMenu_(self.create_menu_bar(menus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_platform(object: &mut Object) -> &MacPlatform {
|
||||||
|
let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
|
||||||
|
assert!(!platform_ptr.is_null());
|
||||||
|
&*(platform_ptr as *const MacPlatform)
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
|
if let Some(event) = Event::from_native(native_event, None) {
|
||||||
|
let platform = get_platform(this);
|
||||||
|
if let Some(callback) = platform.callbacks.borrow_mut().event.as_mut() {
|
||||||
|
if callback(event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,23 +326,23 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
|
||||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||||
app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
|
app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
|
||||||
|
|
||||||
let runner = get_runner(this);
|
let platform = get_platform(this);
|
||||||
if let Some(callback) = runner.finish_launching_callback.take() {
|
if let Some(callback) = platform.callbacks.borrow_mut().finish_launching.take() {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
|
extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
|
||||||
let runner = unsafe { get_runner(this) };
|
let platform = unsafe { get_platform(this) };
|
||||||
if let Some(callback) = runner.become_active_callback.as_mut() {
|
if let Some(callback) = platform.callbacks.borrow_mut().become_active.as_mut() {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
|
extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
|
||||||
let runner = unsafe { get_runner(this) };
|
let platform = unsafe { get_platform(this) };
|
||||||
if let Some(callback) = runner.resign_active_callback.as_mut() {
|
if let Some(callback) = platform.callbacks.borrow_mut().resign_active.as_mut() {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,19 +363,19 @@ extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
let runner = unsafe { get_runner(this) };
|
let platform = unsafe { get_platform(this) };
|
||||||
if let Some(callback) = runner.open_files_callback.as_mut() {
|
if let Some(callback) = platform.callbacks.borrow_mut().open_files.as_mut() {
|
||||||
callback(paths);
|
callback(paths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
|
extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let runner = get_runner(this);
|
let platform = get_platform(this);
|
||||||
if let Some(callback) = runner.menu_command_callback.as_mut() {
|
if let Some(callback) = platform.callbacks.borrow_mut().menu_command.as_mut() {
|
||||||
let tag: NSInteger = msg_send![item, tag];
|
let tag: NSInteger = msg_send![item, tag];
|
||||||
let index = tag as usize;
|
let index = tag as usize;
|
||||||
if let Some(action) = runner.menu_item_actions.get(index) {
|
if let Some(action) = platform.menu_item_actions.borrow().get(index) {
|
||||||
callback(&action);
|
callback(&action);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,19 +22,17 @@ use async_task::Runnable;
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
use std::{ops::Range, path::PathBuf, rc::Rc, sync::Arc};
|
use std::{ops::Range, path::PathBuf, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
pub trait Runner {
|
pub trait Platform {
|
||||||
fn on_finish_launching<F: 'static + FnOnce()>(self, callback: F) -> Self;
|
fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>);
|
||||||
fn on_menu_command<F: 'static + FnMut(&str)>(self, callback: F) -> Self;
|
fn on_become_active(&self, callback: Box<dyn FnMut()>);
|
||||||
fn on_become_active<F: 'static + FnMut()>(self, callback: F) -> Self;
|
fn on_resign_active(&self, callback: Box<dyn FnMut()>);
|
||||||
fn on_resign_active<F: 'static + FnMut()>(self, callback: F) -> Self;
|
fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
|
||||||
fn on_event<F: 'static + FnMut(Event) -> bool>(self, callback: F) -> Self;
|
fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>);
|
||||||
fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(self, callback: F) -> Self;
|
fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>);
|
||||||
fn set_menus(self, menus: &[Menu]) -> Self;
|
|
||||||
fn run(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait App {
|
|
||||||
fn dispatcher(&self) -> Arc<dyn Dispatcher>;
|
fn dispatcher(&self) -> Arc<dyn Dispatcher>;
|
||||||
|
fn fonts(&self) -> Arc<dyn FontSystem>;
|
||||||
|
|
||||||
fn activate(&self, ignoring_other_apps: bool);
|
fn activate(&self, ignoring_other_apps: bool);
|
||||||
fn open_window(
|
fn open_window(
|
||||||
&self,
|
&self,
|
||||||
|
@ -42,9 +40,9 @@ pub trait App {
|
||||||
executor: Rc<executor::Foreground>,
|
executor: Rc<executor::Foreground>,
|
||||||
) -> Result<Box<dyn Window>>;
|
) -> Result<Box<dyn Window>>;
|
||||||
fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>;
|
fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>;
|
||||||
fn fonts(&self) -> Arc<dyn FontSystem>;
|
|
||||||
fn quit(&self);
|
fn quit(&self);
|
||||||
fn copy(&self, text: &str);
|
fn copy(&self, text: &str);
|
||||||
|
fn set_menus(&self, menus: &[Menu]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Dispatcher: Send + Sync {
|
pub trait Dispatcher: Send + Sync {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use pathfinder_geometry::vector::Vector2F;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
struct App {
|
struct Platform {
|
||||||
dispatcher: Arc<dyn super::Dispatcher>,
|
dispatcher: Arc<dyn super::Dispatcher>,
|
||||||
fonts: Arc<dyn super::FontSystem>,
|
fonts: Arc<dyn super::FontSystem>,
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,7 @@ pub struct Window {
|
||||||
resize_handlers: Vec<Box<dyn FnMut(&mut dyn super::WindowContext)>>,
|
resize_handlers: Vec<Box<dyn FnMut(&mut dyn super::WindowContext)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WindowContext {}
|
impl Platform {
|
||||||
|
|
||||||
impl App {
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
dispatcher: Arc::new(Dispatcher),
|
dispatcher: Arc::new(Dispatcher),
|
||||||
|
@ -28,11 +26,29 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::App for App {
|
impl super::Platform for Platform {
|
||||||
|
fn on_menu_command(&self, _: Box<dyn FnMut(&str)>) {}
|
||||||
|
|
||||||
|
fn on_become_active(&self, _: Box<dyn FnMut()>) {}
|
||||||
|
|
||||||
|
fn on_resign_active(&self, _: Box<dyn FnMut()>) {}
|
||||||
|
|
||||||
|
fn on_event(&self, _: Box<dyn FnMut(crate::Event) -> bool>) {}
|
||||||
|
|
||||||
|
fn on_open_files(&self, _: Box<dyn FnMut(Vec<std::path::PathBuf>)>) {}
|
||||||
|
|
||||||
|
fn run(&self, _on_finish_launching: Box<dyn FnOnce() -> ()>) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn dispatcher(&self) -> Arc<dyn super::Dispatcher> {
|
fn dispatcher(&self) -> Arc<dyn super::Dispatcher> {
|
||||||
self.dispatcher.clone()
|
self.dispatcher.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fonts(&self) -> std::sync::Arc<dyn super::FontSystem> {
|
||||||
|
self.fonts.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn activate(&self, _ignoring_other_apps: bool) {}
|
fn activate(&self, _ignoring_other_apps: bool) {}
|
||||||
|
|
||||||
fn open_window(
|
fn open_window(
|
||||||
|
@ -43,9 +59,7 @@ impl super::App for App {
|
||||||
Ok(Box::new(Window::new(options.bounds.size())))
|
Ok(Box::new(Window::new(options.bounds.size())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fonts(&self) -> std::sync::Arc<dyn super::FontSystem> {
|
fn set_menus(&self, _menus: &[crate::Menu]) {}
|
||||||
self.fonts.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quit(&self) {}
|
fn quit(&self) {}
|
||||||
|
|
||||||
|
@ -102,6 +116,6 @@ impl super::Window for Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn app() -> impl super::App {
|
pub fn platform() -> impl super::Platform {
|
||||||
App::new()
|
Platform::new()
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,14 +69,14 @@ impl Presenter {
|
||||||
let mut scene = Scene::new(scale_factor);
|
let mut scene = Scene::new(scale_factor);
|
||||||
|
|
||||||
if let Some(root_view_id) = app.root_view_id(self.window_id) {
|
if let Some(root_view_id) = app.root_view_id(self.window_id) {
|
||||||
self.layout(window_size, app.downgrade());
|
self.layout(window_size, app.as_ref());
|
||||||
self.after_layout(app);
|
self.after_layout(app);
|
||||||
let mut ctx = PaintContext {
|
let mut ctx = PaintContext {
|
||||||
scene: &mut scene,
|
scene: &mut scene,
|
||||||
font_cache: &self.font_cache,
|
font_cache: &self.font_cache,
|
||||||
text_layout_cache: &self.text_layout_cache,
|
text_layout_cache: &self.text_layout_cache,
|
||||||
rendered_views: &mut self.rendered_views,
|
rendered_views: &mut self.rendered_views,
|
||||||
app: app.downgrade(),
|
app: app.as_ref(),
|
||||||
};
|
};
|
||||||
ctx.paint(root_view_id, Vector2F::zero());
|
ctx.paint(root_view_id, Vector2F::zero());
|
||||||
self.text_layout_cache.finish_frame();
|
self.text_layout_cache.finish_frame();
|
||||||
|
|
|
@ -2193,13 +2193,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_events() {
|
fn test_edit_events() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
|
let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
|
||||||
let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
|
let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
|
||||||
let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef"));
|
let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef"));
|
||||||
let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef"));
|
let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef"));
|
||||||
let ops = buffer1.update(&mut app, |buffer, ctx| {
|
let ops = buffer1.update(app, |buffer, ctx| {
|
||||||
let buffer_1_events = buffer_1_events.clone();
|
let buffer_1_events = buffer_1_events.clone();
|
||||||
ctx.subscribe(&buffer1, move |_, event, _| {
|
ctx.subscribe(&buffer1, move |_, event, _| {
|
||||||
buffer_1_events.borrow_mut().push(event.clone())
|
buffer_1_events.borrow_mut().push(event.clone())
|
||||||
|
@ -2211,7 +2211,7 @@ mod tests {
|
||||||
|
|
||||||
buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap()
|
buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap()
|
||||||
});
|
});
|
||||||
buffer2.update(&mut app, |buffer, ctx| {
|
buffer2.update(app, |buffer, ctx| {
|
||||||
buffer.apply_ops(ops, Some(ctx)).unwrap();
|
buffer.apply_ops(ops, Some(ctx)).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2715,12 +2715,12 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_modified() -> Result<()> {
|
fn test_is_modified() -> Result<()> {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let model = app.add_model(|_| Buffer::new(0, "abc"));
|
let model = app.add_model(|_| Buffer::new(0, "abc"));
|
||||||
let events = Rc::new(RefCell::new(Vec::new()));
|
let events = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
|
||||||
// initially, the buffer isn't dirty.
|
// initially, the buffer isn't dirty.
|
||||||
model.update(&mut app, |buffer, ctx| {
|
model.update(app, |buffer, ctx| {
|
||||||
ctx.subscribe(&model, {
|
ctx.subscribe(&model, {
|
||||||
let events = events.clone();
|
let events = events.clone();
|
||||||
move |_, event, _| events.borrow_mut().push(event.clone())
|
move |_, event, _| events.borrow_mut().push(event.clone())
|
||||||
|
@ -2733,7 +2733,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
// after the first edit, the buffer is dirty, and emits a dirtied event.
|
// after the first edit, the buffer is dirty, and emits a dirtied event.
|
||||||
model.update(&mut app, |buffer, ctx| {
|
model.update(app, |buffer, ctx| {
|
||||||
assert!(buffer.text() == "ac");
|
assert!(buffer.text() == "ac");
|
||||||
assert!(buffer.is_dirty());
|
assert!(buffer.is_dirty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -2752,7 +2752,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
// after saving, the buffer is not dirty, and emits a saved event.
|
// after saving, the buffer is not dirty, and emits a saved event.
|
||||||
model.update(&mut app, |buffer, ctx| {
|
model.update(app, |buffer, ctx| {
|
||||||
assert!(!buffer.is_dirty());
|
assert!(!buffer.is_dirty());
|
||||||
assert_eq!(*events.borrow(), &[Event::Saved]);
|
assert_eq!(*events.borrow(), &[Event::Saved]);
|
||||||
events.borrow_mut().clear();
|
events.borrow_mut().clear();
|
||||||
|
@ -2762,7 +2762,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
// after editing again, the buffer is dirty, and emits another dirty event.
|
// after editing again, the buffer is dirty, and emits another dirty event.
|
||||||
model.update(&mut app, |buffer, ctx| {
|
model.update(app, |buffer, ctx| {
|
||||||
assert!(buffer.text() == "aBDc");
|
assert!(buffer.text() == "aBDc");
|
||||||
assert!(buffer.is_dirty());
|
assert!(buffer.is_dirty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -2788,7 +2788,7 @@ mod tests {
|
||||||
assert!(buffer.is_dirty());
|
assert!(buffer.is_dirty());
|
||||||
});
|
});
|
||||||
|
|
||||||
model.update(&mut app, |_, _| {
|
model.update(app, |_, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*events.borrow(),
|
*events.borrow(),
|
||||||
&[Event::Edited(vec![Edit {
|
&[Event::Edited(vec![Edit {
|
||||||
|
|
|
@ -37,7 +37,7 @@ impl BufferElement {
|
||||||
ctx: &mut EventContext,
|
ctx: &mut EventContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if paint.text_bounds.contains_point(position) {
|
if paint.text_bounds.contains_point(position) {
|
||||||
let view = self.view.as_ref(ctx.app);
|
let view = self.view.read(ctx.app);
|
||||||
let position =
|
let position =
|
||||||
paint.point_for_position(view, layout, position, ctx.font_cache, ctx.app);
|
paint.point_for_position(view, layout, position, ctx.font_cache, ctx.app);
|
||||||
ctx.dispatch_action("buffer:select", SelectAction::Begin { position, add: cmd });
|
ctx.dispatch_action("buffer:select", SelectAction::Begin { position, add: cmd });
|
||||||
|
@ -48,7 +48,7 @@ impl BufferElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_up(&self, _position: Vector2F, ctx: &mut EventContext) -> bool {
|
fn mouse_up(&self, _position: Vector2F, ctx: &mut EventContext) -> bool {
|
||||||
if self.view.as_ref(ctx.app).is_selecting() {
|
if self.view.read(ctx.app).is_selecting() {
|
||||||
ctx.dispatch_action("buffer:select", SelectAction::End);
|
ctx.dispatch_action("buffer:select", SelectAction::End);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -63,7 +63,7 @@ impl BufferElement {
|
||||||
paint: &mut PaintState,
|
paint: &mut PaintState,
|
||||||
ctx: &mut EventContext,
|
ctx: &mut EventContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let view = self.view.as_ref(ctx.app);
|
let view = self.view.read(ctx.app);
|
||||||
|
|
||||||
if view.is_selecting() {
|
if view.is_selecting() {
|
||||||
let rect = paint.text_bounds;
|
let rect = paint.text_bounds;
|
||||||
|
@ -145,7 +145,7 @@ impl BufferElement {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let view = self.view.as_ref(ctx.app);
|
let view = self.view.read(ctx.app);
|
||||||
let font_cache = &ctx.font_cache;
|
let font_cache = &ctx.font_cache;
|
||||||
let layout_cache = &ctx.text_layout_cache;
|
let layout_cache = &ctx.text_layout_cache;
|
||||||
let max_glyph_width = view.em_width(font_cache);
|
let max_glyph_width = view.em_width(font_cache);
|
||||||
|
@ -167,7 +167,7 @@ impl BufferElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_gutter(&mut self, rect: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
|
fn paint_gutter(&mut self, rect: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
|
||||||
let view = self.view.as_ref(ctx.app);
|
let view = self.view.read(ctx.app);
|
||||||
let line_height = view.line_height(ctx.font_cache);
|
let line_height = view.line_height(ctx.font_cache);
|
||||||
let scroll_top = view.scroll_position().y() * line_height;
|
let scroll_top = view.scroll_position().y() * line_height;
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ impl BufferElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_text(&mut self, bounds: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
|
fn paint_text(&mut self, bounds: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
|
||||||
let view = self.view.as_ref(ctx.app);
|
let view = self.view.read(ctx.app);
|
||||||
let line_height = view.line_height(ctx.font_cache);
|
let line_height = view.line_height(ctx.font_cache);
|
||||||
let descent = view.font_descent(ctx.font_cache);
|
let descent = view.font_descent(ctx.font_cache);
|
||||||
let start_row = view.scroll_position().y() as u32;
|
let start_row = view.scroll_position().y() as u32;
|
||||||
|
@ -313,14 +313,14 @@ impl Element for BufferElement {
|
||||||
let app = ctx.app;
|
let app = ctx.app;
|
||||||
let mut size = constraint.max;
|
let mut size = constraint.max;
|
||||||
if size.y().is_infinite() {
|
if size.y().is_infinite() {
|
||||||
let view = self.view.as_ref(app);
|
let view = self.view.read(app);
|
||||||
size.set_y((view.max_point(app).row() + 1) as f32 * view.line_height(ctx.font_cache));
|
size.set_y((view.max_point(app).row() + 1) as f32 * view.line_height(ctx.font_cache));
|
||||||
}
|
}
|
||||||
if size.x().is_infinite() {
|
if size.x().is_infinite() {
|
||||||
unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
|
unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
|
||||||
}
|
}
|
||||||
|
|
||||||
let view = self.view.as_ref(app);
|
let view = self.view.read(app);
|
||||||
let font_cache = &ctx.font_cache;
|
let font_cache = &ctx.font_cache;
|
||||||
let layout_cache = &ctx.text_layout_cache;
|
let layout_cache = &ctx.text_layout_cache;
|
||||||
let line_height = view.line_height(font_cache);
|
let line_height = view.line_height(font_cache);
|
||||||
|
@ -402,9 +402,9 @@ impl Element for BufferElement {
|
||||||
ctx: &mut AfterLayoutContext,
|
ctx: &mut AfterLayoutContext,
|
||||||
) {
|
) {
|
||||||
if let Some(layout) = layout {
|
if let Some(layout) = layout {
|
||||||
let app = ctx.app.downgrade();
|
let app = ctx.app.as_ref();
|
||||||
|
|
||||||
let view = self.view.as_ref(app);
|
let view = self.view.read(app);
|
||||||
view.clamp_scroll_left(
|
view.clamp_scroll_left(
|
||||||
layout
|
layout
|
||||||
.scroll_max(view, ctx.font_cache, ctx.text_layout_cache, app)
|
.scroll_max(view, ctx.font_cache, ctx.text_layout_cache, app)
|
||||||
|
@ -437,7 +437,7 @@ impl Element for BufferElement {
|
||||||
layout.text_size,
|
layout.text_size,
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.view.as_ref(ctx.app).is_gutter_visible() {
|
if self.view.read(ctx.app).is_gutter_visible() {
|
||||||
self.paint_gutter(gutter_bounds, layout, ctx);
|
self.paint_gutter(gutter_bounds, layout, ctx);
|
||||||
}
|
}
|
||||||
self.paint_text(text_bounds, layout, ctx);
|
self.paint_text(text_bounds, layout, ctx);
|
||||||
|
|
|
@ -6,8 +6,9 @@ use crate::{settings::Settings, watch, workspace};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
fonts::Properties as FontProperties, keymap::Binding, text_layout, App, AppContext, Element,
|
fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, Element,
|
||||||
ElementBox, Entity, FontCache, ModelHandle, View, ViewContext, WeakViewHandle,
|
ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext,
|
||||||
|
WeakViewHandle,
|
||||||
};
|
};
|
||||||
use gpui::{geometry::vector::Vector2F, TextLayoutCache};
|
use gpui::{geometry::vector::Vector2F, TextLayoutCache};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -24,7 +25,7 @@ use std::{
|
||||||
|
|
||||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||||
|
|
||||||
pub fn init(app: &mut App) {
|
pub fn init(app: &mut MutableAppContext) {
|
||||||
app.add_bindings(vec![
|
app.add_bindings(vec![
|
||||||
Binding::new("backspace", "buffer:backspace", Some("BufferView")),
|
Binding::new("backspace", "buffer:backspace", Some("BufferView")),
|
||||||
Binding::new("enter", "buffer:newline", Some("BufferView")),
|
Binding::new("enter", "buffer:newline", Some("BufferView")),
|
||||||
|
@ -124,7 +125,7 @@ impl BufferView {
|
||||||
});
|
});
|
||||||
ctx.observe(&display_map, Self::on_display_map_changed);
|
ctx.observe(&display_map, Self::on_display_map_changed);
|
||||||
|
|
||||||
let buffer_ref = buffer.as_ref(ctx);
|
let buffer_ref = buffer.read(ctx);
|
||||||
Self {
|
Self {
|
||||||
handle: ctx.handle().downgrade(),
|
handle: ctx.handle().downgrade(),
|
||||||
buffer,
|
buffer,
|
||||||
|
@ -187,7 +188,7 @@ impl BufferView {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
let visible_lines = viewport_height / line_height;
|
let visible_lines = viewport_height / line_height;
|
||||||
let first_cursor_top = self
|
let first_cursor_top = self
|
||||||
.selections
|
.selections
|
||||||
|
@ -237,7 +238,7 @@ impl BufferView {
|
||||||
layouts: &[Arc<text_layout::Line>],
|
layouts: &[Arc<text_layout::Line>],
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
) {
|
) {
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
|
|
||||||
let mut target_left = std::f32::INFINITY;
|
let mut target_left = std::f32::INFINITY;
|
||||||
let mut target_right = 0.0_f32;
|
let mut target_right = 0.0_f32;
|
||||||
|
@ -286,7 +287,7 @@ impl BufferView {
|
||||||
ctx.emit(Event::Activate);
|
ctx.emit(Event::Activate);
|
||||||
}
|
}
|
||||||
|
|
||||||
let display_map = self.display_map.as_ref(ctx);
|
let display_map = self.display_map.read(ctx);
|
||||||
let cursor = display_map
|
let cursor = display_map
|
||||||
.anchor_before(position, Bias::Left, ctx.app())
|
.anchor_before(position, Bias::Left, ctx.app())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -311,8 +312,8 @@ impl BufferView {
|
||||||
scroll_position: Vector2F,
|
scroll_position: Vector2F,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let map = self.display_map.as_ref(ctx);
|
let map = self.display_map.read(ctx);
|
||||||
let cursor = map.anchor_before(position, Bias::Left, ctx.app()).unwrap();
|
let cursor = map.anchor_before(position, Bias::Left, ctx.app()).unwrap();
|
||||||
if let Some(selection) = self.pending_selection.as_mut() {
|
if let Some(selection) = self.pending_selection.as_mut() {
|
||||||
selection.set_head(buffer, cursor);
|
selection.set_head(buffer, cursor);
|
||||||
|
@ -346,8 +347,8 @@ impl BufferView {
|
||||||
where
|
where
|
||||||
T: IntoIterator<Item = &'a Range<DisplayPoint>>,
|
T: IntoIterator<Item = &'a Range<DisplayPoint>>,
|
||||||
{
|
{
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let map = self.display_map.as_ref(ctx);
|
let map = self.display_map.read(ctx);
|
||||||
let mut selections = Vec::new();
|
let mut selections = Vec::new();
|
||||||
for range in ranges {
|
for range in ranges {
|
||||||
selections.push(Selection {
|
selections.push(Selection {
|
||||||
|
@ -365,7 +366,7 @@ impl BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, text: &String, ctx: &mut ViewContext<Self>) {
|
fn insert(&mut self, text: &String, ctx: &mut ViewContext<Self>) {
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let mut offset_ranges = SmallVec::<[Range<usize>; 32]>::new();
|
let mut offset_ranges = SmallVec::<[Range<usize>; 32]>::new();
|
||||||
for selection in &self.selections {
|
for selection in &self.selections {
|
||||||
let start = selection.start.to_offset(buffer).unwrap();
|
let start = selection.start.to_offset(buffer).unwrap();
|
||||||
|
@ -380,7 +381,7 @@ impl BufferView {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let char_count = text.chars().count() as isize;
|
let char_count = text.chars().count() as isize;
|
||||||
let mut delta = 0_isize;
|
let mut delta = 0_isize;
|
||||||
self.selections = offset_ranges
|
self.selections = offset_ranges
|
||||||
|
@ -415,8 +416,8 @@ impl BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn backspace(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
pub fn backspace(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let map = self.display_map.as_ref(ctx);
|
let map = self.display_map.read(ctx);
|
||||||
for selection in &mut self.selections {
|
for selection in &mut self.selections {
|
||||||
if selection.range(buffer).is_empty() {
|
if selection.range(buffer).is_empty() {
|
||||||
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
||||||
|
@ -438,7 +439,7 @@ impl BufferView {
|
||||||
pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
{
|
{
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
let map = self.display_map.as_ref(ctx);
|
let map = self.display_map.read(ctx);
|
||||||
for selection in &mut self.selections {
|
for selection in &mut self.selections {
|
||||||
let start = selection.start.to_display_point(map, app).unwrap();
|
let start = selection.start.to_display_point(map, app).unwrap();
|
||||||
let end = selection.end.to_display_point(map, app).unwrap();
|
let end = selection.end.to_display_point(map, app).unwrap();
|
||||||
|
@ -461,8 +462,8 @@ impl BufferView {
|
||||||
|
|
||||||
pub fn select_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
pub fn select_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
{
|
{
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let map = self.display_map.as_ref(ctx);
|
let map = self.display_map.read(ctx);
|
||||||
for selection in &mut self.selections {
|
for selection in &mut self.selections {
|
||||||
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
||||||
let cursor = map
|
let cursor = map
|
||||||
|
@ -482,7 +483,7 @@ impl BufferView {
|
||||||
pub fn move_right(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
pub fn move_right(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
{
|
{
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
for selection in &mut self.selections {
|
for selection in &mut self.selections {
|
||||||
let start = selection.start.to_display_point(map, app).unwrap();
|
let start = selection.start.to_display_point(map, app).unwrap();
|
||||||
let end = selection.end.to_display_point(map, app).unwrap();
|
let end = selection.end.to_display_point(map, app).unwrap();
|
||||||
|
@ -505,9 +506,9 @@ impl BufferView {
|
||||||
|
|
||||||
pub fn select_right(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
pub fn select_right(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
{
|
{
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
for selection in &mut self.selections {
|
for selection in &mut self.selections {
|
||||||
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
||||||
let cursor = map
|
let cursor = map
|
||||||
|
@ -525,7 +526,7 @@ impl BufferView {
|
||||||
ctx.propagate_action();
|
ctx.propagate_action();
|
||||||
} else {
|
} else {
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
for selection in &mut self.selections {
|
for selection in &mut self.selections {
|
||||||
let start = selection.start.to_display_point(map, app).unwrap();
|
let start = selection.start.to_display_point(map, app).unwrap();
|
||||||
let end = selection.end.to_display_point(map, app).unwrap();
|
let end = selection.end.to_display_point(map, app).unwrap();
|
||||||
|
@ -550,8 +551,8 @@ impl BufferView {
|
||||||
ctx.propagate_action();
|
ctx.propagate_action();
|
||||||
} else {
|
} else {
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
let buffer = self.buffer.as_ref(app);
|
let buffer = self.buffer.read(app);
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
for selection in &mut self.selections {
|
for selection in &mut self.selections {
|
||||||
let head = selection.head().to_display_point(map, app).unwrap();
|
let head = selection.head().to_display_point(map, app).unwrap();
|
||||||
let (head, goal_column) =
|
let (head, goal_column) =
|
||||||
|
@ -568,7 +569,7 @@ impl BufferView {
|
||||||
ctx.propagate_action();
|
ctx.propagate_action();
|
||||||
} else {
|
} else {
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
for selection in &mut self.selections {
|
for selection in &mut self.selections {
|
||||||
let start = selection.start.to_display_point(map, app).unwrap();
|
let start = selection.start.to_display_point(map, app).unwrap();
|
||||||
let end = selection.end.to_display_point(map, app).unwrap();
|
let end = selection.end.to_display_point(map, app).unwrap();
|
||||||
|
@ -593,8 +594,8 @@ impl BufferView {
|
||||||
ctx.propagate_action();
|
ctx.propagate_action();
|
||||||
} else {
|
} else {
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let map = self.display_map.as_ref(ctx);
|
let map = self.display_map.read(ctx);
|
||||||
for selection in &mut self.selections {
|
for selection in &mut self.selections {
|
||||||
let head = selection.head().to_display_point(map, app).unwrap();
|
let head = selection.head().to_display_point(map, app).unwrap();
|
||||||
let (head, goal_column) =
|
let (head, goal_column) =
|
||||||
|
@ -614,7 +615,7 @@ impl BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_selections(&mut self, ctx: &AppContext) {
|
fn merge_selections(&mut self, ctx: &AppContext) {
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let mut i = 1;
|
let mut i = 1;
|
||||||
while i < self.selections.len() {
|
while i < self.selections.len() {
|
||||||
if self.selections[i - 1]
|
if self.selections[i - 1]
|
||||||
|
@ -650,14 +651,14 @@ impl BufferView {
|
||||||
self.selections
|
self.selections
|
||||||
.first()
|
.first()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.display_range(self.display_map.as_ref(app), app)
|
.display_range(self.display_map.read(app), app)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_selection(&self, app: &AppContext) -> Range<DisplayPoint> {
|
pub fn last_selection(&self, app: &AppContext) -> Range<DisplayPoint> {
|
||||||
self.selections
|
self.selections
|
||||||
.last()
|
.last()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.display_range(self.display_map.as_ref(app), app)
|
.display_range(self.display_map.read(app), app)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selections_in_range<'a>(
|
pub fn selections_in_range<'a>(
|
||||||
|
@ -665,7 +666,7 @@ impl BufferView {
|
||||||
range: Range<DisplayPoint>,
|
range: Range<DisplayPoint>,
|
||||||
app: &'a AppContext,
|
app: &'a AppContext,
|
||||||
) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
|
) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
|
|
||||||
let start = map.anchor_before(range.start, Bias::Left, app).unwrap();
|
let start = map.anchor_before(range.start, Bias::Left, app).unwrap();
|
||||||
let start_index = self.selection_insertion_index(&start, app);
|
let start_index = self.selection_insertion_index(&start, app);
|
||||||
|
@ -685,7 +686,7 @@ impl BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selection_insertion_index(&self, start: &Anchor, app: &AppContext) -> usize {
|
fn selection_insertion_index(&self, start: &Anchor, app: &AppContext) -> usize {
|
||||||
let buffer = self.buffer.as_ref(app);
|
let buffer = self.buffer.read(app);
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.selections
|
.selections
|
||||||
|
@ -719,7 +720,7 @@ impl BufferView {
|
||||||
let mut fold_ranges = Vec::new();
|
let mut fold_ranges = Vec::new();
|
||||||
|
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
for selection in &self.selections {
|
for selection in &self.selections {
|
||||||
let (start, end) = selection.display_range(map, app).sorted();
|
let (start, end) = selection.display_range(map, app).sorted();
|
||||||
let buffer_start_row = start.to_buffer_point(map, Bias::Left, app).unwrap().row;
|
let buffer_start_row = start.to_buffer_point(map, Bias::Left, app).unwrap().row;
|
||||||
|
@ -749,8 +750,8 @@ impl BufferView {
|
||||||
use super::RangeExt;
|
use super::RangeExt;
|
||||||
|
|
||||||
let app = ctx.app();
|
let app = ctx.app();
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
let buffer = self.buffer.as_ref(app);
|
let buffer = self.buffer.read(app);
|
||||||
let ranges = self
|
let ranges = self
|
||||||
.selections
|
.selections
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -795,7 +796,7 @@ impl BufferView {
|
||||||
let mut is_blank = true;
|
let mut is_blank = true;
|
||||||
for c in self
|
for c in self
|
||||||
.display_map
|
.display_map
|
||||||
.as_ref(app)
|
.read(app)
|
||||||
.chars_at(DisplayPoint::new(display_row, 0), app)?
|
.chars_at(DisplayPoint::new(display_row, 0), app)?
|
||||||
{
|
{
|
||||||
if c == ' ' {
|
if c == ' ' {
|
||||||
|
@ -809,7 +810,7 @@ impl BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn foldable_range_for_line(&self, start_row: u32, app: &AppContext) -> Result<Range<Point>> {
|
fn foldable_range_for_line(&self, start_row: u32, app: &AppContext) -> Result<Range<Point>> {
|
||||||
let map = self.display_map.as_ref(app);
|
let map = self.display_map.read(app);
|
||||||
let max_point = self.max_point(app);
|
let max_point = self.max_point(app);
|
||||||
|
|
||||||
let (start_indent, _) = self.line_indent(start_row, app)?;
|
let (start_indent, _) = self.line_indent(start_row, app)?;
|
||||||
|
@ -830,7 +831,7 @@ impl BufferView {
|
||||||
|
|
||||||
pub fn fold_selected_ranges(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
pub fn fold_selected_ranges(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
self.display_map.update(ctx, |map, ctx| {
|
self.display_map.update(ctx, |map, ctx| {
|
||||||
let buffer = self.buffer.as_ref(ctx);
|
let buffer = self.buffer.read(ctx);
|
||||||
let ranges = self
|
let ranges = self
|
||||||
.selections
|
.selections
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -841,23 +842,23 @@ impl BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line(&self, display_row: u32, app: &AppContext) -> Result<String> {
|
pub fn line(&self, display_row: u32, app: &AppContext) -> Result<String> {
|
||||||
self.display_map.as_ref(app).line(display_row, app)
|
self.display_map.read(app).line(display_row, app)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_len(&self, display_row: u32, app: &AppContext) -> Result<u32> {
|
pub fn line_len(&self, display_row: u32, app: &AppContext) -> Result<u32> {
|
||||||
self.display_map.as_ref(app).line_len(display_row, app)
|
self.display_map.read(app).line_len(display_row, app)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rightmost_point(&self, app: &AppContext) -> DisplayPoint {
|
pub fn rightmost_point(&self, app: &AppContext) -> DisplayPoint {
|
||||||
self.display_map.as_ref(app).rightmost_point()
|
self.display_map.read(app).rightmost_point()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_point(&self, app: &AppContext) -> DisplayPoint {
|
pub fn max_point(&self, app: &AppContext) -> DisplayPoint {
|
||||||
self.display_map.as_ref(app).max_point(app)
|
self.display_map.read(app).max_point(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(&self, app: &AppContext) -> String {
|
pub fn text(&self, app: &AppContext) -> String {
|
||||||
self.display_map.as_ref(app).text(app)
|
self.display_map.read(app).text(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_size(&self) -> f32 {
|
pub fn font_size(&self) -> f32 {
|
||||||
|
@ -901,7 +902,7 @@ impl BufferView {
|
||||||
let font_size = settings.buffer_font_size;
|
let font_size = settings.buffer_font_size;
|
||||||
let font_id =
|
let font_id =
|
||||||
font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?;
|
font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?;
|
||||||
let digit_count = ((self.buffer.as_ref(app).max_point().row + 1) as f32)
|
let digit_count = ((self.buffer.read(app).max_point().row + 1) as f32)
|
||||||
.log10()
|
.log10()
|
||||||
.floor() as usize
|
.floor() as usize
|
||||||
+ 1;
|
+ 1;
|
||||||
|
@ -922,7 +923,7 @@ impl BufferView {
|
||||||
layout_cache: &TextLayoutCache,
|
layout_cache: &TextLayoutCache,
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
) -> Result<Vec<Arc<text_layout::Line>>> {
|
) -> Result<Vec<Arc<text_layout::Line>>> {
|
||||||
let display_map = self.display_map.as_ref(app);
|
let display_map = self.display_map.read(app);
|
||||||
|
|
||||||
let settings = smol::block_on(self.settings.read());
|
let settings = smol::block_on(self.settings.read());
|
||||||
let font_size = settings.buffer_font_size;
|
let font_size = settings.buffer_font_size;
|
||||||
|
@ -958,7 +959,7 @@ impl BufferView {
|
||||||
layout_cache: &TextLayoutCache,
|
layout_cache: &TextLayoutCache,
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
) -> Result<Vec<Arc<text_layout::Line>>> {
|
) -> Result<Vec<Arc<text_layout::Line>>> {
|
||||||
let display_map = self.display_map.as_ref(app);
|
let display_map = self.display_map.read(app);
|
||||||
|
|
||||||
rows.end = cmp::min(rows.end, display_map.max_point(app).row() + 1);
|
rows.end = cmp::min(rows.end, display_map.max_point(app).row() + 1);
|
||||||
if rows.start >= rows.end {
|
if rows.start >= rows.end {
|
||||||
|
@ -1148,7 +1149,7 @@ impl workspace::ItemView for BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self, app: &AppContext) -> std::string::String {
|
fn title(&self, app: &AppContext) -> std::string::String {
|
||||||
if let Some(path) = self.buffer.as_ref(app).path(app) {
|
if let Some(path) = self.buffer.read(app).path(app) {
|
||||||
path.file_name()
|
path.file_name()
|
||||||
.expect("buffer's path is always to a file")
|
.expect("buffer's path is always to a file")
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
|
@ -1159,7 +1160,7 @@ impl workspace::ItemView for BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> {
|
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> {
|
||||||
self.buffer.as_ref(app).entry_id()
|
self.buffer.read(app).entry_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_on_split(&self, ctx: &mut ViewContext<Self>) -> Option<Self>
|
fn clone_on_split(&self, ctx: &mut ViewContext<Self>) -> Option<Self>
|
||||||
|
@ -1176,7 +1177,7 @@ impl workspace::ItemView for BufferView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_dirty(&self, ctx: &AppContext) -> bool {
|
fn is_dirty(&self, ctx: &AppContext) -> bool {
|
||||||
self.buffer.as_ref(ctx).is_dirty()
|
self.buffer.read(ctx).is_dirty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1239,112 +1240,125 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{editor::Point, settings, test::sample_text};
|
use crate::{editor::Point, settings, test::sample_text};
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
|
use gpui::App;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_selection_with_mouse() {
|
fn test_selection_with_mouse() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n"));
|
let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n"));
|
||||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||||
let (_, buffer_view) =
|
let (_, buffer_view) =
|
||||||
app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
|
app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
|
||||||
|
|
||||||
buffer_view.update(&mut app, |view, ctx| {
|
buffer_view.update(app, |view, ctx| {
|
||||||
view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
|
view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
buffer_view.read(&app, |view, app| {
|
let view = buffer_view.read(app);
|
||||||
let selections = view
|
let selections = view
|
||||||
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
|
.selections_in_range(
|
||||||
.collect::<Vec<_>>();
|
DisplayPoint::zero()..view.max_point(app.as_ref()),
|
||||||
assert_eq!(
|
app.as_ref(),
|
||||||
selections,
|
)
|
||||||
[DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
|
.collect::<Vec<_>>();
|
||||||
);
|
assert_eq!(
|
||||||
});
|
selections,
|
||||||
|
[DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
|
||||||
|
);
|
||||||
|
|
||||||
buffer_view.update(&mut app, |view, ctx| {
|
buffer_view.update(app, |view, ctx| {
|
||||||
view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
|
view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
buffer_view.read(&app, |view, app| {
|
let view = buffer_view.read(app);
|
||||||
let selections = view
|
let selections = view
|
||||||
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
|
.selections_in_range(
|
||||||
.collect::<Vec<_>>();
|
DisplayPoint::zero()..view.max_point(app.as_ref()),
|
||||||
assert_eq!(
|
app.as_ref(),
|
||||||
selections,
|
)
|
||||||
[DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
|
.collect::<Vec<_>>();
|
||||||
);
|
assert_eq!(
|
||||||
});
|
selections,
|
||||||
|
[DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
|
||||||
|
);
|
||||||
|
|
||||||
buffer_view.update(&mut app, |view, ctx| {
|
buffer_view.update(app, |view, ctx| {
|
||||||
view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
|
view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
buffer_view.read(&app, |view, app| {
|
let view = buffer_view.read(app);
|
||||||
let selections = view
|
let selections = view
|
||||||
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
|
.selections_in_range(
|
||||||
.collect::<Vec<_>>();
|
DisplayPoint::zero()..view.max_point(app.as_ref()),
|
||||||
assert_eq!(
|
app.as_ref(),
|
||||||
selections,
|
)
|
||||||
[DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
|
.collect::<Vec<_>>();
|
||||||
);
|
assert_eq!(
|
||||||
});
|
selections,
|
||||||
|
[DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
|
||||||
|
);
|
||||||
|
|
||||||
buffer_view.update(&mut app, |view, ctx| {
|
buffer_view.update(app, |view, ctx| {
|
||||||
view.end_selection(ctx);
|
view.end_selection(ctx);
|
||||||
view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
|
view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
buffer_view.read(&app, |view, app| {
|
let view = buffer_view.read(app);
|
||||||
let selections = view
|
let selections = view
|
||||||
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
|
.selections_in_range(
|
||||||
.collect::<Vec<_>>();
|
DisplayPoint::zero()..view.max_point(app.as_ref()),
|
||||||
assert_eq!(
|
app.as_ref(),
|
||||||
selections,
|
)
|
||||||
[DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
|
.collect::<Vec<_>>();
|
||||||
);
|
assert_eq!(
|
||||||
});
|
selections,
|
||||||
|
[DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
|
||||||
|
);
|
||||||
|
|
||||||
buffer_view.update(&mut app, |view, ctx| {
|
buffer_view.update(app, |view, ctx| {
|
||||||
view.begin_selection(DisplayPoint::new(3, 3), true, ctx);
|
view.begin_selection(DisplayPoint::new(3, 3), true, ctx);
|
||||||
view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx);
|
view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
buffer_view.read(&app, |view, app| {
|
let view = buffer_view.read(app);
|
||||||
let selections = view
|
let selections = view
|
||||||
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
|
.selections_in_range(
|
||||||
.collect::<Vec<_>>();
|
DisplayPoint::zero()..view.max_point(app.as_ref()),
|
||||||
assert_eq!(
|
app.as_ref(),
|
||||||
selections,
|
)
|
||||||
[
|
.collect::<Vec<_>>();
|
||||||
DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
|
assert_eq!(
|
||||||
DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
|
selections,
|
||||||
]
|
[
|
||||||
);
|
DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
|
||||||
});
|
DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
buffer_view.update(&mut app, |view, ctx| {
|
buffer_view.update(app, |view, ctx| {
|
||||||
view.end_selection(ctx);
|
view.end_selection(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
buffer_view.read(&app, |view, app| {
|
let view = buffer_view.read(app);
|
||||||
let selections = view
|
let selections = view
|
||||||
.selections_in_range(DisplayPoint::zero()..view.max_point(app), app)
|
.selections_in_range(
|
||||||
.collect::<Vec<_>>();
|
DisplayPoint::zero()..view.max_point(app.as_ref()),
|
||||||
assert_eq!(
|
app.as_ref(),
|
||||||
selections,
|
)
|
||||||
[DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
|
.collect::<Vec<_>>();
|
||||||
);
|
assert_eq!(
|
||||||
});
|
selections,
|
||||||
|
[DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_layout_line_numbers() -> Result<()> {
|
fn test_layout_line_numbers() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let layout_cache = TextLayoutCache::new(app.platform().fonts());
|
let layout_cache = TextLayoutCache::new(app.platform().fonts());
|
||||||
let font_cache = app.font_cache();
|
let font_cache = app.font_cache().clone();
|
||||||
|
|
||||||
let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
|
let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
|
||||||
|
|
||||||
|
@ -1352,19 +1366,17 @@ mod tests {
|
||||||
let (_, view) =
|
let (_, view) =
|
||||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||||
|
|
||||||
view.read(&app, |view, app| {
|
let layouts = view
|
||||||
let layouts = view.layout_line_numbers(1000.0, &font_cache, &layout_cache, app)?;
|
.read(app)
|
||||||
assert_eq!(layouts.len(), 6);
|
.layout_line_numbers(1000.0, &font_cache, &layout_cache, app.as_ref())
|
||||||
Result::<()>::Ok(())
|
.unwrap();
|
||||||
})?;
|
assert_eq!(layouts.len(), 6);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fold() -> Result<()> {
|
fn test_fold() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let buffer = app.add_model(|_| {
|
let buffer = app.add_model(|_| {
|
||||||
Buffer::new(
|
Buffer::new(
|
||||||
0,
|
0,
|
||||||
|
@ -1392,8 +1404,9 @@ mod tests {
|
||||||
let (_, view) =
|
let (_, view) =
|
||||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||||
|
|
||||||
view.update(&mut app, |view, ctx| {
|
view.update(app, |view, ctx| {
|
||||||
view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)?;
|
view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)
|
||||||
|
.unwrap();
|
||||||
view.fold(&(), ctx);
|
view.fold(&(), ctx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(ctx.app()),
|
view.text(ctx.app()),
|
||||||
|
@ -1447,24 +1460,20 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
view.unfold(&(), ctx);
|
view.unfold(&(), ctx);
|
||||||
assert_eq!(view.text(ctx.app()), buffer.as_ref(ctx).text());
|
assert_eq!(view.text(ctx.app()), buffer.read(ctx).text());
|
||||||
|
});
|
||||||
Ok::<(), Error>(())
|
});
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_move_cursor() -> Result<()> {
|
fn test_move_cursor() -> Result<()> {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
|
let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
|
||||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||||
let (_, view) =
|
let (_, view) =
|
||||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||||
|
|
||||||
buffer.update(&mut app, |buffer, ctx| {
|
buffer.update(app, |buffer, ctx| {
|
||||||
buffer.edit(
|
buffer.edit(
|
||||||
vec![
|
vec![
|
||||||
Point::new(1, 0)..Point::new(1, 0),
|
Point::new(1, 0)..Point::new(1, 0),
|
||||||
|
@ -1475,7 +1484,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
view.update(&mut app, |view, ctx| {
|
view.update(app, |view, ctx| {
|
||||||
view.move_down(&(), ctx);
|
view.move_down(&(), ctx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.selections(ctx.app()),
|
view.selections(ctx.app()),
|
||||||
|
@ -1494,8 +1503,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_backspace() -> Result<()> {
|
fn test_backspace() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let buffer = app.add_model(|_| {
|
let buffer = app.add_model(|_| {
|
||||||
Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
|
Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
|
||||||
});
|
});
|
||||||
|
@ -1503,7 +1512,7 @@ mod tests {
|
||||||
let (_, view) =
|
let (_, view) =
|
||||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||||
|
|
||||||
view.update(&mut app, |view, ctx| -> Result<()> {
|
view.update(app, |view, ctx| {
|
||||||
view.select_ranges(
|
view.select_ranges(
|
||||||
&[
|
&[
|
||||||
// an empty selection - the preceding character is deleted
|
// an empty selection - the preceding character is deleted
|
||||||
|
@ -1514,17 +1523,15 @@ mod tests {
|
||||||
DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
|
DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
|
||||||
],
|
],
|
||||||
ctx,
|
ctx,
|
||||||
)?;
|
)
|
||||||
|
.unwrap();
|
||||||
view.backspace(&(), ctx);
|
view.backspace(&(), ctx);
|
||||||
Ok(())
|
});
|
||||||
})?;
|
|
||||||
|
|
||||||
buffer.read(&mut app, |buffer, _| -> Result<()> {
|
assert_eq!(
|
||||||
assert_eq!(buffer.text(), "oe two three\nfou five six\nseven ten\n");
|
buffer.read(app).text(),
|
||||||
Ok(())
|
"oe two three\nfou five six\nseven ten\n"
|
||||||
})?;
|
);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct FoldMap {
|
||||||
|
|
||||||
impl FoldMap {
|
impl FoldMap {
|
||||||
pub fn new(buffer: ModelHandle<Buffer>, app: &AppContext) -> Self {
|
pub fn new(buffer: ModelHandle<Buffer>, app: &AppContext) -> Self {
|
||||||
let text_summary = buffer.as_ref(app).text_summary();
|
let text_summary = buffer.read(app).text_summary();
|
||||||
Self {
|
Self {
|
||||||
buffer,
|
buffer,
|
||||||
folds: Vec::new(),
|
folds: Vec::new(),
|
||||||
|
@ -72,7 +72,7 @@ impl FoldMap {
|
||||||
let offset = self.to_display_offset(point, app)?;
|
let offset = self.to_display_offset(point, app)?;
|
||||||
let mut cursor = self.transforms.cursor();
|
let mut cursor = self.transforms.cursor();
|
||||||
cursor.seek(&offset, SeekBias::Right);
|
cursor.seek(&offset, SeekBias::Right);
|
||||||
let buffer = self.buffer.as_ref(app);
|
let buffer = self.buffer.read(app);
|
||||||
Ok(Chars {
|
Ok(Chars {
|
||||||
cursor,
|
cursor,
|
||||||
offset: offset.0,
|
offset: offset.0,
|
||||||
|
@ -95,7 +95,7 @@ impl FoldMap {
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
let buffer = self.buffer.as_ref(app);
|
let buffer = self.buffer.read(app);
|
||||||
for range in ranges.into_iter() {
|
for range in ranges.into_iter() {
|
||||||
let start = range.start.to_offset(buffer)?;
|
let start = range.start.to_offset(buffer)?;
|
||||||
let end = range.end.to_offset(buffer)?;
|
let end = range.end.to_offset(buffer)?;
|
||||||
|
@ -124,7 +124,7 @@ impl FoldMap {
|
||||||
ranges: impl IntoIterator<Item = Range<T>>,
|
ranges: impl IntoIterator<Item = Range<T>>,
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let buffer = self.buffer.as_ref(app);
|
let buffer = self.buffer.read(app);
|
||||||
|
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
for range in ranges.into_iter() {
|
for range in ranges.into_iter() {
|
||||||
|
@ -184,7 +184,7 @@ impl FoldMap {
|
||||||
.ok_or_else(|| anyhow!("display point {:?} is out of range", point))?;
|
.ok_or_else(|| anyhow!("display point {:?} is out of range", point))?;
|
||||||
assert!(transform.display_text.is_none());
|
assert!(transform.display_text.is_none());
|
||||||
let end_buffer_offset =
|
let end_buffer_offset =
|
||||||
(cursor.start().buffer.lines + overshoot).to_offset(self.buffer.as_ref(app))?;
|
(cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(app))?;
|
||||||
offset += end_buffer_offset - cursor.start().buffer.chars;
|
offset += end_buffer_offset - cursor.start().buffer.chars;
|
||||||
}
|
}
|
||||||
Ok(DisplayOffset(offset))
|
Ok(DisplayOffset(offset))
|
||||||
|
@ -208,7 +208,7 @@ impl FoldMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_edits(&mut self, edits: &[Edit], app: &AppContext) -> Result<()> {
|
pub fn apply_edits(&mut self, edits: &[Edit], app: &AppContext) -> Result<()> {
|
||||||
let buffer = self.buffer.as_ref(app);
|
let buffer = self.buffer.read(app);
|
||||||
let mut edits = edits.iter().cloned().peekable();
|
let mut edits = edits.iter().cloned().peekable();
|
||||||
|
|
||||||
let mut new_transforms = SumTree::new();
|
let mut new_transforms = SumTree::new();
|
||||||
|
@ -469,115 +469,106 @@ mod tests {
|
||||||
use gpui::App;
|
use gpui::App;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_folds() -> Result<()> {
|
fn test_basic_folds() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
|
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
|
||||||
let mut map = app.read(|app| FoldMap::new(buffer.clone(), app));
|
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
|
||||||
|
|
||||||
app.read(|app| {
|
map.fold(
|
||||||
map.fold(
|
vec![
|
||||||
vec![
|
Point::new(0, 2)..Point::new(2, 2),
|
||||||
Point::new(0, 2)..Point::new(2, 2),
|
Point::new(2, 4)..Point::new(4, 1),
|
||||||
Point::new(2, 4)..Point::new(4, 1),
|
],
|
||||||
],
|
app.as_ref(),
|
||||||
app,
|
)
|
||||||
)?;
|
.unwrap();
|
||||||
assert_eq!(map.text(app), "aa…cc…eeeee");
|
assert_eq!(map.text(app.as_ref()), "aa…cc…eeeee");
|
||||||
Ok::<(), anyhow::Error>(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let edits = buffer.update(&mut app, |buffer, ctx| {
|
let edits = buffer.update(app, |buffer, ctx| {
|
||||||
let start_version = buffer.version.clone();
|
let start_version = buffer.version.clone();
|
||||||
buffer.edit(
|
buffer
|
||||||
vec![
|
.edit(
|
||||||
Point::new(0, 0)..Point::new(0, 1),
|
vec![
|
||||||
Point::new(2, 3)..Point::new(2, 3),
|
Point::new(0, 0)..Point::new(0, 1),
|
||||||
],
|
Point::new(2, 3)..Point::new(2, 3),
|
||||||
"123",
|
],
|
||||||
Some(ctx),
|
"123",
|
||||||
)?;
|
Some(ctx),
|
||||||
Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
|
)
|
||||||
})?;
|
.unwrap();
|
||||||
|
buffer.edits_since(start_version).collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
app.read(|app| {
|
map.apply_edits(&edits, app.as_ref()).unwrap();
|
||||||
map.apply_edits(&edits, app)?;
|
assert_eq!(map.text(app.as_ref()), "123a…c123c…eeeee");
|
||||||
assert_eq!(map.text(app), "123a…c123c…eeeee");
|
|
||||||
Ok::<(), anyhow::Error>(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let edits = buffer.update(&mut app, |buffer, ctx| {
|
let edits = buffer.update(app, |buffer, ctx| {
|
||||||
let start_version = buffer.version.clone();
|
let start_version = buffer.version.clone();
|
||||||
buffer.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))?;
|
buffer
|
||||||
Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
|
.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))
|
||||||
})?;
|
.unwrap();
|
||||||
|
buffer.edits_since(start_version).collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
app.read(|app| {
|
map.apply_edits(&edits, app.as_ref()).unwrap();
|
||||||
map.apply_edits(&edits, app)?;
|
assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
|
||||||
assert_eq!(map.text(app), "123a…c123456eee");
|
|
||||||
|
|
||||||
map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app)?;
|
map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app.as_ref())
|
||||||
assert_eq!(map.text(app), "123aaaaa\nbbbbbb\nccc123456eee");
|
.unwrap();
|
||||||
|
assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
#[test]
|
||||||
})
|
fn test_overlapping_folds() {
|
||||||
|
App::test((), |app| {
|
||||||
|
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
|
||||||
|
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
|
||||||
|
map.fold(
|
||||||
|
vec![
|
||||||
|
Point::new(0, 2)..Point::new(2, 2),
|
||||||
|
Point::new(0, 4)..Point::new(1, 0),
|
||||||
|
Point::new(1, 2)..Point::new(3, 2),
|
||||||
|
Point::new(3, 1)..Point::new(4, 1),
|
||||||
|
],
|
||||||
|
app.as_ref(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(map.text(app.as_ref()), "aa…eeeee");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_overlapping_folds() -> Result<()> {
|
fn test_merging_folds_via_edit() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
|
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
|
||||||
app.read(|app| {
|
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
|
||||||
let mut map = FoldMap::new(buffer.clone(), app);
|
|
||||||
map.fold(
|
|
||||||
vec![
|
|
||||||
Point::new(0, 2)..Point::new(2, 2),
|
|
||||||
Point::new(0, 4)..Point::new(1, 0),
|
|
||||||
Point::new(1, 2)..Point::new(3, 2),
|
|
||||||
Point::new(3, 1)..Point::new(4, 1),
|
|
||||||
],
|
|
||||||
app,
|
|
||||||
)?;
|
|
||||||
assert_eq!(map.text(app), "aa…eeeee");
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
map.fold(
|
||||||
fn test_merging_folds_via_edit() -> Result<()> {
|
vec![
|
||||||
App::test((), |mut app| async move {
|
Point::new(0, 2)..Point::new(2, 2),
|
||||||
let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
|
Point::new(3, 1)..Point::new(4, 1),
|
||||||
let mut map = app.read(|app| FoldMap::new(buffer.clone(), app));
|
],
|
||||||
|
app.as_ref(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee");
|
||||||
|
|
||||||
app.read(|app| {
|
let edits = buffer.update(app, |buffer, ctx| {
|
||||||
map.fold(
|
|
||||||
vec![
|
|
||||||
Point::new(0, 2)..Point::new(2, 2),
|
|
||||||
Point::new(3, 1)..Point::new(4, 1),
|
|
||||||
],
|
|
||||||
app,
|
|
||||||
)?;
|
|
||||||
assert_eq!(map.text(app), "aa…cccc\nd…eeeee");
|
|
||||||
Ok::<(), anyhow::Error>(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let edits = buffer.update(&mut app, |buffer, ctx| {
|
|
||||||
let start_version = buffer.version.clone();
|
let start_version = buffer.version.clone();
|
||||||
buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))?;
|
buffer
|
||||||
Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
|
.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))
|
||||||
})?;
|
.unwrap();
|
||||||
|
buffer.edits_since(start_version).collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
app.read(|app| {
|
map.apply_edits(&edits, app.as_ref()).unwrap();
|
||||||
map.apply_edits(&edits, app)?;
|
assert_eq!(map.text(app.as_ref()), "aa…eeeee");
|
||||||
assert_eq!(map.text(app), "aa…eeeee");
|
});
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_random_folds() -> Result<()> {
|
fn test_random_folds() {
|
||||||
use crate::editor::ToPoint;
|
use crate::editor::ToPoint;
|
||||||
use crate::util::RandomCharIter;
|
use crate::util::RandomCharIter;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
@ -597,16 +588,16 @@ mod tests {
|
||||||
println!("{:?}", seed);
|
println!("{:?}", seed);
|
||||||
let mut rng = StdRng::seed_from_u64(seed);
|
let mut rng = StdRng::seed_from_u64(seed);
|
||||||
|
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let buffer = app.add_model(|_| {
|
let buffer = app.add_model(|_| {
|
||||||
let len = rng.gen_range(0..10);
|
let len = rng.gen_range(0..10);
|
||||||
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
|
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
|
||||||
Buffer::new(0, text)
|
Buffer::new(0, text)
|
||||||
});
|
});
|
||||||
let mut map = app.read(|app| FoldMap::new(buffer.clone(), app));
|
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
|
||||||
|
|
||||||
app.read(|app| {
|
{
|
||||||
let buffer = buffer.as_ref(app);
|
let buffer = buffer.read(app);
|
||||||
|
|
||||||
let fold_count = rng.gen_range(0..10);
|
let fold_count = rng.gen_range(0..10);
|
||||||
let mut fold_ranges: Vec<Range<usize>> = Vec::new();
|
let mut fold_ranges: Vec<Range<usize>> = Vec::new();
|
||||||
|
@ -616,93 +607,83 @@ mod tests {
|
||||||
fold_ranges.push(start..end);
|
fold_ranges.push(start..end);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.fold(fold_ranges, app)?;
|
map.fold(fold_ranges, app.as_ref()).unwrap();
|
||||||
|
|
||||||
let mut expected_text = buffer.text();
|
let mut expected_text = buffer.text();
|
||||||
for fold_range in map.merged_fold_ranges(app).into_iter().rev() {
|
for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
|
||||||
expected_text.replace_range(fold_range.start..fold_range.end, "…");
|
expected_text.replace_range(fold_range.start..fold_range.end, "…");
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(map.text(app), expected_text);
|
assert_eq!(map.text(app.as_ref()), expected_text);
|
||||||
|
|
||||||
for fold_range in map.merged_fold_ranges(app) {
|
for fold_range in map.merged_fold_ranges(app.as_ref()) {
|
||||||
let display_point =
|
let display_point =
|
||||||
map.to_display_point(fold_range.start.to_point(buffer).unwrap());
|
map.to_display_point(fold_range.start.to_point(buffer).unwrap());
|
||||||
assert!(map.is_line_folded(display_point.row()));
|
assert!(map.is_line_folded(display_point.row()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok::<(), anyhow::Error>(())
|
let edits = buffer.update(app, |buffer, ctx| {
|
||||||
})?;
|
|
||||||
|
|
||||||
let edits = buffer.update(&mut app, |buffer, ctx| {
|
|
||||||
let start_version = buffer.version.clone();
|
let start_version = buffer.version.clone();
|
||||||
let edit_count = rng.gen_range(1..10);
|
let edit_count = rng.gen_range(1..10);
|
||||||
buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
|
buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
|
||||||
Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::<Vec<_>>())
|
buffer.edits_since(start_version).collect::<Vec<_>>()
|
||||||
})?;
|
});
|
||||||
|
|
||||||
app.read(|app| {
|
map.apply_edits(&edits, app.as_ref()).unwrap();
|
||||||
map.apply_edits(&edits, app)?;
|
|
||||||
|
|
||||||
let buffer = map.buffer.as_ref(app);
|
let buffer = map.buffer.read(app);
|
||||||
let mut expected_text = buffer.text();
|
let mut expected_text = buffer.text();
|
||||||
let mut expected_buffer_rows = Vec::new();
|
let mut expected_buffer_rows = Vec::new();
|
||||||
let mut next_row = buffer.max_point().row;
|
let mut next_row = buffer.max_point().row;
|
||||||
for fold_range in map.merged_fold_ranges(app).into_iter().rev() {
|
for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
|
||||||
let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
|
let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
|
||||||
let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
|
let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
|
||||||
expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
|
expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
|
||||||
next_row = fold_start.row;
|
next_row = fold_start.row;
|
||||||
|
|
||||||
expected_text.replace_range(fold_range.start..fold_range.end, "…");
|
expected_text.replace_range(fold_range.start..fold_range.end, "…");
|
||||||
}
|
}
|
||||||
expected_buffer_rows.extend((0..=next_row).rev());
|
expected_buffer_rows.extend((0..=next_row).rev());
|
||||||
expected_buffer_rows.reverse();
|
expected_buffer_rows.reverse();
|
||||||
|
|
||||||
assert_eq!(map.text(app), expected_text);
|
assert_eq!(map.text(app.as_ref()), expected_text);
|
||||||
|
|
||||||
for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
|
for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
|
||||||
let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
|
let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
|
map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
|
||||||
expected_buffer_rows[idx..],
|
expected_buffer_rows[idx..],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
Ok::<(), anyhow::Error>(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok::<(), anyhow::Error>(())
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_buffer_rows() -> Result<()> {
|
fn test_buffer_rows() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let text = sample_text(6, 6) + "\n";
|
let text = sample_text(6, 6) + "\n";
|
||||||
let buffer = app.add_model(|_| Buffer::new(0, text));
|
let buffer = app.add_model(|_| Buffer::new(0, text));
|
||||||
|
|
||||||
app.read(|app| {
|
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
|
||||||
let mut map = FoldMap::new(buffer.clone(), app);
|
|
||||||
|
|
||||||
map.fold(
|
map.fold(
|
||||||
vec![
|
vec![
|
||||||
Point::new(0, 2)..Point::new(2, 2),
|
Point::new(0, 2)..Point::new(2, 2),
|
||||||
Point::new(3, 1)..Point::new(4, 1),
|
Point::new(3, 1)..Point::new(4, 1),
|
||||||
],
|
],
|
||||||
app,
|
app.as_ref(),
|
||||||
)?;
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(map.text(app), "aa…cccc\nd…eeeee\nffffff\n");
|
assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee\nffffff\n");
|
||||||
assert_eq!(map.buffer_rows(0)?.collect::<Vec<_>>(), vec![0, 3, 5, 6]);
|
assert_eq!(
|
||||||
assert_eq!(map.buffer_rows(3)?.collect::<Vec<_>>(), vec![6]);
|
map.buffer_rows(0).unwrap().collect::<Vec<_>>(),
|
||||||
|
vec![0, 3, 5, 6]
|
||||||
Ok(())
|
);
|
||||||
})
|
assert_eq!(map.buffer_rows(3).unwrap().collect::<Vec<_>>(), vec![6]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FoldMap {
|
impl FoldMap {
|
||||||
|
@ -713,7 +694,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merged_fold_ranges(&self, app: &AppContext) -> Vec<Range<usize>> {
|
fn merged_fold_ranges(&self, app: &AppContext) -> Vec<Range<usize>> {
|
||||||
let buffer = self.buffer.as_ref(app);
|
let buffer = self.buffer.read(app);
|
||||||
let mut fold_ranges = self
|
let mut fold_ranges = self
|
||||||
.folds
|
.folds
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -108,7 +108,7 @@ impl DisplayMap {
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
) -> Result<Anchor> {
|
) -> Result<Anchor> {
|
||||||
self.buffer
|
self.buffer
|
||||||
.as_ref(app)
|
.read(app)
|
||||||
.anchor_before(point.to_buffer_point(self, bias, app)?)
|
.anchor_before(point.to_buffer_point(self, bias, app)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ impl DisplayMap {
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
) -> Result<Anchor> {
|
) -> Result<Anchor> {
|
||||||
self.buffer
|
self.buffer
|
||||||
.as_ref(app)
|
.read(app)
|
||||||
.anchor_after(point.to_buffer_point(self, bias, app)?)
|
.anchor_after(point.to_buffer_point(self, bias, app)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ impl Point {
|
||||||
|
|
||||||
impl Anchor {
|
impl Anchor {
|
||||||
pub fn to_display_point(&self, map: &DisplayMap, app: &AppContext) -> Result<DisplayPoint> {
|
pub fn to_display_point(&self, map: &DisplayMap, app: &AppContext) -> Result<DisplayPoint> {
|
||||||
self.to_point(map.buffer.as_ref(app))?
|
self.to_point(map.buffer.read(app))?
|
||||||
.to_display_point(map, app)
|
.to_display_point(map, app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,52 +292,51 @@ pub fn collapse_tabs(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test::*;
|
use crate::test::*;
|
||||||
use anyhow::Error;
|
|
||||||
use gpui::App;
|
use gpui::App;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chars_at() -> Result<()> {
|
fn test_chars_at() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let text = sample_text(6, 6);
|
let text = sample_text(6, 6);
|
||||||
let buffer = app.add_model(|_| Buffer::new(0, text));
|
let buffer = app.add_model(|_| Buffer::new(0, text));
|
||||||
let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
|
let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
|
||||||
buffer.update(&mut app, |buffer, ctx| {
|
buffer
|
||||||
buffer.edit(
|
.update(app, |buffer, ctx| {
|
||||||
vec![
|
buffer.edit(
|
||||||
Point::new(1, 0)..Point::new(1, 0),
|
vec![
|
||||||
Point::new(1, 1)..Point::new(1, 1),
|
Point::new(1, 0)..Point::new(1, 0),
|
||||||
Point::new(2, 1)..Point::new(2, 1),
|
Point::new(1, 1)..Point::new(1, 1),
|
||||||
],
|
Point::new(2, 1)..Point::new(2, 1),
|
||||||
"\t",
|
],
|
||||||
Some(ctx),
|
"\t",
|
||||||
)
|
Some(ctx),
|
||||||
})?;
|
)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
map.read(&app, |map, ctx| {
|
let map = map.read(app);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.chars_at(DisplayPoint::new(1, 0), ctx)?
|
map.chars_at(DisplayPoint::new(1, 0), app.as_ref())
|
||||||
.take(10)
|
.unwrap()
|
||||||
.collect::<String>(),
|
.take(10)
|
||||||
" b bb"
|
.collect::<String>(),
|
||||||
);
|
" b bb"
|
||||||
assert_eq!(
|
);
|
||||||
map.chars_at(DisplayPoint::new(1, 2), ctx)?
|
assert_eq!(
|
||||||
.take(10)
|
map.chars_at(DisplayPoint::new(1, 2), app.as_ref())
|
||||||
.collect::<String>(),
|
.unwrap()
|
||||||
" b bbbb"
|
.take(10)
|
||||||
);
|
.collect::<String>(),
|
||||||
assert_eq!(
|
" b bbbb"
|
||||||
map.chars_at(DisplayPoint::new(1, 6), ctx)?
|
);
|
||||||
.take(13)
|
assert_eq!(
|
||||||
.collect::<String>(),
|
map.chars_at(DisplayPoint::new(1, 6), app.as_ref())
|
||||||
" bbbbb\nc c"
|
.unwrap()
|
||||||
);
|
.take(13)
|
||||||
|
.collect::<String>(),
|
||||||
Ok::<(), Error>(())
|
" bbbbb\nc c"
|
||||||
})?;
|
);
|
||||||
|
});
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -364,14 +363,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_max_point() -> Result<()> {
|
fn test_max_point() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb"));
|
let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb"));
|
||||||
let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
|
let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
|
||||||
map.read(&app, |map, app| {
|
assert_eq!(
|
||||||
assert_eq!(map.max_point(app), DisplayPoint::new(1, 11))
|
map.read(app).max_point(app.as_ref()),
|
||||||
});
|
DisplayPoint::new(1, 11)
|
||||||
Ok(())
|
)
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ use gpui::{
|
||||||
fonts::{Properties, Weight},
|
fonts::{Properties, Weight},
|
||||||
geometry::vector::vec2f,
|
geometry::vector::vec2f,
|
||||||
keymap::{self, Binding},
|
keymap::{self, Binding},
|
||||||
App, AppContext, Axis, Border, Entity, ModelHandle, View, ViewContext, ViewHandle,
|
AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext,
|
||||||
WeakViewHandle,
|
ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ pub struct FileFinder {
|
||||||
list_state: UniformListState,
|
list_state: UniformListState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(app: &mut App) {
|
pub fn init(app: &mut MutableAppContext) {
|
||||||
app.add_action("file_finder:toggle", FileFinder::toggle);
|
app.add_action("file_finder:toggle", FileFinder::toggle);
|
||||||
app.add_action("file_finder:confirm", FileFinder::confirm);
|
app.add_action("file_finder:confirm", FileFinder::confirm);
|
||||||
app.add_action("file_finder:select", FileFinder::select);
|
app.add_action("file_finder:select", FileFinder::select);
|
||||||
|
@ -114,7 +114,7 @@ impl FileFinder {
|
||||||
self.matches.len(),
|
self.matches.len(),
|
||||||
move |mut range, items, app| {
|
move |mut range, items, app| {
|
||||||
let finder = handle.upgrade(app).unwrap();
|
let finder = handle.upgrade(app).unwrap();
|
||||||
let finder = finder.as_ref(app);
|
let finder = finder.read(app);
|
||||||
let start = range.start;
|
let start = range.start;
|
||||||
range.end = cmp::min(range.end, finder.matches.len());
|
range.end = cmp::min(range.end, finder.matches.len());
|
||||||
items.extend(finder.matches[range].iter().enumerate().filter_map(
|
items.extend(finder.matches[range].iter().enumerate().filter_map(
|
||||||
|
@ -287,7 +287,7 @@ impl FileFinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_updated(&mut self, _: ModelHandle<Workspace>, ctx: &mut ViewContext<Self>) {
|
fn workspace_updated(&mut self, _: ModelHandle<Workspace>, ctx: &mut ViewContext<Self>) {
|
||||||
self.spawn_search(self.query_buffer.as_ref(ctx).text(ctx.app()), ctx);
|
self.spawn_search(self.query_buffer.read(ctx).text(ctx.app()), ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_query_buffer_event(
|
fn on_query_buffer_event(
|
||||||
|
@ -299,7 +299,7 @@ impl FileFinder {
|
||||||
use buffer_view::Event::*;
|
use buffer_view::Event::*;
|
||||||
match event {
|
match event {
|
||||||
Edited => {
|
Edited => {
|
||||||
let query = self.query_buffer.as_ref(ctx).text(ctx.app());
|
let query = self.query_buffer.read(ctx).text(ctx.app());
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
self.latest_search_id = util::post_inc(&mut self.search_count);
|
self.latest_search_id = util::post_inc(&mut self.search_count);
|
||||||
self.matches.clear();
|
self.matches.clear();
|
||||||
|
@ -371,18 +371,18 @@ impl FileFinder {
|
||||||
|
|
||||||
fn worktree<'a>(&'a self, tree_id: usize, app: &'a AppContext) -> Option<&'a Worktree> {
|
fn worktree<'a>(&'a self, tree_id: usize, app: &'a AppContext) -> Option<&'a Worktree> {
|
||||||
self.workspace
|
self.workspace
|
||||||
.as_ref(app)
|
.read(app)
|
||||||
.worktrees()
|
.worktrees()
|
||||||
.get(&tree_id)
|
.get(&tree_id)
|
||||||
.map(|worktree| worktree.as_ref(app))
|
.map(|worktree| worktree.read(app))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn worktrees(&self, app: &AppContext) -> Vec<Worktree> {
|
fn worktrees(&self, app: &AppContext) -> Vec<Worktree> {
|
||||||
self.workspace
|
self.workspace
|
||||||
.as_ref(app)
|
.read(app)
|
||||||
.worktrees()
|
.worktrees()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|worktree| worktree.as_ref(app).clone())
|
.map(|worktree| worktree.read(app).clone())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -394,20 +394,25 @@ mod tests {
|
||||||
editor, settings,
|
editor, settings,
|
||||||
workspace::{Workspace, WorkspaceView},
|
workspace::{Workspace, WorkspaceView},
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
|
||||||
use gpui::App;
|
use gpui::App;
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_matching_paths() -> Result<()> {
|
fn test_matching_paths() {
|
||||||
App::test((), |mut app| async move {
|
App::test_async((), |mut app| async move {
|
||||||
let tmp_dir = TempDir::new("example")?;
|
let tmp_dir = TempDir::new("example").unwrap();
|
||||||
fs::create_dir(tmp_dir.path().join("a")).await?;
|
fs::create_dir(tmp_dir.path().join("a")).await.unwrap();
|
||||||
fs::write(tmp_dir.path().join("a/banana"), "banana").await?;
|
fs::write(tmp_dir.path().join("a/banana"), "banana")
|
||||||
fs::write(tmp_dir.path().join("a/bandana"), "bandana").await?;
|
.await
|
||||||
super::init(&mut app);
|
.unwrap();
|
||||||
editor::init(&mut app);
|
fs::write(tmp_dir.path().join("a/bandana"), "bandana")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
app.update(|ctx| {
|
||||||
|
super::init(ctx);
|
||||||
|
editor::init(ctx);
|
||||||
|
});
|
||||||
|
|
||||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||||
let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx));
|
let workspace = app.add_model(|ctx| Workspace::new(vec![tmp_dir.path().into()], ctx));
|
||||||
|
@ -420,16 +425,17 @@ mod tests {
|
||||||
"file_finder:toggle".into(),
|
"file_finder:toggle".into(),
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
let (finder, query_buffer) = workspace_view.read(&app, |view, ctx| {
|
|
||||||
let finder = view
|
let finder = app.read(|ctx| {
|
||||||
|
workspace_view
|
||||||
|
.read(ctx)
|
||||||
.modal()
|
.modal()
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.downcast::<FileFinder>()
|
.downcast::<FileFinder>()
|
||||||
.unwrap();
|
.unwrap()
|
||||||
let query_buffer = finder.as_ref(ctx).query_buffer.clone();
|
|
||||||
(finder, query_buffer)
|
|
||||||
});
|
});
|
||||||
|
let query_buffer = app.read(|ctx| finder.read(ctx).query_buffer.clone());
|
||||||
|
|
||||||
let chain = vec![finder.id(), query_buffer.id()];
|
let chain = vec![finder.id(), query_buffer.id()];
|
||||||
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
|
app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
|
||||||
|
@ -452,7 +458,7 @@ mod tests {
|
||||||
// (),
|
// (),
|
||||||
// );
|
// );
|
||||||
// app.finish_pending_tasks().await; // Load Buffer and open BufferView.
|
// app.finish_pending_tasks().await; // Load Buffer and open BufferView.
|
||||||
// let active_pane = workspace_view.read(&app, |view, _| view.active_pane().clone());
|
// let active_pane = workspace_view.as_ref(app).active_pane().clone();
|
||||||
// assert_eq!(
|
// assert_eq!(
|
||||||
// active_pane.state(&app),
|
// active_pane.state(&app),
|
||||||
// pane::State {
|
// pane::State {
|
||||||
|
@ -462,7 +468,6 @@ mod tests {
|
||||||
// }]
|
// }]
|
||||||
// }
|
// }
|
||||||
// );
|
// );
|
||||||
Ok(())
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use fs::OpenOptions;
|
use fs::OpenOptions;
|
||||||
use gpui::platform::{current as platform, PathPromptOptions, Runner as _};
|
use gpui::PathPromptOptions;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use simplelog::SimpleLogger;
|
use simplelog::SimpleLogger;
|
||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
@ -13,55 +13,48 @@ fn main() {
|
||||||
|
|
||||||
let app = gpui::App::new(assets::Assets).unwrap();
|
let app = gpui::App::new(assets::Assets).unwrap();
|
||||||
let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap();
|
let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap();
|
||||||
|
app.set_menus(menus::MENUS);
|
||||||
platform::runner()
|
app.on_menu_command({
|
||||||
.set_menus(menus::MENUS)
|
let settings_rx = settings_rx.clone();
|
||||||
.on_menu_command({
|
move |command, ctx| match command {
|
||||||
let app = app.clone();
|
"app:open" => {
|
||||||
let settings_rx = settings_rx.clone();
|
if let Some(paths) = ctx.platform().prompt_for_paths(PathPromptOptions {
|
||||||
move |command| match command {
|
files: true,
|
||||||
"app:open" => {
|
directories: true,
|
||||||
if let Some(paths) = app.platform().prompt_for_paths(PathPromptOptions {
|
multiple: true,
|
||||||
files: true,
|
}) {
|
||||||
directories: true,
|
ctx.dispatch_global_action(
|
||||||
multiple: true,
|
|
||||||
}) {
|
|
||||||
app.dispatch_global_action(
|
|
||||||
"workspace:open_paths",
|
|
||||||
OpenParams {
|
|
||||||
paths,
|
|
||||||
settings: settings_rx.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => app.dispatch_global_action(command, ()),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_finish_launching({
|
|
||||||
let mut app = app.clone();
|
|
||||||
move || {
|
|
||||||
workspace::init(&mut app);
|
|
||||||
editor::init(&mut app);
|
|
||||||
file_finder::init(&mut app);
|
|
||||||
|
|
||||||
if stdout_is_a_pty() {
|
|
||||||
app.platform().activate(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let paths = collect_path_args();
|
|
||||||
if !paths.is_empty() {
|
|
||||||
app.dispatch_global_action(
|
|
||||||
"workspace:open_paths",
|
"workspace:open_paths",
|
||||||
OpenParams {
|
OpenParams {
|
||||||
paths,
|
paths,
|
||||||
settings: settings_rx,
|
settings: settings_rx.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
_ => ctx.dispatch_global_action(command, ()),
|
||||||
.run();
|
}
|
||||||
|
})
|
||||||
|
.run(move |ctx| {
|
||||||
|
workspace::init(ctx);
|
||||||
|
editor::init(ctx);
|
||||||
|
file_finder::init(ctx);
|
||||||
|
|
||||||
|
if stdout_is_a_pty() {
|
||||||
|
ctx.platform().activate(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let paths = collect_path_args();
|
||||||
|
if !paths.is_empty() {
|
||||||
|
ctx.dispatch_global_action(
|
||||||
|
"workspace:open_paths",
|
||||||
|
OpenParams {
|
||||||
|
paths,
|
||||||
|
settings: settings_rx,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
|
|
|
@ -9,10 +9,10 @@ pub use workspace::*;
|
||||||
pub use workspace_view::*;
|
pub use workspace_view::*;
|
||||||
|
|
||||||
use crate::{settings::Settings, watch};
|
use crate::{settings::Settings, watch};
|
||||||
use gpui::{App, MutableAppContext};
|
use gpui::MutableAppContext;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub fn init(app: &mut App) {
|
pub fn init(app: &mut MutableAppContext) {
|
||||||
app.add_global_action("workspace:open_paths", open_paths);
|
app.add_global_action("workspace:open_paths", open_paths);
|
||||||
app.add_global_action("app:quit", quit);
|
app.add_global_action("app:quit", quit);
|
||||||
pane::init(app);
|
pane::init(app);
|
||||||
|
@ -64,10 +64,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_open_paths_action() {
|
fn test_open_paths_action() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |app| {
|
||||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||||
|
|
||||||
init(&mut app);
|
init(app);
|
||||||
|
|
||||||
let dir = temp_tree(json!({
|
let dir = temp_tree(json!({
|
||||||
"a": {
|
"a": {
|
||||||
|
@ -94,7 +94,7 @@ mod tests {
|
||||||
settings: settings.clone(),
|
settings: settings.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(app.window_ids().len(), 1);
|
assert_eq!(app.window_ids().count(), 1);
|
||||||
|
|
||||||
app.dispatch_global_action(
|
app.dispatch_global_action(
|
||||||
"workspace:open_paths",
|
"workspace:open_paths",
|
||||||
|
@ -103,11 +103,19 @@ mod tests {
|
||||||
settings: settings.clone(),
|
settings: settings.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(app.window_ids().len(), 1);
|
assert_eq!(app.window_ids().count(), 1);
|
||||||
let workspace_view_1 = app.root_view::<WorkspaceView>(app.window_ids()[0]).unwrap();
|
let workspace_view_1 = app
|
||||||
workspace_view_1.read(&app, |view, app| {
|
.root_view::<WorkspaceView>(app.window_ids().next().unwrap())
|
||||||
assert_eq!(view.workspace.as_ref(app).worktrees().len(), 2);
|
.unwrap();
|
||||||
});
|
assert_eq!(
|
||||||
|
workspace_view_1
|
||||||
|
.read(app)
|
||||||
|
.workspace
|
||||||
|
.read(app)
|
||||||
|
.worktrees()
|
||||||
|
.len(),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
app.dispatch_global_action(
|
app.dispatch_global_action(
|
||||||
"workspace:open_paths",
|
"workspace:open_paths",
|
||||||
|
@ -119,7 +127,7 @@ mod tests {
|
||||||
settings: settings.clone(),
|
settings: settings.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(app.window_ids().len(), 2);
|
assert_eq!(app.window_ids().count(), 2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@ use gpui::{
|
||||||
elements::*,
|
elements::*,
|
||||||
geometry::{rect::RectF, vector::vec2f},
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
keymap::Binding,
|
keymap::Binding,
|
||||||
App, AppContext, Border, Entity, Quad, View, ViewContext,
|
AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext,
|
||||||
};
|
};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
|
||||||
pub fn init(app: &mut App) {
|
pub fn init(app: &mut MutableAppContext) {
|
||||||
app.add_action(
|
app.add_action(
|
||||||
"pane:activate_item",
|
"pane:activate_item",
|
||||||
|pane: &mut Pane, index: &usize, ctx| {
|
|pane: &mut Pane, index: &usize, ctx| {
|
||||||
|
|
|
@ -101,7 +101,7 @@ impl Workspace {
|
||||||
pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool {
|
pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool {
|
||||||
self.worktrees
|
self.worktrees
|
||||||
.iter()
|
.iter()
|
||||||
.any(|worktree| worktree.as_ref(app).contains_path(path))
|
.any(|worktree| worktree.read(app).contains_path(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_paths(&mut self, paths: &[PathBuf], ctx: &mut ModelContext<Self>) {
|
pub fn open_paths(&mut self, paths: &[PathBuf], ctx: &mut ModelContext<Self>) {
|
||||||
|
@ -112,7 +112,7 @@ impl Workspace {
|
||||||
|
|
||||||
pub fn open_path<'a>(&'a mut self, path: PathBuf, ctx: &mut ModelContext<Self>) {
|
pub fn open_path<'a>(&'a mut self, path: PathBuf, ctx: &mut ModelContext<Self>) {
|
||||||
for tree in self.worktrees.iter() {
|
for tree in self.worktrees.iter() {
|
||||||
if tree.as_ref(ctx).contains_path(&path) {
|
if tree.read(ctx).contains_path(&path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,23 +200,22 @@ impl Entity for Workspace {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub trait WorkspaceHandle {
|
pub trait WorkspaceHandle {
|
||||||
fn file_entries(&self, app: &gpui::App) -> Vec<(usize, usize)>;
|
fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl WorkspaceHandle for ModelHandle<Workspace> {
|
impl WorkspaceHandle for ModelHandle<Workspace> {
|
||||||
fn file_entries(&self, app: &gpui::App) -> Vec<(usize, usize)> {
|
fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)> {
|
||||||
self.read(&app, |w, app| {
|
self.read(app)
|
||||||
w.worktrees()
|
.worktrees()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|tree| {
|
.flat_map(|tree| {
|
||||||
let tree_id = tree.id();
|
let tree_id = tree.id();
|
||||||
tree.as_ref(app)
|
tree.read(app)
|
||||||
.files()
|
.files()
|
||||||
.map(move |file| (tree_id, file.entry_id))
|
.map(move |file| (tree_id, file.entry_id))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,8 +227,8 @@ mod tests {
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_open_entry() -> Result<(), Arc<anyhow::Error>> {
|
fn test_open_entry() {
|
||||||
App::test((), |mut app| async move {
|
App::test_async((), |mut app| async move {
|
||||||
let dir = temp_tree(json!({
|
let dir = temp_tree(json!({
|
||||||
"a": {
|
"a": {
|
||||||
"aa": "aa contents",
|
"aa": "aa contents",
|
||||||
|
@ -241,11 +240,9 @@ mod tests {
|
||||||
app.finish_pending_tasks().await; // Open and populate worktree.
|
app.finish_pending_tasks().await; // Open and populate worktree.
|
||||||
|
|
||||||
// Get the first file entry.
|
// Get the first file entry.
|
||||||
let entry = workspace.read(&app, |w, app| {
|
let tree = app.read(|ctx| workspace.read(ctx).worktrees.iter().next().unwrap().clone());
|
||||||
let tree = w.worktrees.iter().next().unwrap();
|
let entry_id = app.read(|ctx| tree.read(ctx).files().next().unwrap().entry_id);
|
||||||
let entry_id = tree.as_ref(app).files().next().unwrap().entry_id;
|
let entry = (tree.id(), entry_id);
|
||||||
(tree.id(), entry_id)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open the same entry twice before it finishes loading.
|
// Open the same entry twice before it finishes loading.
|
||||||
let (future_1, future_2) = workspace.update(&mut app, |w, app| {
|
let (future_1, future_2) = workspace.update(&mut app, |w, app| {
|
||||||
|
@ -255,18 +252,17 @@ mod tests {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let handle_1 = future_1.await?;
|
let handle_1 = future_1.await.unwrap();
|
||||||
let handle_2 = future_2.await?;
|
let handle_2 = future_2.await.unwrap();
|
||||||
assert_eq!(handle_1.id(), handle_2.id());
|
assert_eq!(handle_1.id(), handle_2.id());
|
||||||
|
|
||||||
// Open the same entry again now that it has loaded
|
// Open the same entry again now that it has loaded
|
||||||
let handle_3 = workspace
|
let handle_3 = workspace
|
||||||
.update(&mut app, |w, app| w.open_entry(entry, app).unwrap())
|
.update(&mut app, |w, app| w.open_entry(entry, app).unwrap())
|
||||||
.await?;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(handle_3.id(), handle_1.id());
|
assert_eq!(handle_3.id(), handle_1.id());
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@ use super::{pane, Pane, PaneGroup, SplitDirection, Workspace};
|
||||||
use crate::{settings::Settings, watch};
|
use crate::{settings::Settings, watch};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, App,
|
color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
|
||||||
AppContext, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
|
Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
|
||||||
};
|
};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::{collections::HashSet, path::PathBuf};
|
use std::{collections::HashSet, path::PathBuf};
|
||||||
|
|
||||||
pub fn init(app: &mut App) {
|
pub fn init(app: &mut MutableAppContext) {
|
||||||
app.add_action("workspace:save", WorkspaceView::save_active_item);
|
app.add_action("workspace:save", WorkspaceView::save_active_item);
|
||||||
app.add_action("workspace:debug_elements", WorkspaceView::debug_elements);
|
app.add_action("workspace:debug_elements", WorkspaceView::debug_elements);
|
||||||
app.add_bindings(vec![
|
app.add_bindings(vec![
|
||||||
|
@ -54,11 +54,11 @@ pub trait ItemViewHandle: Send + Sync {
|
||||||
|
|
||||||
impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
|
impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
|
||||||
fn title(&self, app: &AppContext) -> String {
|
fn title(&self, app: &AppContext) -> String {
|
||||||
self.as_ref(app).title(app)
|
self.read(app).title(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> {
|
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> {
|
||||||
self.as_ref(app).entry_id(app)
|
self.read(app).entry_id(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn boxed_clone(&self) -> Box<dyn ItemViewHandle> {
|
fn boxed_clone(&self) -> Box<dyn ItemViewHandle> {
|
||||||
|
@ -93,7 +93,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_dirty(&self, ctx: &AppContext) -> bool {
|
fn is_dirty(&self, ctx: &AppContext) -> bool {
|
||||||
self.as_ref(ctx).is_dirty(ctx)
|
self.read(ctx).is_dirty(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> usize {
|
fn id(&self) -> usize {
|
||||||
|
@ -154,7 +154,7 @@ impl WorkspaceView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_paths(&self, paths: &[PathBuf], app: &AppContext) -> bool {
|
pub fn contains_paths(&self, paths: &[PathBuf], app: &AppContext) -> bool {
|
||||||
self.workspace.as_ref(app).contains_paths(paths, app)
|
self.workspace.read(app).contains_paths(paths, app)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_paths(&self, paths: &[PathBuf], app: &mut MutableAppContext) {
|
pub fn open_paths(&self, paths: &[PathBuf], app: &mut MutableAppContext) {
|
||||||
|
@ -228,8 +228,8 @@ impl WorkspaceView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_example_entry(&mut self, ctx: &mut ViewContext<Self>) {
|
pub fn open_example_entry(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
if let Some(tree) = self.workspace.as_ref(ctx).worktrees().iter().next() {
|
if let Some(tree) = self.workspace.read(ctx).worktrees().iter().next() {
|
||||||
if let Some(file) = tree.as_ref(ctx).files().next() {
|
if let Some(file) = tree.read(ctx).files().next() {
|
||||||
info!("open_entry ({}, {})", tree.id(), file.entry_id);
|
info!("open_entry ({}, {})", tree.id(), file.entry_id);
|
||||||
self.open_entry((tree.id(), file.entry_id), ctx);
|
self.open_entry((tree.id(), file.entry_id), ctx);
|
||||||
} else {
|
} else {
|
||||||
|
@ -322,7 +322,7 @@ impl WorkspaceView {
|
||||||
) -> ViewHandle<Pane> {
|
) -> ViewHandle<Pane> {
|
||||||
let new_pane = self.add_pane(ctx);
|
let new_pane = self.add_pane(ctx);
|
||||||
self.activate_pane(new_pane.clone(), ctx);
|
self.activate_pane(new_pane.clone(), ctx);
|
||||||
if let Some(item) = pane.as_ref(ctx).active_item() {
|
if let Some(item) = pane.read(ctx).active_item() {
|
||||||
if let Some(clone) = item.clone_on_split(ctx.app_mut()) {
|
if let Some(clone) = item.clone_on_split(ctx.app_mut()) {
|
||||||
self.add_item(clone, ctx);
|
self.add_item(clone, ctx);
|
||||||
}
|
}
|
||||||
|
@ -389,13 +389,12 @@ impl View for WorkspaceView {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{pane, Workspace, WorkspaceView};
|
use super::{pane, Workspace, WorkspaceView};
|
||||||
use crate::{settings, test::temp_tree, workspace::WorkspaceHandle as _};
|
use crate::{settings, test::temp_tree, workspace::WorkspaceHandle as _};
|
||||||
use anyhow::Result;
|
|
||||||
use gpui::App;
|
use gpui::App;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_open_entry() -> Result<()> {
|
fn test_open_entry() {
|
||||||
App::test((), |mut app| async move {
|
App::test_async((), |mut app| async move {
|
||||||
let dir = temp_tree(json!({
|
let dir = temp_tree(json!({
|
||||||
"a": {
|
"a": {
|
||||||
"aa": "aa contents",
|
"aa": "aa contents",
|
||||||
|
@ -407,7 +406,7 @@ mod tests {
|
||||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||||
let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
|
let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
|
||||||
app.finish_pending_tasks().await; // Open and populate worktree.
|
app.finish_pending_tasks().await; // Open and populate worktree.
|
||||||
let entries = workspace.file_entries(&app);
|
let entries = app.read(|ctx| workspace.file_entries(ctx));
|
||||||
|
|
||||||
let (_, workspace_view) =
|
let (_, workspace_view) =
|
||||||
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
|
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
|
||||||
|
@ -416,19 +415,27 @@ mod tests {
|
||||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
|
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
|
||||||
app.finish_pending_tasks().await;
|
app.finish_pending_tasks().await;
|
||||||
|
|
||||||
workspace_view.read(&app, |w, app| {
|
app.read(|ctx| {
|
||||||
assert_eq!(w.active_pane().as_ref(app).items().len(), 1);
|
assert_eq!(
|
||||||
|
workspace_view
|
||||||
|
.read(ctx)
|
||||||
|
.active_pane()
|
||||||
|
.read(ctx)
|
||||||
|
.items()
|
||||||
|
.len(),
|
||||||
|
1
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open the second entry
|
// Open the second entry
|
||||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[1], ctx));
|
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[1], ctx));
|
||||||
app.finish_pending_tasks().await;
|
app.finish_pending_tasks().await;
|
||||||
|
|
||||||
workspace_view.read(&app, |w, app| {
|
app.read(|ctx| {
|
||||||
let active_pane = w.active_pane().as_ref(app);
|
let active_pane = workspace_view.read(ctx).active_pane().read(ctx);
|
||||||
assert_eq!(active_pane.items().len(), 2);
|
assert_eq!(active_pane.items().len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_pane.active_item().unwrap().entry_id(app),
|
active_pane.active_item().unwrap().entry_id(ctx),
|
||||||
Some(entries[1])
|
Some(entries[1])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -437,11 +444,11 @@ mod tests {
|
||||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
|
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
|
||||||
app.finish_pending_tasks().await;
|
app.finish_pending_tasks().await;
|
||||||
|
|
||||||
workspace_view.read(&app, |w, app| {
|
app.read(|ctx| {
|
||||||
let active_pane = w.active_pane().as_ref(app);
|
let active_pane = workspace_view.read(ctx).active_pane().read(ctx);
|
||||||
assert_eq!(active_pane.items().len(), 2);
|
assert_eq!(active_pane.items().len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_pane.active_item().unwrap().entry_id(app),
|
active_pane.active_item().unwrap().entry_id(ctx),
|
||||||
Some(entries[0])
|
Some(entries[0])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -453,18 +460,24 @@ mod tests {
|
||||||
});
|
});
|
||||||
app.finish_pending_tasks().await;
|
app.finish_pending_tasks().await;
|
||||||
|
|
||||||
workspace_view.read(&app, |w, app| {
|
app.read(|ctx| {
|
||||||
assert_eq!(w.active_pane().as_ref(app).items().len(), 3);
|
assert_eq!(
|
||||||
|
workspace_view
|
||||||
|
.read(ctx)
|
||||||
|
.active_pane()
|
||||||
|
.read(ctx)
|
||||||
|
.items()
|
||||||
|
.len(),
|
||||||
|
3
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pane_actions() -> Result<()> {
|
fn test_pane_actions() {
|
||||||
App::test((), |mut app| async move {
|
App::test_async((), |mut app| async move {
|
||||||
pane::init(&mut app);
|
app.update(|ctx| pane::init(ctx));
|
||||||
|
|
||||||
let dir = temp_tree(json!({
|
let dir = temp_tree(json!({
|
||||||
"a": {
|
"a": {
|
||||||
|
@ -477,7 +490,7 @@ mod tests {
|
||||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||||
let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
|
let workspace = app.add_model(|ctx| Workspace::new(vec![dir.path().into()], ctx));
|
||||||
app.finish_pending_tasks().await; // Open and populate worktree.
|
app.finish_pending_tasks().await; // Open and populate worktree.
|
||||||
let entries = workspace.file_entries(&app);
|
let entries = app.read(|ctx| workspace.file_entries(ctx));
|
||||||
|
|
||||||
let (window_id, workspace_view) =
|
let (window_id, workspace_view) =
|
||||||
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
|
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
|
||||||
|
@ -485,24 +498,28 @@ mod tests {
|
||||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
|
workspace_view.update(&mut app, |w, ctx| w.open_entry(entries[0], ctx));
|
||||||
app.finish_pending_tasks().await;
|
app.finish_pending_tasks().await;
|
||||||
|
|
||||||
let pane_1 = workspace_view.read(&app, |w, _| w.active_pane().clone());
|
let pane_1 = app.read(|ctx| workspace_view.read(ctx).active_pane().clone());
|
||||||
|
|
||||||
app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
|
app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
|
||||||
let pane_2 = workspace_view.read(&app, |w, _| w.active_pane().clone());
|
app.update(|ctx| {
|
||||||
assert_ne!(pane_1, pane_2);
|
let pane_2 = workspace_view.read(ctx).active_pane().clone();
|
||||||
|
assert_ne!(pane_1, pane_2);
|
||||||
|
|
||||||
pane_2.read(&app, |p, app| {
|
assert_eq!(
|
||||||
assert_eq!(p.active_item().unwrap().entry_id(app), Some(entries[0]));
|
pane_2
|
||||||
});
|
.read(ctx)
|
||||||
|
.active_item()
|
||||||
|
.unwrap()
|
||||||
|
.entry_id(ctx.as_ref()),
|
||||||
|
Some(entries[0])
|
||||||
|
);
|
||||||
|
|
||||||
app.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
|
ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
|
||||||
|
|
||||||
workspace_view.read(&app, |w, _| {
|
let w = workspace_view.read(ctx);
|
||||||
assert_eq!(w.panes.len(), 1);
|
assert_eq!(w.panes.len(), 1);
|
||||||
assert_eq!(w.active_pane(), &pane_1)
|
assert_eq!(w.active_pane(), &pane_1);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -409,7 +409,7 @@ pub trait WorktreeHandle {
|
||||||
|
|
||||||
impl WorktreeHandle for ModelHandle<Worktree> {
|
impl WorktreeHandle for ModelHandle<Worktree> {
|
||||||
fn file(&self, entry_id: usize, app: &AppContext) -> Result<FileHandle> {
|
fn file(&self, entry_id: usize, app: &AppContext) -> Result<FileHandle> {
|
||||||
if entry_id >= self.as_ref(app).entry_count() {
|
if entry_id >= self.read(app).entry_count() {
|
||||||
return Err(anyhow!("Entry does not exist in tree"));
|
return Err(anyhow!("Entry does not exist in tree"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,15 +461,15 @@ pub struct FileHandle {
|
||||||
|
|
||||||
impl FileHandle {
|
impl FileHandle {
|
||||||
pub fn path(&self, app: &AppContext) -> PathBuf {
|
pub fn path(&self, app: &AppContext) -> PathBuf {
|
||||||
self.worktree.as_ref(app).entry_path(self.entry_id).unwrap()
|
self.worktree.read(app).entry_path(self.entry_id).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_history(&self, app: &AppContext) -> impl Future<Output = Result<History>> {
|
pub fn load_history(&self, app: &AppContext) -> impl Future<Output = Result<History>> {
|
||||||
self.worktree.as_ref(app).load_history(self.entry_id)
|
self.worktree.read(app).load_history(self.entry_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save<'a>(&self, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
|
pub fn save<'a>(&self, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
|
||||||
let worktree = self.worktree.as_ref(ctx);
|
let worktree = self.worktree.read(ctx);
|
||||||
worktree.save(self.entry_id, content, ctx)
|
worktree.save(self.entry_id, content, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -648,8 +648,8 @@ mod test {
|
||||||
use std::os::unix;
|
use std::os::unix;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_populate_and_search() -> Result<()> {
|
fn test_populate_and_search() {
|
||||||
App::test((), |mut app| async move {
|
App::test_async((), |mut app| async move {
|
||||||
let dir = temp_tree(json!({
|
let dir = temp_tree(json!({
|
||||||
"root": {
|
"root": {
|
||||||
"apple": "",
|
"apple": "",
|
||||||
|
@ -666,12 +666,13 @@ mod test {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let root_link_path = dir.path().join("root_link");
|
let root_link_path = dir.path().join("root_link");
|
||||||
unix::fs::symlink(&dir.path().join("root"), &root_link_path)?;
|
unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
|
||||||
|
|
||||||
let tree = app.add_model(|ctx| Worktree::new(1, root_link_path, Some(ctx)));
|
let tree = app.add_model(|ctx| Worktree::new(1, root_link_path, Some(ctx)));
|
||||||
app.finish_pending_tasks().await;
|
app.finish_pending_tasks().await;
|
||||||
|
|
||||||
tree.read(&app, |tree, _| {
|
app.read(|ctx| {
|
||||||
|
let tree = tree.read(ctx);
|
||||||
assert_eq!(tree.file_count(), 4);
|
assert_eq!(tree.file_count(), 4);
|
||||||
let results = match_paths(&[tree.clone()], "bna", false, false, 10)
|
let results = match_paths(&[tree.clone()], "bna", false, false, 10)
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -685,14 +686,13 @@ mod test {
|
||||||
PathBuf::from("root_link/banana/carrot/endive"),
|
PathBuf::from("root_link/banana/carrot/endive"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
})
|
||||||
Ok(())
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_save_file() {
|
fn test_save_file() {
|
||||||
App::test((), |mut app| async move {
|
App::test_async((), |mut app| async move {
|
||||||
let dir = temp_tree(json!({
|
let dir = temp_tree(json!({
|
||||||
"file1": "the old contents",
|
"file1": "the old contents",
|
||||||
}));
|
}));
|
||||||
|
@ -700,24 +700,24 @@ mod test {
|
||||||
let tree = app.add_model(|ctx| Worktree::new(1, dir.path(), Some(ctx)));
|
let tree = app.add_model(|ctx| Worktree::new(1, dir.path(), Some(ctx)));
|
||||||
app.finish_pending_tasks().await;
|
app.finish_pending_tasks().await;
|
||||||
|
|
||||||
let file_id = tree.read(&app, |tree, _| {
|
|
||||||
let entry = tree.files().next().unwrap();
|
|
||||||
assert_eq!(entry.path.file_name().unwrap(), "file1");
|
|
||||||
entry.entry_id
|
|
||||||
});
|
|
||||||
|
|
||||||
let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
|
let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
|
||||||
|
|
||||||
|
let entry = app.read(|ctx| {
|
||||||
|
let entry = tree.read(ctx).files().next().unwrap();
|
||||||
|
assert_eq!(entry.path.file_name().unwrap(), "file1");
|
||||||
|
entry
|
||||||
|
});
|
||||||
|
let file_id = entry.entry_id;
|
||||||
|
|
||||||
tree.update(&mut app, |tree, ctx| {
|
tree.update(&mut app, |tree, ctx| {
|
||||||
smol::block_on(tree.save(file_id, buffer.snapshot(), ctx.app())).unwrap()
|
smol::block_on(tree.save(file_id, buffer.snapshot(), ctx.app())).unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
let history = tree
|
let history = app
|
||||||
.read(&app, |tree, _| tree.load_history(file_id))
|
.read(|ctx| tree.read(ctx).load_history(file_id))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(history.base_text, buffer.text());
|
assert_eq!(history.base_text, buffer.text());
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue