Eliminate GPUI View, ViewContext, and WindowContext types (#22632)

There's still a bit more work to do on this, but this PR is compiling
(with warnings) after eliminating the key types. When the tasks below
are complete, this will be the new narrative for GPUI:

- `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit
of state, and if `T` implements `Render`, then `Entity<T>` implements
`Element`.
- `&mut App` This replaces `AppContext` and represents the app.
- `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It
is provided by the framework when updating an entity.
- `&mut Window` Broken out of `&mut WindowContext` which no longer
exists. Every method that once took `&mut WindowContext` now takes `&mut
Window, &mut App` and every method that took `&mut ViewContext<T>` now
takes `&mut Window, &mut Context<T>`

Not pictured here are the two other failed attempts. It's been quite a
month!

Tasks:

- [x] Remove `View`, `ViewContext`, `WindowContext` and thread through
`Window`
- [x] [@cole-miller @mikayla-maki] Redraw window when entities change
- [x] [@cole-miller @mikayla-maki] Get examples and Zed running
- [x] [@cole-miller @mikayla-maki] Fix Zed rendering
- [x] [@mikayla-maki] Fix todo! macros and comments
- [x] Fix a bug where the editor would not be redrawn because of view
caching
- [x] remove publicness window.notify() and replace with
`AppContext::notify`
- [x] remove `observe_new_window_models`, replace with
`observe_new_models` with an optional window
- [x] Fix a bug where the project panel would not be redrawn because of
the wrong refresh() call being used
- [x] Fix the tests
- [x] Fix warnings by eliminating `Window` params or using `_`
- [x] Fix conflicts
- [x] Simplify generic code where possible
- [x] Rename types
- [ ] Update docs

### issues post merge

- [x] Issues switching between normal and insert mode
- [x] Assistant re-rendering failure
- [x] Vim test failures
- [x] Mac build issue



Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: max <max@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local>
Co-authored-by: Mikayla <mikayla.c.maki@gmail.com>
Co-authored-by: joão <joao@zed.dev>
This commit is contained in:
Nathan Sobo 2025-01-25 20:02:45 -07:00 committed by GitHub
parent 21b4a0d50e
commit 6fca1d2b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
648 changed files with 36248 additions and 28208 deletions

View file

@ -45,7 +45,7 @@ impl ParentElement for KernelListItem {
}
impl RenderOnce for KernelListItem {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
ListItem::new(self.kernel_specification.name())
.selectable(false)
.start_slot(

View file

@ -16,7 +16,7 @@ use gpui::SharedString;
use gpui::Task;
use ui::{prelude::*, ListItem, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
type OnSelect = Box<dyn Fn(KernelSpecification, &mut WindowContext)>;
type OnSelect = Box<dyn Fn(KernelSpecification, &mut Window, &mut App)>;
#[derive(IntoElement)]
pub struct KernelSelector<T: PopoverTrigger> {
@ -84,16 +84,21 @@ impl PickerDelegate for KernelPickerDelegate {
}
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
self.selected_kernelspec = self.filtered_kernels.get(ix).cloned();
cx.notify();
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Select a kernel...".into()
}
fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
fn update_matches(
&mut self,
query: String,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let all_kernels = self.all_kernels.clone();
if query.is_empty() {
@ -113,20 +118,21 @@ impl PickerDelegate for KernelPickerDelegate {
return Task::ready(());
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
if let Some(kernelspec) = &self.selected_kernelspec {
(self.on_select)(kernelspec.clone(), cx.window_context());
(self.on_select)(kernelspec.clone(), window, cx);
cx.emit(DismissEvent);
}
}
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let kernelspec = self.filtered_kernels.get(ix)?;
let is_selected = self.selected_kernelspec.as_ref() == Some(kernelspec);
@ -204,7 +210,11 @@ impl PickerDelegate for KernelPickerDelegate {
)
}
fn render_footer(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<gpui::AnyElement> {
fn render_footer(
&self,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<gpui::AnyElement> {
Some(
h_flex()
.w_full()
@ -218,7 +228,7 @@ impl PickerDelegate for KernelPickerDelegate {
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::End)
.on_click(move |_, cx| cx.open_url(KERNEL_DOCS_URL)),
.on_click(move |_, _, cx| cx.open_url(KERNEL_DOCS_URL)),
)
.into_any(),
)
@ -226,7 +236,7 @@ impl PickerDelegate for KernelPickerDelegate {
}
impl<T: PopoverTrigger> RenderOnce for KernelSelector<T> {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let store = ReplStore::global(cx).read(cx);
let all_kernels: Vec<KernelSpecification> = store
@ -243,15 +253,15 @@ impl<T: PopoverTrigger> RenderOnce for KernelSelector<T> {
selected_kernelspec,
};
let picker_view = cx.new_view(|cx| {
let picker = Picker::uniform_list(delegate, cx)
let picker_view = cx.new(|cx| {
let picker = Picker::uniform_list(delegate, window, cx)
.width(rems(30.))
.max_height(Some(rems(20.).into()));
picker
});
PopoverMenu::new("kernel-switcher")
.menu(move |_cx| Some(picker_view.clone()))
.menu(move |_window, _cx| Some(picker_view.clone()))
.trigger(self.trigger)
.attach(gpui::Corner::BottomLeft)
.when_some(self.handle, |menu, handle| menu.with_handle(handle))

View file

@ -1,7 +1,7 @@
use std::collections::HashMap;
use editor::EditorSettings;
use gpui::AppContext;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
@ -12,7 +12,7 @@ pub struct JupyterSettings {
}
impl JupyterSettings {
pub fn enabled(cx: &AppContext) -> bool {
pub fn enabled(cx: &App) -> bool {
// In order to avoid a circular dependency between `editor` and `repl` crates,
// we put the `enable` flag on its settings.
// This allows the editor to set up context for key bindings/actions.
@ -43,7 +43,7 @@ impl Settings for JupyterSettings {
fn load(
sources: SettingsSources<Self::FileContent>,
_cx: &mut gpui::AppContext,
_cx: &mut gpui::App,
) -> anyhow::Result<Self>
where
Self: Sized,

View file

@ -6,7 +6,7 @@ use futures::{
future::Shared,
stream,
};
use gpui::{AppContext, Model, Task, WindowContext};
use gpui::{App, Entity, Task, Window};
use language::LanguageName;
pub use native_kernel::*;
@ -61,7 +61,7 @@ impl KernelSpecification {
})
}
pub fn icon(&self, cx: &AppContext) -> Icon {
pub fn icon(&self, cx: &App) -> Icon {
let lang_name = match self {
Self::Jupyter(spec) => spec.kernelspec.language.clone(),
Self::PythonEnv(spec) => spec.kernelspec.language.clone(),
@ -76,9 +76,9 @@ impl KernelSpecification {
}
pub fn python_env_kernel_specifications(
project: &Model<Project>,
project: &Entity<Project>,
worktree_id: WorktreeId,
cx: &mut AppContext,
cx: &mut App,
) -> impl Future<Output = Result<Vec<KernelSpecification>>> {
let python_language = LanguageName::new("Python");
let toolchains = project
@ -148,7 +148,7 @@ pub trait RunningKernel: Send + Debug {
fn set_execution_state(&mut self, state: ExecutionState);
fn kernel_info(&self) -> Option<&KernelInfoReply>;
fn set_kernel_info(&mut self, info: KernelInfoReply);
fn force_shutdown(&mut self, cx: &mut WindowContext) -> Task<anyhow::Result<()>>;
fn force_shutdown(&mut self, window: &mut Window, cx: &mut App) -> Task<anyhow::Result<()>>;
}
#[derive(Debug, Clone)]

View file

@ -5,7 +5,7 @@ use futures::{
stream::{SelectAll, StreamExt},
AsyncBufReadExt as _, SinkExt as _,
};
use gpui::{EntityId, Task, View, WindowContext};
use gpui::{App, Entity, EntityId, Task, Window};
use jupyter_protocol::{
connection_info::{ConnectionInfo, Transport},
ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent, KernelInfoReply,
@ -114,10 +114,11 @@ impl NativeRunningKernel {
working_directory: PathBuf,
fs: Arc<dyn Fs>,
// todo: convert to weak view
session: View<Session>,
cx: &mut WindowContext,
session: Entity<Session>,
window: &mut Window,
cx: &mut App,
) -> Task<Result<Box<dyn RunningKernel>>> {
cx.spawn(|cx| async move {
window.spawn(cx, |cx| async move {
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let ports = peek_ports(ip).await?;
@ -179,8 +180,8 @@ impl NativeRunningKernel {
|mut cx| async move {
while let Some(message) = messages_rx.next().await {
session
.update(&mut cx, |session, cx| {
session.route(&message, cx);
.update_in(&mut cx, |session, window, cx| {
session.route(&message, window, cx);
})
.ok();
}
@ -196,8 +197,8 @@ impl NativeRunningKernel {
|mut cx| async move {
while let Ok(message) = iopub_socket.read().await {
session
.update(&mut cx, |session, cx| {
session.route(&message, cx);
.update_in(&mut cx, |session, window, cx| {
session.route(&message, window, cx);
})
.ok();
}
@ -347,7 +348,7 @@ impl RunningKernel for NativeRunningKernel {
self.kernel_info = Some(info);
}
fn force_shutdown(&mut self, _cx: &mut WindowContext) -> Task<anyhow::Result<()>> {
fn force_shutdown(&mut self, _window: &mut Window, _cx: &mut App) -> Task<anyhow::Result<()>> {
self._process_status_task.take();
self.request_tx.close_channel();

View file

@ -1,5 +1,5 @@
use futures::{channel::mpsc, SinkExt as _};
use gpui::{Task, View, WindowContext};
use gpui::{App, Entity, Task, Window};
use http_client::{AsyncBody, HttpClient, Request};
use jupyter_protocol::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply};
@ -137,8 +137,9 @@ impl RemoteRunningKernel {
pub fn new(
kernelspec: RemoteKernelSpecification,
working_directory: std::path::PathBuf,
session: View<Session>,
cx: &mut WindowContext,
session: Entity<Session>,
window: &mut Window,
cx: &mut App,
) -> Task<Result<Box<dyn RunningKernel>>> {
let remote_server = RemoteServer {
base_url: kernelspec.url,
@ -147,7 +148,7 @@ impl RemoteRunningKernel {
let http_client = cx.http_client();
cx.spawn(|cx| async move {
window.spawn(cx, |cx| async move {
let kernel_id = launch_remote_kernel(
&remote_server,
http_client.clone(),
@ -205,8 +206,8 @@ impl RemoteRunningKernel {
match message {
Ok(message) => {
session
.update(&mut cx, |session, cx| {
session.route(&message, cx);
.update_in(&mut cx, |session, window, cx| {
session.route(&message, window, cx);
})
.ok();
}
@ -273,14 +274,14 @@ impl RunningKernel for RemoteRunningKernel {
self.kernel_info = Some(info);
}
fn force_shutdown(&mut self, cx: &mut WindowContext) -> Task<anyhow::Result<()>> {
fn force_shutdown(&mut self, window: &mut Window, cx: &mut App) -> Task<anyhow::Result<()>> {
let url = self
.remote_server
.api_url(&format!("/kernels/{}", self.kernel_id));
let token = self.remote_server.token.clone();
let http_client = self.http_client.clone();
cx.spawn(|_| async move {
window.spawn(cx, |_| async move {
let request = Request::builder()
.method("DELETE")
.uri(&url)

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use editor::{Editor, EditorMode, MultiBuffer};
use futures::future::Shared;
use gpui::{prelude::*, AppContext, Hsla, Task, TextStyleRefinement, View};
use gpui::{prelude::*, App, Entity, Hsla, Task, TextStyleRefinement};
use language::{Buffer, Language, LanguageRegistry};
use markdown_preview::{markdown_parser::parse_markdown, markdown_renderer::render_markdown_block};
use nbformat::v4::{CellId, CellMetadata, CellType};
@ -62,7 +62,10 @@ impl CellControl {
}
impl Clickable for CellControl {
fn on_click(self, handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static) -> Self {
fn on_click(
self,
handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
let button = self.button.on_click(handler);
Self { button }
}
@ -75,28 +78,33 @@ impl Clickable for CellControl {
/// A notebook cell
#[derive(Clone)]
pub enum Cell {
Code(View<CodeCell>),
Markdown(View<MarkdownCell>),
Raw(View<RawCell>),
Code(Entity<CodeCell>),
Markdown(Entity<MarkdownCell>),
Raw(Entity<RawCell>),
}
fn convert_outputs(outputs: &Vec<nbformat::v4::Output>, cx: &mut WindowContext) -> Vec<Output> {
fn convert_outputs(
outputs: &Vec<nbformat::v4::Output>,
window: &mut Window,
cx: &mut App,
) -> Vec<Output> {
outputs
.into_iter()
.map(|output| match output {
nbformat::v4::Output::Stream { text, .. } => Output::Stream {
content: cx.new_view(|cx| TerminalOutput::from(&text.0, cx)),
content: cx.new(|cx| TerminalOutput::from(&text.0, window, cx)),
},
nbformat::v4::Output::DisplayData(display_data) => {
Output::new(&display_data.data, None, cx)
Output::new(&display_data.data, None, window, cx)
}
nbformat::v4::Output::ExecuteResult(execute_result) => {
Output::new(&execute_result.data, None, cx)
Output::new(&execute_result.data, None, window, cx)
}
nbformat::v4::Output::Error(error) => Output::ErrorOutput(ErrorView {
ename: error.ename.clone(),
evalue: error.evalue.clone(),
traceback: cx.new_view(|cx| TerminalOutput::from(&error.traceback.join("\n"), cx)),
traceback: cx
.new(|cx| TerminalOutput::from(&error.traceback.join("\n"), window, cx)),
}),
})
.collect()
@ -107,7 +115,8 @@ impl Cell {
cell: &nbformat::v4::Cell,
languages: &Arc<LanguageRegistry>,
notebook_language: Shared<Task<Option<Arc<Language>>>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut App,
) -> Self {
match cell {
nbformat::v4::Cell::Markdown {
@ -118,12 +127,12 @@ impl Cell {
} => {
let source = source.join("");
let view = cx.new_view(|cx| {
let model = cx.new(|cx| {
let markdown_parsing_task = {
let languages = languages.clone();
let source = source.clone();
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
let parsed_markdown = cx
.background_executor()
.spawn(async move {
@ -150,7 +159,7 @@ impl Cell {
}
});
Cell::Markdown(view)
Cell::Markdown(model)
}
nbformat::v4::Cell::Code {
id,
@ -158,18 +167,19 @@ impl Cell {
execution_count,
source,
outputs,
} => Cell::Code(cx.new_view(|cx| {
} => Cell::Code(cx.new(|cx| {
let text = source.join("");
let buffer = cx.new_model(|cx| Buffer::local(text.clone(), cx));
let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
let buffer = cx.new(|cx| Buffer::local(text.clone(), cx));
let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
let editor_view = cx.new_view(|cx| {
let editor_view = cx.new(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight { max_lines: 1024 },
multi_buffer,
None,
false,
window,
cx,
);
@ -183,7 +193,7 @@ impl Cell {
..Default::default()
};
editor.set_text(text, cx);
editor.set_text(text, window, cx);
editor.set_show_gutter(false, cx);
editor.set_text_style_refinement(refinement);
@ -192,7 +202,7 @@ impl Cell {
});
let buffer = buffer.clone();
let language_task = cx.spawn(|this, mut cx| async move {
let language_task = cx.spawn_in(window, |this, mut cx| async move {
let language = notebook_language.await;
buffer.update(&mut cx, |buffer, cx| {
@ -206,7 +216,7 @@ impl Cell {
execution_count: *execution_count,
source: source.join(""),
editor: editor_view,
outputs: convert_outputs(outputs, cx),
outputs: convert_outputs(outputs, window, cx),
selected: false,
language_task,
cell_position: None,
@ -216,7 +226,7 @@ impl Cell {
id,
metadata,
source,
} => Cell::Raw(cx.new_view(|_| RawCell {
} => Cell::Raw(cx.new(|_| RawCell {
id: id.clone(),
metadata: metadata.clone(),
source: source.join(""),
@ -236,7 +246,7 @@ pub trait RenderableCell: Render {
fn source(&self) -> &String;
fn selected(&self) -> bool;
fn set_selected(&mut self, selected: bool) -> &mut Self;
fn selected_bg_color(&self, cx: &ViewContext<Self>) -> Hsla {
fn selected_bg_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
if self.selected() {
let mut color = cx.theme().colors().icon_accent;
color.fade_out(0.9);
@ -246,14 +256,15 @@ pub trait RenderableCell: Render {
cx.theme().colors().tab_bar_background
}
}
fn control(&self, _cx: &ViewContext<Self>) -> Option<CellControl> {
fn control(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<CellControl> {
None
}
fn cell_position_spacer(
&self,
is_first: bool,
cx: &ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> {
let cell_position = self.cell_position();
@ -266,7 +277,7 @@ pub trait RenderableCell: Render {
}
}
fn gutter(&self, cx: &ViewContext<Self>) -> impl IntoElement {
fn gutter(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_selected = self.selected();
div()
@ -289,7 +300,7 @@ pub trait RenderableCell: Render {
.when(!is_selected, |this| this.bg(cx.theme().colors().border)),
),
)
.when_some(self.control(cx), |this, control| {
.when_some(self.control(window, cx), |this, control| {
this.child(
div()
.absolute()
@ -314,7 +325,7 @@ pub trait RenderableCell: Render {
pub trait RunnableCell: RenderableCell {
fn execution_count(&self) -> Option<i32>;
fn set_execution_count(&mut self, count: i32) -> &mut Self;
fn run(&mut self, cx: &mut ViewContext<Self>) -> ();
fn run(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ();
}
pub struct MarkdownCell {
@ -356,7 +367,7 @@ impl RenderableCell for MarkdownCell {
self
}
fn control(&self, _: &ViewContext<Self>) -> Option<CellControl> {
fn control(&self, _window: &mut Window, _: &mut Context<Self>) -> Option<CellControl> {
None
}
@ -371,18 +382,18 @@ impl RenderableCell for MarkdownCell {
}
impl Render for MarkdownCell {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let Some(parsed) = self.parsed_markdown.as_ref() else {
return div();
};
let mut markdown_render_context =
markdown_preview::markdown_renderer::RenderContext::new(None, cx);
markdown_preview::markdown_renderer::RenderContext::new(None, window, cx);
v_flex()
.size_full()
// TODO: Move base cell render into trait impl so we don't have to repeat this
.children(self.cell_position_spacer(true, cx))
.children(self.cell_position_spacer(true, window, cx))
.child(
h_flex()
.w_full()
@ -390,8 +401,8 @@ impl Render for MarkdownCell {
.rounded_sm()
.items_start()
.gap(DynamicSpacing::Base08.rems(cx))
.bg(self.selected_bg_color(cx))
.child(self.gutter(cx))
.bg(self.selected_bg_color(window, cx))
.child(self.gutter(window, cx))
.child(
v_flex()
.size_full()
@ -408,7 +419,7 @@ impl Render for MarkdownCell {
),
)
// TODO: Move base cell render into trait impl so we don't have to repeat this
.children(self.cell_position_spacer(false, cx))
.children(self.cell_position_spacer(false, window, cx))
}
}
@ -417,7 +428,7 @@ pub struct CodeCell {
metadata: CellMetadata,
execution_count: Option<i32>,
source: String,
editor: View<editor::Editor>,
editor: Entity<editor::Editor>,
outputs: Vec<Output>,
selected: bool,
cell_position: Option<CellPosition>,
@ -425,7 +436,7 @@ pub struct CodeCell {
}
impl CodeCell {
pub fn is_dirty(&self, cx: &AppContext) -> bool {
pub fn is_dirty(&self, cx: &App) -> bool {
self.editor.read(cx).buffer().read(cx).is_dirty(cx)
}
pub fn has_outputs(&self) -> bool {
@ -444,7 +455,7 @@ impl CodeCell {
}
}
pub fn gutter_output(&self, cx: &ViewContext<Self>) -> impl IntoElement {
pub fn gutter_output(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_selected = self.selected();
div()
@ -505,12 +516,12 @@ impl RenderableCell for CodeCell {
&self.source
}
fn control(&self, cx: &ViewContext<Self>) -> Option<CellControl> {
fn control(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<CellControl> {
let cell_control = if self.has_outputs() {
CellControl::new("rerun-cell", CellControlType::RerunCell)
} else {
CellControl::new("run-cell", CellControlType::RunCell)
.on_click(cx.listener(move |this, _, cx| this.run(cx)))
.on_click(cx.listener(move |this, _, window, cx| this.run(window, cx)))
};
Some(cell_control)
@ -536,7 +547,7 @@ impl RenderableCell for CodeCell {
}
impl RunnableCell for CodeCell {
fn run(&mut self, cx: &mut ViewContext<Self>) {
fn run(&mut self, window: &mut Window, cx: &mut Context<Self>) {
println!("Running code cell: {}", self.id);
}
@ -552,11 +563,11 @@ impl RunnableCell for CodeCell {
}
impl Render for CodeCell {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.size_full()
// TODO: Move base cell render into trait impl so we don't have to repeat this
.children(self.cell_position_spacer(true, cx))
.children(self.cell_position_spacer(true, window, cx))
// Editor portion
.child(
h_flex()
@ -565,8 +576,8 @@ impl Render for CodeCell {
.rounded_sm()
.items_start()
.gap(DynamicSpacing::Base08.rems(cx))
.bg(self.selected_bg_color(cx))
.child(self.gutter(cx))
.bg(self.selected_bg_color(window, cx))
.child(self.gutter(window, cx))
.child(
div().py_1p5().w_full().child(
div()
@ -591,8 +602,8 @@ impl Render for CodeCell {
.rounded_sm()
.items_start()
.gap(DynamicSpacing::Base08.rems(cx))
.bg(self.selected_bg_color(cx))
.child(self.gutter_output(cx))
.bg(self.selected_bg_color(window, cx))
.child(self.gutter_output(window, cx))
.child(
div().py_1p5().w_full().child(
div()
@ -627,7 +638,7 @@ impl Render for CodeCell {
Some(content.clone().into_any_element())
}
Output::ErrorOutput(error_view) => {
error_view.render(cx)
error_view.render(window, cx)
}
Output::ClearOutputWaitMarker => None,
};
@ -648,7 +659,7 @@ impl Render for CodeCell {
),
)
// TODO: Move base cell render into trait impl so we don't have to repeat this
.children(self.cell_position_spacer(false, cx))
.children(self.cell_position_spacer(false, window, cx))
}
}
@ -699,11 +710,11 @@ impl RenderableCell for RawCell {
}
impl Render for RawCell {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.size_full()
// TODO: Move base cell render into trait impl so we don't have to repeat this
.children(self.cell_position_spacer(true, cx))
.children(self.cell_position_spacer(true, window, cx))
.child(
h_flex()
.w_full()
@ -711,8 +722,8 @@ impl Render for RawCell {
.rounded_sm()
.items_start()
.gap(DynamicSpacing::Base08.rems(cx))
.bg(self.selected_bg_color(cx))
.child(self.gutter(cx))
.bg(self.selected_bg_color(window, cx))
.child(self.gutter(window, cx))
.child(
div()
.flex()
@ -725,6 +736,6 @@ impl Render for RawCell {
),
)
// TODO: Move base cell render into trait impl so we don't have to repeat this
.children(self.cell_position_spacer(false, cx))
.children(self.cell_position_spacer(false, window, cx))
}
}

View file

@ -9,8 +9,8 @@ use feature_flags::{FeatureFlagAppExt as _, NotebookFeatureFlag};
use futures::future::Shared;
use futures::FutureExt;
use gpui::{
actions, list, prelude::*, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView,
ListScrollEvent, ListState, Model, Point, Task, View,
actions, list, prelude::*, AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable,
ListScrollEvent, ListState, Point, Task,
};
use language::{Language, LanguageRegistry};
use project::{Project, ProjectEntryId, ProjectPath};
@ -46,7 +46,7 @@ pub(crate) const GUTTER_WIDTH: f32 = 19.0;
pub(crate) const CODE_BLOCK_INSET: f32 = MEDIUM_SPACING_SIZE;
pub(crate) const CONTROL_SIZE: f32 = 20.0;
pub fn init(cx: &mut AppContext) {
pub fn init(cx: &mut App) {
if cx.has_flag::<NotebookFeatureFlag>() || std::env::var("LOCAL_NOTEBOOK_DEV").is_ok() {
workspace::register_project_item::<NotebookEditor>(cx);
}
@ -66,10 +66,10 @@ pub fn init(cx: &mut AppContext) {
pub struct NotebookEditor {
languages: Arc<LanguageRegistry>,
project: Model<Project>,
project: Entity<Project>,
focus_handle: FocusHandle,
notebook_item: Model<NotebookItem>,
notebook_item: Entity<NotebookItem>,
remote_id: Option<ViewId>,
cell_list: ListState,
@ -81,9 +81,10 @@ pub struct NotebookEditor {
impl NotebookEditor {
pub fn new(
project: Model<Project>,
notebook_item: Model<NotebookItem>,
cx: &mut ViewContext<Self>,
project: Entity<Project>,
notebook_item: Entity<NotebookItem>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let focus_handle = cx.focus_handle();
@ -91,7 +92,7 @@ impl NotebookEditor {
let language_name = notebook_item.read(cx).language_name();
let notebook_language = notebook_item.read(cx).notebook_language();
let notebook_language = cx.spawn(|_, _| notebook_language).shared();
let notebook_language = cx.spawn_in(window, |_, _| notebook_language).shared();
let mut cell_order = vec![]; // Vec<CellId>
let mut cell_map = HashMap::default(); // HashMap<CellId, Cell>
@ -108,27 +109,32 @@ impl NotebookEditor {
cell_order.push(cell_id.clone());
cell_map.insert(
cell_id.clone(),
Cell::load(cell, &languages, notebook_language.clone(), cx),
Cell::load(cell, &languages, notebook_language.clone(), window, cx),
);
}
let view = cx.view().downgrade();
let notebook_handle = cx.model().downgrade();
let cell_count = cell_order.len();
let this = cx.view();
let this = cx.model();
let cell_list = ListState::new(
cell_count,
gpui::ListAlignment::Top,
px(1000.),
move |ix, cx| {
view.upgrade()
move |ix, window, cx| {
notebook_handle
.upgrade()
.and_then(|notebook_handle| {
notebook_handle.update(cx, |notebook, cx| {
notebook
.cell_order
.get(ix)
.and_then(|cell_id| notebook.cell_map.get(cell_id))
.map(|cell| notebook.render_cell(ix, cell, cx).into_any_element())
.map(|cell| {
notebook
.render_cell(ix, cell, window, cx)
.into_any_element()
})
})
})
.unwrap_or_else(|| div().into_any())
@ -148,7 +154,7 @@ impl NotebookEditor {
}
}
fn has_outputs(&self, cx: &ViewContext<Self>) -> bool {
fn has_outputs(&self, window: &mut Window, cx: &mut Context<Self>) -> bool {
self.cell_map.values().any(|cell| {
if let Cell::Code(code_cell) = cell {
code_cell.read(cx).has_outputs()
@ -158,7 +164,7 @@ impl NotebookEditor {
})
}
fn clear_outputs(&mut self, cx: &mut ViewContext<Self>) {
fn clear_outputs(&mut self, window: &mut Window, cx: &mut Context<Self>) {
for cell in self.cell_map.values() {
if let Cell::Code(code_cell) = cell {
code_cell.update(cx, |cell, _cx| {
@ -168,27 +174,27 @@ impl NotebookEditor {
}
}
fn run_cells(&mut self, cx: &mut ViewContext<Self>) {
fn run_cells(&mut self, window: &mut Window, cx: &mut Context<Self>) {
println!("Cells would all run here, if that was implemented!");
}
fn open_notebook(&mut self, _: &OpenNotebook, _cx: &mut ViewContext<Self>) {
fn open_notebook(&mut self, _: &OpenNotebook, _window: &mut Window, _cx: &mut Context<Self>) {
println!("Open notebook triggered");
}
fn move_cell_up(&mut self, cx: &mut ViewContext<Self>) {
fn move_cell_up(&mut self, window: &mut Window, cx: &mut Context<Self>) {
println!("Move cell up triggered");
}
fn move_cell_down(&mut self, cx: &mut ViewContext<Self>) {
fn move_cell_down(&mut self, window: &mut Window, cx: &mut Context<Self>) {
println!("Move cell down triggered");
}
fn add_markdown_block(&mut self, cx: &mut ViewContext<Self>) {
fn add_markdown_block(&mut self, window: &mut Window, cx: &mut Context<Self>) {
println!("Add markdown block triggered");
}
fn add_code_block(&mut self, cx: &mut ViewContext<Self>) {
fn add_code_block(&mut self, window: &mut Window, cx: &mut Context<Self>) {
println!("Add code block triggered");
}
@ -204,7 +210,8 @@ impl NotebookEditor {
&mut self,
index: usize,
jump_to_index: bool,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) {
// let previous_index = self.selected_cell_index;
self.selected_cell_index = index;
@ -213,11 +220,16 @@ impl NotebookEditor {
// in the future we may have some `on_cell_change` event that we want to fire here
if jump_to_index {
self.jump_to_cell(current_index, cx);
self.jump_to_cell(current_index, window, cx);
}
}
pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
pub fn select_next(
&mut self,
_: &menu::SelectNext,
window: &mut Window,
cx: &mut Context<Self>,
) {
let count = self.cell_count();
if count > 0 {
let index = self.selected_index();
@ -226,42 +238,57 @@ impl NotebookEditor {
} else {
index + 1
};
self.set_selected_index(ix, true, cx);
self.set_selected_index(ix, true, window, cx);
cx.notify();
}
}
pub fn select_previous(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
pub fn select_previous(
&mut self,
_: &menu::SelectPrev,
window: &mut Window,
cx: &mut Context<Self>,
) {
let count = self.cell_count();
if count > 0 {
let index = self.selected_index();
let ix = if index == 0 { 0 } else { index - 1 };
self.set_selected_index(ix, true, cx);
self.set_selected_index(ix, true, window, cx);
cx.notify();
}
}
pub fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
pub fn select_first(
&mut self,
_: &menu::SelectFirst,
window: &mut Window,
cx: &mut Context<Self>,
) {
let count = self.cell_count();
if count > 0 {
self.set_selected_index(0, true, cx);
self.set_selected_index(0, true, window, cx);
cx.notify();
}
}
pub fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
pub fn select_last(
&mut self,
_: &menu::SelectLast,
window: &mut Window,
cx: &mut Context<Self>,
) {
let count = self.cell_count();
if count > 0 {
self.set_selected_index(count - 1, true, cx);
self.set_selected_index(count - 1, true, window, cx);
cx.notify();
}
}
fn jump_to_cell(&mut self, index: usize, _cx: &mut ViewContext<Self>) {
fn jump_to_cell(&mut self, index: usize, _window: &mut Window, _cx: &mut Context<Self>) {
self.cell_list.scroll_to_reveal_item(index);
}
fn button_group(cx: &ViewContext<Self>) -> Div {
fn button_group(window: &mut Window, cx: &mut Context<Self>) -> Div {
v_flex()
.gap(DynamicSpacing::Base04.rems(cx))
.items_center()
@ -277,14 +304,19 @@ impl NotebookEditor {
fn render_notebook_control(
id: impl Into<SharedString>,
icon: IconName,
_cx: &ViewContext<Self>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> IconButton {
let id: ElementId = ElementId::Name(id.into());
IconButton::new(id, icon).width(px(CONTROL_SIZE).into())
}
fn render_notebook_controls(&self, cx: &ViewContext<Self>) -> impl IntoElement {
let has_outputs = self.has_outputs(cx);
fn render_notebook_controls(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let has_outputs = self.has_outputs(window, cx);
v_flex()
.max_w(px(CONTROL_SIZE + 4.0))
@ -298,83 +330,107 @@ impl NotebookEditor {
v_flex()
.gap(DynamicSpacing::Base08.rems(cx))
.child(
Self::button_group(cx)
Self::button_group(window, cx)
.child(
Self::render_notebook_control("run-all-cells", IconName::Play, cx)
.tooltip(move |cx| {
Tooltip::for_action("Execute all cells", &RunAll, cx)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(RunAll));
}),
Self::render_notebook_control(
"run-all-cells",
IconName::Play,
window,
cx,
)
.tooltip(move |window, cx| {
Tooltip::for_action("Execute all cells", &RunAll, window, cx)
})
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(RunAll), cx);
}),
)
.child(
Self::render_notebook_control(
"clear-all-outputs",
IconName::ListX,
window,
cx,
)
.disabled(!has_outputs)
.tooltip(move |cx| {
Tooltip::for_action("Clear all outputs", &ClearOutputs, cx)
.tooltip(move |window, cx| {
Tooltip::for_action(
"Clear all outputs",
&ClearOutputs,
window,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(ClearOutputs));
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(ClearOutputs), cx);
}),
),
)
.child(
Self::button_group(cx)
Self::button_group(window, cx)
.child(
Self::render_notebook_control(
"move-cell-up",
IconName::ArrowUp,
window,
cx,
)
.tooltip(move |cx| {
Tooltip::for_action("Move cell up", &MoveCellUp, cx)
.tooltip(move |window, cx| {
Tooltip::for_action("Move cell up", &MoveCellUp, window, cx)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(MoveCellUp));
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(MoveCellUp), cx);
}),
)
.child(
Self::render_notebook_control(
"move-cell-down",
IconName::ArrowDown,
window,
cx,
)
.tooltip(move |cx| {
Tooltip::for_action("Move cell down", &MoveCellDown, cx)
.tooltip(move |window, cx| {
Tooltip::for_action("Move cell down", &MoveCellDown, window, cx)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(MoveCellDown));
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(MoveCellDown), cx);
}),
),
)
.child(
Self::button_group(cx)
Self::button_group(window, cx)
.child(
Self::render_notebook_control(
"new-markdown-cell",
IconName::Plus,
window,
cx,
)
.tooltip(move |cx| {
Tooltip::for_action("Add markdown block", &AddMarkdownBlock, cx)
.tooltip(move |window, cx| {
Tooltip::for_action(
"Add markdown block",
&AddMarkdownBlock,
window,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(AddMarkdownBlock));
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(AddMarkdownBlock), cx);
}),
)
.child(
Self::render_notebook_control("new-code-cell", IconName::Code, cx)
.tooltip(move |cx| {
Tooltip::for_action("Add code block", &AddCodeBlock, cx)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(AddCodeBlock));
}),
Self::render_notebook_control(
"new-code-cell",
IconName::Code,
window,
cx,
)
.tooltip(move |window, cx| {
Tooltip::for_action("Add code block", &AddCodeBlock, window, cx)
})
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(AddCodeBlock), cx);
}),
),
),
)
@ -385,10 +441,11 @@ impl NotebookEditor {
.child(Self::render_notebook_control(
"more-menu",
IconName::Ellipsis,
window,
cx,
))
.child(
Self::button_group(cx)
Self::button_group(window, cx)
.child(IconButton::new("repl", IconName::ReplNeutral)),
),
)
@ -406,7 +463,8 @@ impl NotebookEditor {
&self,
index: usize,
cell: &Cell,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let cell_position = self.cell_position(index);
@ -439,17 +497,27 @@ impl NotebookEditor {
}
impl Render for NotebookEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.key_context("notebook")
.track_focus(&self.focus_handle)
.on_action(cx.listener(|this, &OpenNotebook, cx| this.open_notebook(&OpenNotebook, cx)))
.on_action(cx.listener(|this, &ClearOutputs, cx| this.clear_outputs(cx)))
.on_action(cx.listener(|this, &RunAll, cx| this.run_cells(cx)))
.on_action(cx.listener(|this, &MoveCellUp, cx| this.move_cell_up(cx)))
.on_action(cx.listener(|this, &MoveCellDown, cx| this.move_cell_down(cx)))
.on_action(cx.listener(|this, &AddMarkdownBlock, cx| this.add_markdown_block(cx)))
.on_action(cx.listener(|this, &AddCodeBlock, cx| this.add_code_block(cx)))
.on_action(cx.listener(|this, &OpenNotebook, window, cx| {
this.open_notebook(&OpenNotebook, window, cx)
}))
.on_action(
cx.listener(|this, &ClearOutputs, window, cx| this.clear_outputs(window, cx)),
)
.on_action(cx.listener(|this, &RunAll, window, cx| this.run_cells(window, cx)))
.on_action(cx.listener(|this, &MoveCellUp, window, cx| this.move_cell_up(window, cx)))
.on_action(
cx.listener(|this, &MoveCellDown, window, cx| this.move_cell_down(window, cx)),
)
.on_action(cx.listener(|this, &AddMarkdownBlock, window, cx| {
this.add_markdown_block(window, cx)
}))
.on_action(
cx.listener(|this, &AddCodeBlock, window, cx| this.add_code_block(window, cx)),
)
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_previous))
.on_action(cx.listener(Self::select_first))
@ -469,12 +537,12 @@ impl Render for NotebookEditor {
.overflow_y_scroll()
.child(list(self.cell_list.clone()).size_full()),
)
.child(self.render_notebook_controls(cx))
.child(self.render_notebook_controls(window, cx))
}
}
impl FocusableView for NotebookEditor {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
impl Focusable for NotebookEditor {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
@ -492,10 +560,10 @@ pub struct NotebookItem {
impl project::ProjectItem for NotebookItem {
fn try_open(
project: &Model<Project>,
project: &Entity<Project>,
path: &ProjectPath,
cx: &mut AppContext,
) -> Option<Task<gpui::Result<Model<Self>>>> {
cx: &mut App,
) -> Option<Task<gpui::Result<Entity<Self>>>> {
let path = path.clone();
let project = project.clone();
let fs = project.read(cx).fs().clone();
@ -531,7 +599,7 @@ impl project::ProjectItem for NotebookItem {
.context("Entry not found")?
.id;
cx.new_model(|_| NotebookItem {
cx.new(|_| NotebookItem {
path: abs_path,
project_path: path,
languages,
@ -544,11 +612,11 @@ impl project::ProjectItem for NotebookItem {
}
}
fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
Some(self.id)
}
fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
fn project_path(&self, _: &App) -> Option<ProjectPath> {
Some(self.project_path.clone())
}
@ -607,7 +675,7 @@ impl EventEmitter<()> for NotebookEditor {}
// impl EventEmitter<ToolbarItemEvent> for NotebookControls {}
// impl Render for NotebookControls {
// fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
// fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
// div().child("notebook controls")
// }
// }
@ -616,7 +684,7 @@ impl EventEmitter<()> for NotebookEditor {}
// fn set_active_pane_item(
// &mut self,
// active_pane_item: Option<&dyn workspace::ItemHandle>,
// cx: &mut ViewContext<Self>,
// window: &mut Window, cx: &mut ModelContext<Self>,
// ) -> workspace::ToolbarItemLocation {
// cx.notify();
// self.active_item = None;
@ -628,7 +696,7 @@ impl EventEmitter<()> for NotebookEditor {}
// ToolbarItemLocation::PrimaryLeft
// }
// fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
// fn pane_focus_update(&mut self, pane_focused: bool, _window: &mut Window, _cx: &mut ModelContext<Self>) {
// self.pane_focused = pane_focused;
// }
// }
@ -639,27 +707,28 @@ impl Item for NotebookEditor {
fn clone_on_split(
&self,
_workspace_id: Option<workspace::WorkspaceId>,
cx: &mut ViewContext<Self>,
) -> Option<gpui::View<Self>>
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Entity<Self>>
where
Self: Sized,
{
Some(cx.new_view(|cx| Self::new(self.project.clone(), self.notebook_item.clone(), cx)))
Some(cx.new(|cx| Self::new(self.project.clone(), self.notebook_item.clone(), window, cx)))
}
fn for_each_project_item(
&self,
cx: &AppContext,
cx: &App,
f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem),
) {
f(self.notebook_item.entity_id(), self.notebook_item.read(cx))
}
fn is_singleton(&self, _cx: &AppContext) -> bool {
fn is_singleton(&self, _cx: &App) -> bool {
true
}
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement {
let path = &self.notebook_item.read(cx).path;
let title = path
.file_name()
@ -673,7 +742,7 @@ impl Item for NotebookEditor {
.into_any_element()
}
fn tab_icon(&self, _cx: &WindowContext) -> Option<Icon> {
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
Some(IconName::Book.into())
}
@ -682,29 +751,35 @@ impl Item for NotebookEditor {
}
// TODO
fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Point<Pixels>> {
fn pixel_position_of_cursor(&self, _: &App) -> Option<Point<Pixels>> {
None
}
// TODO
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
None
}
fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {
fn set_nav_history(
&mut self,
_: workspace::ItemNavHistory,
_window: &mut Window,
_: &mut Context<Self>,
) {
// TODO
}
// TODO
fn can_save(&self, _cx: &AppContext) -> bool {
fn can_save(&self, _cx: &App) -> bool {
false
}
// TODO
fn save(
&mut self,
_format: bool,
_project: Model<Project>,
_cx: &mut ViewContext<Self>,
_project: Entity<Project>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Task<Result<()>> {
unimplemented!("save() must be implemented if can_save() returns true")
}
@ -712,22 +787,24 @@ impl Item for NotebookEditor {
// TODO
fn save_as(
&mut self,
_project: Model<Project>,
_project: Entity<Project>,
_path: ProjectPath,
_cx: &mut ViewContext<Self>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Task<Result<()>> {
unimplemented!("save_as() must be implemented if can_save() returns true")
}
// TODO
fn reload(
&mut self,
_project: Model<Project>,
_cx: &mut ViewContext<Self>,
_project: Entity<Project>,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Task<Result<()>> {
unimplemented!("reload() must be implemented if can_save() returns true")
}
fn is_dirty(&self, cx: &AppContext) -> bool {
fn is_dirty(&self, cx: &App) -> bool {
self.cell_map.values().any(|cell| {
if let Cell::Code(code_cell) = cell {
code_cell.read(cx).is_dirty(cx)
@ -745,13 +822,14 @@ impl ProjectItem for NotebookEditor {
type Item = NotebookItem;
fn for_project_item(
project: Model<Project>,
item: Model<Self::Item>,
cx: &mut ViewContext<Self>,
project: Entity<Project>,
item: Entity<Self::Item>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self
where
Self: Sized,
{
Self::new(project, item, cx)
Self::new(project, item, window, cx)
}
}

View file

@ -37,12 +37,12 @@ use std::time::Duration;
use editor::{Editor, MultiBuffer};
use gpui::{
percentage, Animation, AnimationExt, AnyElement, ClipboardItem, Model, Render, Transformation,
View, WeakView,
percentage, Animation, AnimationExt, AnyElement, ClipboardItem, Entity, Render, Transformation,
WeakEntity,
};
use language::Buffer;
use runtimelib::{ExecutionState, JupyterMessageContent, MimeBundle, MimeType};
use ui::{div, prelude::*, v_flex, IntoElement, Styled, Tooltip, ViewContext};
use ui::{div, prelude::*, v_flex, Context, IntoElement, Styled, Tooltip, Window};
mod image;
use image::ImageView;
@ -74,56 +74,56 @@ fn rank_mime_type(mimetype: &MimeType) -> usize {
}
pub(crate) trait OutputContent {
fn clipboard_content(&self, cx: &WindowContext) -> Option<ClipboardItem>;
fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
fn clipboard_content(&self, window: &Window, cx: &App) -> Option<ClipboardItem>;
fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool {
false
}
fn has_buffer_content(&self, _cx: &WindowContext) -> bool {
fn has_buffer_content(&self, _window: &Window, _cx: &App) -> bool {
false
}
fn buffer_content(&mut self, _cx: &mut WindowContext) -> Option<Model<Buffer>> {
fn buffer_content(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Entity<Buffer>> {
None
}
}
impl<V: OutputContent + 'static> OutputContent for View<V> {
fn clipboard_content(&self, cx: &WindowContext) -> Option<ClipboardItem> {
self.read(cx).clipboard_content(cx)
impl<V: OutputContent + 'static> OutputContent for Entity<V> {
fn clipboard_content(&self, window: &Window, cx: &App) -> Option<ClipboardItem> {
self.read(cx).clipboard_content(window, cx)
}
fn has_clipboard_content(&self, cx: &WindowContext) -> bool {
self.read(cx).has_clipboard_content(cx)
fn has_clipboard_content(&self, window: &Window, cx: &App) -> bool {
self.read(cx).has_clipboard_content(window, cx)
}
fn has_buffer_content(&self, cx: &WindowContext) -> bool {
self.read(cx).has_buffer_content(cx)
fn has_buffer_content(&self, window: &Window, cx: &App) -> bool {
self.read(cx).has_buffer_content(window, cx)
}
fn buffer_content(&mut self, cx: &mut WindowContext) -> Option<Model<Buffer>> {
self.update(cx, |item, cx| item.buffer_content(cx))
fn buffer_content(&mut self, window: &mut Window, cx: &mut App) -> Option<Entity<Buffer>> {
self.update(cx, |item, cx| item.buffer_content(window, cx))
}
}
pub enum Output {
Plain {
content: View<TerminalOutput>,
content: Entity<TerminalOutput>,
display_id: Option<String>,
},
Stream {
content: View<TerminalOutput>,
content: Entity<TerminalOutput>,
},
Image {
content: View<ImageView>,
content: Entity<ImageView>,
display_id: Option<String>,
},
ErrorOutput(ErrorView),
Message(String),
Table {
content: View<TableView>,
content: Entity<TableView>,
display_id: Option<String>,
},
Markdown {
content: View<MarkdownView>,
content: Entity<MarkdownView>,
display_id: Option<String>,
},
ClearOutputWaitMarker,
@ -131,25 +131,26 @@ pub enum Output {
impl Output {
fn render_output_controls<V: OutputContent + 'static>(
v: View<V>,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<ExecutionView>,
v: Entity<V>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<ExecutionView>,
) -> Option<AnyElement> {
if !v.has_clipboard_content(cx) && !v.has_buffer_content(cx) {
if !v.has_clipboard_content(window, cx) && !v.has_buffer_content(window, cx) {
return None;
}
Some(
h_flex()
.pl_1()
.when(v.has_clipboard_content(cx), |el| {
.when(v.has_clipboard_content(window, cx), |el| {
let v = v.clone();
el.child(
IconButton::new(ElementId::Name("copy-output".into()), IconName::Copy)
.style(ButtonStyle::Transparent)
.tooltip(move |cx| Tooltip::text("Copy Output", cx))
.on_click(cx.listener(move |_, _, cx| {
let clipboard_content = v.clipboard_content(cx);
.tooltip(Tooltip::text("Copy Output"))
.on_click(cx.listener(move |_, _, window, cx| {
let clipboard_content = v.clipboard_content(window, cx);
if let Some(clipboard_content) = clipboard_content.as_ref() {
cx.write_to_clipboard(clipboard_content.clone());
@ -157,7 +158,7 @@ impl Output {
})),
)
})
.when(v.has_buffer_content(cx), |el| {
.when(v.has_buffer_content(window, cx), |el| {
let v = v.clone();
el.child(
IconButton::new(
@ -165,18 +166,18 @@ impl Output {
IconName::FileText,
)
.style(ButtonStyle::Transparent)
.tooltip(move |cx| Tooltip::text("Open in Buffer", cx))
.tooltip(Tooltip::text("Open in Buffer"))
.on_click(cx.listener({
let workspace = workspace.clone();
move |_, _, cx| {
move |_, _, window, cx| {
let buffer_content =
v.update(cx, |item, cx| item.buffer_content(cx));
v.update(cx, |item, cx| item.buffer_content(window, cx));
if let Some(buffer_content) = buffer_content.as_ref() {
let buffer = buffer_content.clone();
let editor = Box::new(cx.new_view(|cx| {
let multibuffer = cx.new_model(|cx| {
let editor = Box::new(cx.new(|cx| {
let multibuffer = cx.new(|cx| {
let mut multi_buffer =
MultiBuffer::singleton(buffer.clone(), cx);
@ -184,12 +185,19 @@ impl Output {
multi_buffer
});
Editor::for_multibuffer(multibuffer, None, false, cx)
Editor::for_multibuffer(
multibuffer,
None,
false,
window,
cx,
)
}));
workspace
.update(cx, |workspace, cx| {
workspace
.add_item_to_active_pane(editor, None, true, cx);
workspace.add_item_to_active_pane(
editor, None, true, window, cx,
);
})
.ok();
}
@ -204,8 +212,9 @@ impl Output {
pub fn render(
&self,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<ExecutionView>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<ExecutionView>,
) -> impl IntoElement {
let content = match self {
Self::Plain { content, .. } => Some(content.clone().into_any_element()),
@ -214,7 +223,7 @@ impl Output {
Self::Image { content, .. } => Some(content.clone().into_any_element()),
Self::Message(message) => Some(div().child(message.clone()).into_any_element()),
Self::Table { content, .. } => Some(content.clone().into_any_element()),
Self::ErrorOutput(error_view) => error_view.render(cx),
Self::ErrorOutput(error_view) => error_view.render(window, cx),
Self::ClearOutputWaitMarker => None,
};
@ -224,23 +233,26 @@ impl Output {
.child(div().flex_1().children(content))
.children(match self {
Self::Plain { content, .. } => {
Self::render_output_controls(content.clone(), workspace.clone(), cx)
Self::render_output_controls(content.clone(), workspace.clone(), window, cx)
}
Self::Markdown { content, .. } => {
Self::render_output_controls(content.clone(), workspace.clone(), cx)
Self::render_output_controls(content.clone(), workspace.clone(), window, cx)
}
Self::Stream { content, .. } => {
Self::render_output_controls(content.clone(), workspace.clone(), cx)
Self::render_output_controls(content.clone(), workspace.clone(), window, cx)
}
Self::Image { content, .. } => {
Self::render_output_controls(content.clone(), workspace.clone(), cx)
}
Self::ErrorOutput(err) => {
Self::render_output_controls(err.traceback.clone(), workspace.clone(), cx)
Self::render_output_controls(content.clone(), workspace.clone(), window, cx)
}
Self::ErrorOutput(err) => Self::render_output_controls(
err.traceback.clone(),
workspace.clone(),
window,
cx,
),
Self::Message(_) => None,
Self::Table { content, .. } => {
Self::render_output_controls(content.clone(), workspace.clone(), cx)
Self::render_output_controls(content.clone(), workspace.clone(), window, cx)
}
Self::ClearOutputWaitMarker => None,
})
@ -259,28 +271,33 @@ impl Output {
}
}
pub fn new(data: &MimeBundle, display_id: Option<String>, cx: &mut WindowContext) -> Self {
pub fn new(
data: &MimeBundle,
display_id: Option<String>,
window: &mut Window,
cx: &mut App,
) -> Self {
match data.richest(rank_mime_type) {
Some(MimeType::Plain(text)) => Output::Plain {
content: cx.new_view(|cx| TerminalOutput::from(text, cx)),
content: cx.new(|cx| TerminalOutput::from(text, window, cx)),
display_id,
},
Some(MimeType::Markdown(text)) => {
let view = cx.new_view(|cx| MarkdownView::from(text.clone(), cx));
let content = cx.new(|cx| MarkdownView::from(text.clone(), cx));
Output::Markdown {
content: view,
content,
display_id,
}
}
Some(MimeType::Png(data)) | Some(MimeType::Jpeg(data)) => match ImageView::from(data) {
Ok(view) => Output::Image {
content: cx.new_view(|_| view),
content: cx.new(|_| view),
display_id,
},
Err(error) => Output::Message(format!("Failed to load image: {}", error)),
},
Some(MimeType::DataTable(data)) => Output::Table {
content: cx.new_view(|cx| TableView::new(data, cx)),
content: cx.new(|cx| TableView::new(data, window, cx)),
display_id,
},
// Any other media types are not supported
@ -308,7 +325,7 @@ pub enum ExecutionStatus {
/// sees as "the output" for a single execution.
pub struct ExecutionView {
#[allow(unused)]
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
pub outputs: Vec<Output>,
pub status: ExecutionStatus,
}
@ -316,8 +333,8 @@ pub struct ExecutionView {
impl ExecutionView {
pub fn new(
status: ExecutionStatus,
workspace: WeakView<Workspace>,
_cx: &mut ViewContext<Self>,
workspace: WeakEntity<Workspace>,
_cx: &mut Context<Self>,
) -> Self {
Self {
workspace,
@ -327,21 +344,28 @@ impl ExecutionView {
}
/// Accept a Jupyter message belonging to this execution
pub fn push_message(&mut self, message: &JupyterMessageContent, cx: &mut ViewContext<Self>) {
pub fn push_message(
&mut self,
message: &JupyterMessageContent,
window: &mut Window,
cx: &mut Context<Self>,
) {
let output: Output = match message {
JupyterMessageContent::ExecuteResult(result) => Output::new(
&result.data,
result.transient.as_ref().and_then(|t| t.display_id.clone()),
window,
cx,
),
JupyterMessageContent::DisplayData(result) => Output::new(
&result.data,
result.transient.as_ref().and_then(|t| t.display_id.clone()),
window,
cx,
),
JupyterMessageContent::StreamContent(result) => {
// Previous stream data will combine together, handling colors, carriage returns, etc
if let Some(new_terminal) = self.apply_terminal_text(&result.text, cx) {
if let Some(new_terminal) = self.apply_terminal_text(&result.text, window, cx) {
new_terminal
} else {
return;
@ -349,7 +373,7 @@ impl ExecutionView {
}
JupyterMessageContent::ErrorOutput(result) => {
let terminal =
cx.new_view(|cx| TerminalOutput::from(&result.traceback.join("\n"), cx));
cx.new(|cx| TerminalOutput::from(&result.traceback.join("\n"), window, cx));
Output::ErrorOutput(ErrorView {
ename: result.ename.clone(),
@ -360,7 +384,7 @@ impl ExecutionView {
JupyterMessageContent::ExecuteReply(reply) => {
for payload in reply.payload.iter() {
if let runtimelib::Payload::Page { data, .. } = payload {
let output = Output::new(data, None, cx);
let output = Output::new(data, None, window, cx);
self.outputs.push(output);
}
}
@ -408,14 +432,15 @@ impl ExecutionView {
&mut self,
data: &MimeBundle,
display_id: &str,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let mut any = false;
self.outputs.iter_mut().for_each(|output| {
if let Some(other_display_id) = output.display_id().as_ref() {
if other_display_id == display_id {
*output = Output::new(data, Some(display_id.to_owned()), cx);
*output = Output::new(data, Some(display_id.to_owned()), window, cx);
any = true;
}
}
@ -426,7 +451,12 @@ impl ExecutionView {
}
}
fn apply_terminal_text(&mut self, text: &str, cx: &mut ViewContext<Self>) -> Option<Output> {
fn apply_terminal_text(
&mut self,
text: &str,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Output> {
if let Some(last_output) = self.outputs.last_mut() {
if let Output::Stream {
content: last_stream,
@ -443,13 +473,13 @@ impl ExecutionView {
}
Some(Output::Stream {
content: cx.new_view(|cx| TerminalOutput::from(text, cx)),
content: cx.new(|cx| TerminalOutput::from(text, window, cx)),
})
}
}
impl Render for ExecutionView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let status = match &self.status {
ExecutionStatus::ConnectingToKernel => Label::new("Connecting to kernel...")
.color(Color::Muted)
@ -493,7 +523,7 @@ impl Render for ExecutionView {
if self.outputs.is_empty() {
return v_flex()
.min_h(cx.line_height())
.min_h(window.line_height())
.justify_center()
.child(status)
.into_any_element();
@ -504,7 +534,7 @@ impl Render for ExecutionView {
.children(
self.outputs
.iter()
.map(|output| output.render(self.workspace.clone(), cx)),
.map(|output| output.render(self.workspace.clone(), window, cx)),
)
.children(match self.status {
ExecutionStatus::Executing => vec![status],

View file

@ -4,7 +4,7 @@ use base64::{
engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig},
Engine as _,
};
use gpui::{img, ClipboardItem, Image, ImageFormat, Pixels, RenderImage, WindowContext};
use gpui::{img, App, ClipboardItem, Image, ImageFormat, Pixels, RenderImage, Window};
use std::sync::Arc;
use ui::{div, prelude::*, IntoElement, Styled};
@ -74,8 +74,8 @@ impl ImageView {
}
impl Render for ImageView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let line_height = cx.line_height();
fn render(&mut self, window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let line_height = window.line_height();
let (height, width) = if self.height as f32 / line_height.0 == u8::MAX as f32 {
let height = u8::MAX as f32 * line_height.0;
@ -92,11 +92,11 @@ impl Render for ImageView {
}
impl OutputContent for ImageView {
fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
fn clipboard_content(&self, _window: &Window, _cx: &App) -> Option<ClipboardItem> {
Some(ClipboardItem::new_image(self.clipboard_image.as_ref()))
}
fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool {
true
}
}

View file

@ -1,5 +1,5 @@
use anyhow::Result;
use gpui::{div, prelude::*, ClipboardItem, Model, Task, ViewContext, WindowContext};
use gpui::{div, prelude::*, App, ClipboardItem, Context, Entity, Task, Window};
use language::Buffer;
use markdown_preview::{
markdown_elements::ParsedMarkdown, markdown_parser::parse_markdown,
@ -16,7 +16,7 @@ pub struct MarkdownView {
}
impl MarkdownView {
pub fn from(text: String, cx: &mut ViewContext<Self>) -> Self {
pub fn from(text: String, cx: &mut Context<Self>) -> Self {
let task = cx.spawn(|markdown_view, mut cx| {
let text = text.clone();
let parsed = cx
@ -43,20 +43,20 @@ impl MarkdownView {
}
impl OutputContent for MarkdownView {
fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
fn clipboard_content(&self, _window: &Window, _cx: &App) -> Option<ClipboardItem> {
Some(ClipboardItem::new_string(self.raw_text.clone()))
}
fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool {
true
}
fn has_buffer_content(&self, _cx: &WindowContext) -> bool {
fn has_buffer_content(&self, _window: &Window, _cx: &App) -> bool {
true
}
fn buffer_content(&mut self, cx: &mut WindowContext) -> Option<Model<Buffer>> {
let buffer = cx.new_model(|cx| {
fn buffer_content(&mut self, _: &mut Window, cx: &mut App) -> Option<Entity<Buffer>> {
let buffer = cx.new(|cx| {
// TODO: Bring in the language registry so we can set the language to markdown
let mut buffer = Buffer::local(self.raw_text.clone(), cx)
.with_language(language::PLAIN_TEXT.clone(), cx);
@ -68,13 +68,13 @@ impl OutputContent for MarkdownView {
}
impl Render for MarkdownView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let Some(parsed) = self.contents.as_ref() else {
return div().into_any_element();
};
let mut markdown_render_context =
markdown_preview::markdown_renderer::RenderContext::new(None, cx);
markdown_preview::markdown_renderer::RenderContext::new(None, window, cx);
v_flex()
.gap_3()

View file

@ -22,7 +22,7 @@ use alacritty_terminal::{
term::Config,
vte::ansi::Processor,
};
use gpui::{canvas, size, ClipboardItem, FontStyle, Model, TextStyle, WhiteSpace};
use gpui::{canvas, size, ClipboardItem, Entity, FontStyle, TextStyle, WhiteSpace};
use language::Buffer;
use settings::Settings as _;
use terminal_view::terminal_element::TerminalElement;
@ -45,7 +45,7 @@ use crate::outputs::OutputContent;
/// supporting ANSI escape sequences for text formatting and colors.
///
pub struct TerminalOutput {
full_buffer: Option<Model<Buffer>>,
full_buffer: Option<Entity<Buffer>>,
/// ANSI escape sequence processor for parsing input text.
parser: Processor,
/// Alacritty terminal instance that manages the terminal state and content.
@ -56,7 +56,7 @@ const DEFAULT_NUM_LINES: usize = 32;
const DEFAULT_NUM_COLUMNS: usize = 128;
/// Returns the default text style for the terminal output.
pub fn text_style(cx: &mut WindowContext) -> TextStyle {
pub fn text_style(window: &mut Window, cx: &mut App) -> TextStyle {
let settings = ThemeSettings::get_global(cx).clone();
let font_size = settings.buffer_font_size().into();
@ -74,7 +74,7 @@ pub fn text_style(cx: &mut WindowContext) -> TextStyle {
font_fallbacks,
font_size,
font_style: FontStyle::Normal,
line_height: cx.line_height().into(),
line_height: window.line_height().into(),
background_color: Some(theme.colors().terminal_ansi_background),
white_space: WhiteSpace::Normal,
truncate: None,
@ -88,13 +88,13 @@ pub fn text_style(cx: &mut WindowContext) -> TextStyle {
}
/// Returns the default terminal size for the terminal output.
pub fn terminal_size(cx: &mut WindowContext) -> terminal::TerminalSize {
let text_style = text_style(cx);
let text_system = cx.text_system();
pub fn terminal_size(window: &mut Window, cx: &mut App) -> terminal::TerminalSize {
let text_style = text_style(window, cx);
let text_system = window.text_system();
let line_height = cx.line_height();
let line_height = window.line_height();
let font_pixels = text_style.font_size.to_pixels(cx.rem_size());
let font_pixels = text_style.font_size.to_pixels(window.rem_size());
let font_id = text_system.resolve_font(&text_style.font());
let cell_width = text_system
@ -107,7 +107,7 @@ pub fn terminal_size(cx: &mut WindowContext) -> terminal::TerminalSize {
// Reversed math from terminal::TerminalSize to get pixel width according to terminal width
let width = columns as f32 * cell_width;
let height = num_lines as f32 * cx.line_height();
let height = num_lines as f32 * window.line_height();
terminal::TerminalSize {
cell_width,
@ -122,9 +122,12 @@ impl TerminalOutput {
/// This method initializes a new terminal emulator with default configuration
/// and sets up the necessary components for handling terminal events and rendering.
///
pub fn new(cx: &mut WindowContext) -> Self {
let term =
alacritty_terminal::Term::new(Config::default(), &terminal_size(cx), VoidListener);
pub fn new(window: &mut Window, cx: &mut App) -> Self {
let term = alacritty_terminal::Term::new(
Config::default(),
&terminal_size(window, cx),
VoidListener,
);
Self {
parser: Processor::new(),
@ -145,8 +148,8 @@ impl TerminalOutput {
/// # Returns
///
/// A new instance of `TerminalOutput` containing the provided text.
pub fn from(text: &str, cx: &mut WindowContext) -> Self {
let mut output = Self::new(cx);
pub fn from(text: &str, window: &mut Window, cx: &mut App) -> Self {
let mut output = Self::new(window, cx);
output.append_text(text, cx);
output
}
@ -177,7 +180,7 @@ impl TerminalOutput {
/// # Arguments
///
/// * `text` - A string slice containing the text to be appended.
pub fn append_text(&mut self, text: &str, cx: &mut WindowContext) {
pub fn append_text(&mut self, text: &str, cx: &mut App) {
for byte in text.as_bytes() {
if *byte == b'\n' {
// Dirty (?) hack to move the cursor down
@ -241,9 +244,9 @@ impl Render for TerminalOutput {
/// Converts the current terminal state into a renderable GPUI element. It handles
/// the layout of the terminal grid, calculates the dimensions of the output, and
/// creates a canvas element that paints the terminal cells and background rectangles.
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let text_style = text_style(cx);
let text_system = cx.text_system();
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let text_style = text_style(window, cx);
let text_system = window.text_system();
let grid = self
.handler
@ -253,14 +256,15 @@ impl Render for TerminalOutput {
point: ic.point,
cell: ic.cell.clone(),
});
let (cells, rects) = TerminalElement::layout_grid(grid, &text_style, text_system, None, cx);
let (cells, rects) =
TerminalElement::layout_grid(grid, &text_style, text_system, None, window, cx);
// lines are 0-indexed, so we must add 1 to get the number of lines
let text_line_height = text_style.line_height_in_pixels(cx.rem_size());
let text_line_height = text_style.line_height_in_pixels(window.rem_size());
let num_lines = cells.iter().map(|c| c.point.line).max().unwrap_or(0) + 1;
let height = num_lines as f32 * text_line_height;
let font_pixels = text_style.font_size.to_pixels(cx.rem_size());
let font_pixels = text_style.font_size.to_pixels(window.rem_size());
let font_id = text_system.resolve_font(&text_style.font());
let cell_width = text_system
@ -270,9 +274,9 @@ impl Render for TerminalOutput {
canvas(
// prepaint
move |_bounds, _| {},
move |_bounds, _, _| {},
// paint
move |bounds, _, cx| {
move |bounds, _, window, cx| {
for rect in rects {
rect.paint(
bounds.origin,
@ -281,7 +285,7 @@ impl Render for TerminalOutput {
line_height: text_line_height,
size: bounds.size,
},
cx,
window,
);
}
@ -294,6 +298,7 @@ impl Render for TerminalOutput {
size: bounds.size,
},
bounds,
window,
cx,
);
}
@ -305,24 +310,24 @@ impl Render for TerminalOutput {
}
impl OutputContent for TerminalOutput {
fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
fn clipboard_content(&self, _window: &Window, _cx: &App) -> Option<ClipboardItem> {
Some(ClipboardItem::new_string(self.full_text()))
}
fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool {
true
}
fn has_buffer_content(&self, _cx: &WindowContext) -> bool {
fn has_buffer_content(&self, _window: &Window, _cx: &App) -> bool {
true
}
fn buffer_content(&mut self, cx: &mut WindowContext) -> Option<Model<Buffer>> {
fn buffer_content(&mut self, _: &mut Window, cx: &mut App) -> Option<Entity<Buffer>> {
if self.full_buffer.as_ref().is_some() {
return self.full_buffer.clone();
}
let buffer = cx.new_model(|cx| {
let buffer = cx.new(|cx| {
let mut buffer =
Buffer::local(self.full_text(), cx).with_language(language::PLAIN_TEXT.clone(), cx);
buffer.set_capability(language::Capability::ReadOnly, cx);

View file

@ -88,11 +88,11 @@ fn cell_content(row: &Value, field: &str) -> String {
const TABLE_Y_PADDING_MULTIPLE: f32 = 0.5;
impl TableView {
pub fn new(table: &TabularDataResource, cx: &mut WindowContext) -> Self {
pub fn new(table: &TabularDataResource, window: &mut Window, cx: &mut App) -> Self {
let mut widths = Vec::with_capacity(table.schema.fields.len());
let text_system = cx.text_system();
let text_style = cx.text_style();
let text_system = window.text_system();
let text_style = window.text_style();
let text_font = ThemeSettings::get_global(cx).buffer_font.clone();
let font_size = ThemeSettings::get_global(cx).buffer_font_size;
let mut runs = [TextRun {
@ -119,7 +119,7 @@ impl TableView {
for row in data {
let content = cell_content(row, &field.name);
runs[0].len = content.len();
let cell_width = cx
let cell_width = window
.text_system()
.layout_line(&content, font_size, &runs)
.map(|layout| layout.width)
@ -189,11 +189,12 @@ impl TableView {
schema: &TableSchema,
is_header: bool,
row: &Value,
cx: &WindowContext,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
let theme = cx.theme();
let line_height = cx.line_height();
let line_height = window.line_height();
let row_cells = schema
.fields
@ -248,7 +249,7 @@ impl TableView {
}
impl Render for TableView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let data = match &self.table.data {
Some(data) => data,
None => return div().into_any_element(),
@ -258,11 +259,17 @@ impl Render for TableView {
for field in &self.table.schema.fields {
headings.insert(field.name.clone(), Value::String(field.name.clone()));
}
let header = self.render_row(&self.table.schema, true, &Value::Object(headings), cx);
let header = self.render_row(
&self.table.schema,
true,
&Value::Object(headings),
window,
cx,
);
let body = data
.iter()
.map(|row| self.render_row(&self.table.schema, false, row, cx));
.map(|row| self.render_row(&self.table.schema, false, row, window, cx));
v_flex()
.id("table")
@ -275,11 +282,11 @@ impl Render for TableView {
}
impl OutputContent for TableView {
fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
fn clipboard_content(&self, _window: &Window, _cx: &App) -> Option<ClipboardItem> {
Some(self.cached_clipboard_content.clone())
}
fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool {
true
}
}

View file

@ -1,4 +1,4 @@
use gpui::{AnyElement, FontWeight, View, WindowContext};
use gpui::{AnyElement, App, Entity, FontWeight, Window};
use ui::{h_flex, prelude::*, v_flex, Label};
use crate::outputs::plain::TerminalOutput;
@ -7,14 +7,14 @@ use crate::outputs::plain::TerminalOutput;
pub struct ErrorView {
pub ename: String,
pub evalue: String,
pub traceback: View<TerminalOutput>,
pub traceback: Entity<TerminalOutput>,
}
impl ErrorView {
pub fn render(&self, cx: &mut WindowContext) -> Option<AnyElement> {
pub fn render(&self, window: &mut Window, cx: &mut App) -> Option<AnyElement> {
let theme = cx.theme();
let padding = cx.line_height() / 2.;
let padding = window.line_height() / 2.;
Some(
v_flex()

View file

@ -11,7 +11,7 @@ mod session;
use std::{sync::Arc, time::Duration};
use async_dispatcher::{set_dispatcher, Dispatcher, Runnable};
use gpui::{AppContext, PlatformDispatcher};
use gpui::{App, PlatformDispatcher};
use project::Fs;
pub use runtimelib::ExecutionState;
use settings::Settings as _;
@ -27,7 +27,7 @@ pub use crate::session::Session;
pub const KERNEL_DOCS_URL: &str = "https://zed.dev/docs/repl#changing-kernels";
pub fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
set_dispatcher(zed_dispatcher(cx));
JupyterSettings::register(cx);
::editor::init_settings(cx);
@ -35,7 +35,7 @@ pub fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
ReplStore::init(fs, cx);
}
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
fn zed_dispatcher(cx: &mut App) -> impl Dispatcher {
struct ZedDispatcher {
dispatcher: Arc<dyn PlatformDispatcher>,
}

View file

@ -3,9 +3,9 @@
use std::ops::Range;
use std::sync::Arc;
use anyhow::{Context, Result};
use anyhow::{Context as _, Result};
use editor::Editor;
use gpui::{prelude::*, Entity, View, WeakView, WindowContext};
use gpui::{prelude::*, App, Entity, WeakEntity, Window};
use language::{BufferSnapshot, Language, LanguageName, Point};
use project::{ProjectItem as _, WorktreeId};
@ -17,8 +17,9 @@ use crate::{
pub fn assign_kernelspec(
kernel_specification: KernelSpecification,
weak_editor: WeakView<Editor>,
cx: &mut WindowContext,
weak_editor: WeakEntity<Editor>,
window: &mut Window,
cx: &mut App,
) -> Result<()> {
let store = ReplStore::global(cx);
if !store.read(cx).is_enabled() {
@ -38,12 +39,13 @@ pub fn assign_kernelspec(
// Drop previous session, start new one
session.update(cx, |session, cx| {
session.clear_outputs(cx);
session.shutdown(cx);
session.shutdown(window, cx);
cx.notify();
});
}
let session = cx.new_view(|cx| Session::new(weak_editor.clone(), fs, kernel_specification, cx));
let session =
cx.new(|cx| Session::new(weak_editor.clone(), fs, kernel_specification, window, cx));
weak_editor
.update(cx, |_editor, cx| {
@ -70,7 +72,12 @@ pub fn assign_kernelspec(
Ok(())
}
pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) -> Result<()> {
pub fn run(
editor: WeakEntity<Editor>,
move_down: bool,
window: &mut Window,
cx: &mut App,
) -> Result<()> {
let store = ReplStore::global(cx);
if !store.read(cx).is_enabled() {
return Ok(());
@ -109,7 +116,8 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
session
} else {
let weak_editor = editor.downgrade();
let session = cx.new_view(|cx| Session::new(weak_editor, fs, kernel_specification, cx));
let session =
cx.new(|cx| Session::new(weak_editor, fs, kernel_specification, window, cx));
editor.update(cx, |_editor, cx| {
cx.notify();
@ -148,7 +156,14 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
}
session.update(cx, |session, cx| {
session.execute(selected_text, anchor_range, next_cursor, move_down, cx);
session.execute(
selected_text,
anchor_range,
next_cursor,
move_down,
window,
cx,
);
});
}
@ -157,16 +172,13 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
#[allow(clippy::large_enum_variant)]
pub enum SessionSupport {
ActiveSession(View<Session>),
ActiveSession(Entity<Session>),
Inactive(KernelSpecification),
RequiresSetup(LanguageName),
Unsupported,
}
pub fn worktree_id_for_editor(
editor: WeakView<Editor>,
cx: &mut WindowContext,
) -> Option<WorktreeId> {
pub fn worktree_id_for_editor(editor: WeakEntity<Editor>, cx: &mut App) -> Option<WorktreeId> {
editor.upgrade().and_then(|editor| {
editor
.read(cx)
@ -179,7 +191,7 @@ pub fn worktree_id_for_editor(
})
}
pub fn session(editor: WeakView<Editor>, cx: &mut WindowContext) -> SessionSupport {
pub fn session(editor: WeakEntity<Editor>, cx: &mut App) -> SessionSupport {
let store = ReplStore::global(cx);
let entity_id = editor.entity_id();
@ -213,7 +225,7 @@ pub fn session(editor: WeakView<Editor>, cx: &mut WindowContext) -> SessionSuppo
}
}
pub fn clear_outputs(editor: WeakView<Editor>, cx: &mut WindowContext) {
pub fn clear_outputs(editor: WeakEntity<Editor>, cx: &mut App) {
let store = ReplStore::global(cx);
let entity_id = editor.entity_id();
let Some(session) = store.read(cx).get_session(entity_id).cloned() else {
@ -225,7 +237,7 @@ pub fn clear_outputs(editor: WeakView<Editor>, cx: &mut WindowContext) {
});
}
pub fn interrupt(editor: WeakView<Editor>, cx: &mut WindowContext) {
pub fn interrupt(editor: WeakEntity<Editor>, cx: &mut App) {
let store = ReplStore::global(cx);
let entity_id = editor.entity_id();
let Some(session) = store.read(cx).get_session(entity_id).cloned() else {
@ -238,7 +250,7 @@ pub fn interrupt(editor: WeakView<Editor>, cx: &mut WindowContext) {
});
}
pub fn shutdown(editor: WeakView<Editor>, cx: &mut WindowContext) {
pub fn shutdown(editor: WeakEntity<Editor>, window: &mut Window, cx: &mut App) {
let store = ReplStore::global(cx);
let entity_id = editor.entity_id();
let Some(session) = store.read(cx).get_session(entity_id).cloned() else {
@ -246,12 +258,12 @@ pub fn shutdown(editor: WeakView<Editor>, cx: &mut WindowContext) {
};
session.update(cx, |session, cx| {
session.shutdown(cx);
session.shutdown(window, cx);
cx.notify();
});
}
pub fn restart(editor: WeakView<Editor>, cx: &mut WindowContext) {
pub fn restart(editor: WeakEntity<Editor>, window: &mut Window, cx: &mut App) {
let Some(editor) = editor.upgrade() else {
return;
};
@ -267,16 +279,16 @@ pub fn restart(editor: WeakView<Editor>, cx: &mut WindowContext) {
};
session.update(cx, |session, cx| {
session.restart(cx);
session.restart(window, cx);
cx.notify();
});
}
pub fn setup_editor_session_actions(editor: &mut Editor, editor_handle: WeakView<Editor>) {
pub fn setup_editor_session_actions(editor: &mut Editor, editor_handle: WeakEntity<Editor>) {
editor
.register_action({
let editor_handle = editor_handle.clone();
move |_: &ClearOutputs, cx| {
move |_: &ClearOutputs, _, cx| {
if !JupyterSettings::enabled(cx) {
return;
}
@ -289,7 +301,7 @@ pub fn setup_editor_session_actions(editor: &mut Editor, editor_handle: WeakView
editor
.register_action({
let editor_handle = editor_handle.clone();
move |_: &Interrupt, cx| {
move |_: &Interrupt, _, cx| {
if !JupyterSettings::enabled(cx) {
return;
}
@ -302,12 +314,12 @@ pub fn setup_editor_session_actions(editor: &mut Editor, editor_handle: WeakView
editor
.register_action({
let editor_handle = editor_handle.clone();
move |_: &Shutdown, cx| {
move |_: &Shutdown, window, cx| {
if !JupyterSettings::enabled(cx) {
return;
}
crate::shutdown(editor_handle.clone(), cx);
crate::shutdown(editor_handle.clone(), window, cx);
}
})
.detach();
@ -315,12 +327,12 @@ pub fn setup_editor_session_actions(editor: &mut Editor, editor_handle: WeakView
editor
.register_action({
let editor_handle = editor_handle.clone();
move |_: &Restart, cx| {
move |_: &Restart, window, cx| {
if !JupyterSettings::enabled(cx) {
return;
}
crate::restart(editor_handle.clone(), cx);
crate::restart(editor_handle.clone(), window, cx);
}
})
.detach();
@ -448,7 +460,7 @@ fn language_supported(language: &Arc<Language>) -> bool {
}
}
fn get_language(editor: WeakView<Editor>, cx: &mut WindowContext) -> Option<Arc<Language>> {
fn get_language(editor: WeakEntity<Editor>, cx: &mut App) -> Option<Arc<Language>> {
editor
.update(cx, |editor, cx| {
let selection = editor.selections.newest::<usize>(cx);
@ -462,12 +474,12 @@ fn get_language(editor: WeakView<Editor>, cx: &mut WindowContext) -> Option<Arc<
#[cfg(test)]
mod tests {
use super::*;
use gpui::{AppContext, Context};
use gpui::App;
use indoc::indoc;
use language::{Buffer, Language, LanguageConfig, LanguageRegistry};
#[gpui::test]
fn test_snippet_ranges(cx: &mut AppContext) {
fn test_snippet_ranges(cx: &mut App) {
// Create a test language
let test_language = Arc::new(Language::new(
LanguageConfig {
@ -478,7 +490,7 @@ mod tests {
None,
));
let buffer = cx.new_model(|cx| {
let buffer = cx.new(|cx| {
Buffer::local(
indoc! { r#"
print(1 + 1)
@ -533,7 +545,7 @@ mod tests {
}
#[gpui::test]
fn test_jupytext_snippet_ranges(cx: &mut AppContext) {
fn test_jupytext_snippet_ranges(cx: &mut App) {
// Create a test language
let test_language = Arc::new(Language::new(
LanguageConfig {
@ -544,7 +556,7 @@ mod tests {
None,
));
let buffer = cx.new_model(|cx| {
let buffer = cx.new(|cx| {
Buffer::local(
indoc! { r#"
# Hello!
@ -611,7 +623,7 @@ mod tests {
}
#[gpui::test]
fn test_markdown_code_blocks(cx: &mut AppContext) {
fn test_markdown_code_blocks(cx: &mut App) {
let markdown = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
let typescript = languages::language(
"typescript",
@ -624,7 +636,7 @@ mod tests {
language_registry.add(python.clone());
// Two code blocks intersecting with selection
let buffer = cx.new_model(|cx| {
let buffer = cx.new(|cx| {
let mut buffer = Buffer::local(
indoc! { r#"
Hey this is Markdown!
@ -666,7 +678,7 @@ mod tests {
);
// Three code blocks intersecting with selection
let buffer = cx.new_model(|cx| {
let buffer = cx.new(|cx| {
let mut buffer = Buffer::local(
indoc! { r#"
Hey this is Markdown!
@ -712,7 +724,7 @@ mod tests {
);
// Python code block
let buffer = cx.new_model(|cx| {
let buffer = cx.new(|cx| {
let mut buffer = Buffer::local(
indoc! { r#"
Hey this is Markdown!

View file

@ -1,7 +1,7 @@
use editor::Editor;
use gpui::{
actions, prelude::*, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView,
Subscription, View,
actions, prelude::*, AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable,
Subscription,
};
use project::ProjectItem as _;
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
@ -27,10 +27,10 @@ actions!(
]
);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace.register_action(|workspace, _: &Sessions, cx| {
pub fn init(cx: &mut App) {
cx.observe_new(
|workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
workspace.register_action(|workspace, _: &Sessions, window, cx| {
let existing = workspace
.active_pane()
.read(cx)
@ -38,14 +38,20 @@ pub fn init(cx: &mut AppContext) {
.find_map(|item| item.downcast::<ReplSessionsPage>());
if let Some(existing) = existing {
workspace.activate_item(&existing, true, true, cx);
workspace.activate_item(&existing, true, true, window, cx);
} else {
let repl_sessions_page = ReplSessionsPage::new(cx);
workspace.add_item_to_active_pane(Box::new(repl_sessions_page), None, true, cx)
let repl_sessions_page = ReplSessionsPage::new(window, cx);
workspace.add_item_to_active_pane(
Box::new(repl_sessions_page),
None,
true,
window,
cx,
)
}
});
workspace.register_action(|_workspace, _: &RefreshKernelspecs, cx| {
workspace.register_action(|_workspace, _: &RefreshKernelspecs, _, cx| {
let store = ReplStore::global(cx);
store.update(cx, |store, cx| {
store.refresh_kernelspecs(cx).detach();
@ -55,74 +61,84 @@ pub fn init(cx: &mut AppContext) {
)
.detach();
cx.observe_new_views(move |editor: &mut Editor, cx: &mut ViewContext<Editor>| {
if !editor.use_modal_editing() || !editor.buffer().read(cx).is_singleton() {
return;
}
cx.observe_new(
move |editor: &mut Editor, window, cx: &mut Context<Editor>| {
let Some(window) = window else {
return;
};
cx.defer(|editor, cx| {
let workspace = Workspace::for_window(cx);
let project = workspace.map(|workspace| workspace.read(cx).project().clone());
let is_local_project = project
.as_ref()
.map(|project| project.read(cx).is_local())
.unwrap_or(false);
if !is_local_project {
if !editor.use_modal_editing() || !editor.buffer().read(cx).is_singleton() {
return;
}
let buffer = editor.buffer().read(cx).as_singleton();
cx.defer_in(window, |editor, window, cx| {
let workspace = Workspace::for_window(window, cx);
let project = workspace.map(|workspace| workspace.read(cx).project().clone());
let language = buffer
.as_ref()
.and_then(|buffer| buffer.read(cx).language());
let is_local_project = project
.as_ref()
.map(|project| project.read(cx).is_local())
.unwrap_or(false);
let project_path = buffer.and_then(|buffer| buffer.read(cx).project_path(cx));
if !is_local_project {
return;
}
let editor_handle = cx.view().downgrade();
let buffer = editor.buffer().read(cx).as_singleton();
if let Some(language) = language {
if language.name() == "Python".into() {
if let (Some(project_path), Some(project)) = (project_path, project) {
let store = ReplStore::global(cx);
store.update(cx, |store, cx| {
store
.refresh_python_kernelspecs(project_path.worktree_id, &project, cx)
.detach_and_log_err(cx);
});
let language = buffer
.as_ref()
.and_then(|buffer| buffer.read(cx).language());
let project_path = buffer.and_then(|buffer| buffer.read(cx).project_path(cx));
let editor_handle = cx.model().downgrade();
if let Some(language) = language {
if language.name() == "Python".into() {
if let (Some(project_path), Some(project)) = (project_path, project) {
let store = ReplStore::global(cx);
store.update(cx, |store, cx| {
store
.refresh_python_kernelspecs(
project_path.worktree_id,
&project,
cx,
)
.detach_and_log_err(cx);
});
}
}
}
}
editor
.register_action({
let editor_handle = editor_handle.clone();
move |_: &Run, cx| {
if !JupyterSettings::enabled(cx) {
return;
editor
.register_action({
let editor_handle = editor_handle.clone();
move |_: &Run, window, cx| {
if !JupyterSettings::enabled(cx) {
return;
}
crate::run(editor_handle.clone(), true, window, cx).log_err();
}
})
.detach();
crate::run(editor_handle.clone(), true, cx).log_err();
}
})
.detach();
editor
.register_action({
let editor_handle = editor_handle.clone();
move |_: &RunInPlace, window, cx| {
if !JupyterSettings::enabled(cx) {
return;
}
editor
.register_action({
let editor_handle = editor_handle.clone();
move |_: &RunInPlace, cx| {
if !JupyterSettings::enabled(cx) {
return;
crate::run(editor_handle.clone(), false, window, cx).log_err();
}
crate::run(editor_handle.clone(), false, cx).log_err();
}
})
.detach();
});
})
})
.detach();
});
},
)
.detach();
}
@ -132,13 +148,15 @@ pub struct ReplSessionsPage {
}
impl ReplSessionsPage {
pub fn new(cx: &mut ViewContext<Workspace>) -> View<Self> {
cx.new_view(|cx: &mut ViewContext<Self>| {
pub fn new(window: &mut Window, cx: &mut Context<Workspace>) -> Entity<Self> {
cx.new(|cx| {
let focus_handle = cx.focus_handle();
let subscriptions = vec![
cx.on_focus_in(&focus_handle, |_this, cx| cx.notify()),
cx.on_focus_out(&focus_handle, |_this, _event, cx| cx.notify()),
cx.on_focus_in(&focus_handle, window, |_this, _window, cx| cx.notify()),
cx.on_focus_out(&focus_handle, window, |_this, _event, _window, cx| {
cx.notify()
}),
];
Self {
@ -151,8 +169,8 @@ impl ReplSessionsPage {
impl EventEmitter<ItemEvent> for ReplSessionsPage {}
impl FocusableView for ReplSessionsPage {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
impl Focusable for ReplSessionsPage {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
@ -160,7 +178,7 @@ impl FocusableView for ReplSessionsPage {
impl Item for ReplSessionsPage {
type Event = ItemEvent;
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
Some("REPL Sessions".into())
}
@ -175,8 +193,9 @@ impl Item for ReplSessionsPage {
fn clone_on_split(
&self,
_workspace_id: Option<WorkspaceId>,
_: &mut ViewContext<Self>,
) -> Option<View<Self>> {
_window: &mut Window,
_: &mut Context<Self>,
) -> Option<Entity<Self>> {
None
}
@ -186,7 +205,7 @@ impl Item for ReplSessionsPage {
}
impl Render for ReplSessionsPage {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let store = ReplStore::global(cx);
let (kernel_specifications, sessions) = store.update(cx, |store, _cx| {
@ -214,7 +233,7 @@ impl Render for ReplSessionsPage {
.size(ButtonSize::Large)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Install Kernels"))
.on_click(move |_, cx| {
.on_click(move |_, _, cx| {
cx.open_url(
"https://zed.dev/docs/repl#language-specific-instructions",
)
@ -230,7 +249,7 @@ impl Render for ReplSessionsPage {
return ReplSessionsContainer::new("No Jupyter Kernel Sessions").child(
v_flex()
.child(Label::new(instructions))
.children(KeyBinding::for_action(&Run, cx)),
.children(KeyBinding::for_action(&Run, window)),
);
}
@ -260,7 +279,7 @@ impl ParentElement for ReplSessionsContainer {
}
impl RenderOnce for ReplSessionsContainer {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
v_flex()
.p_4()
.gap_2()

View file

@ -3,9 +3,7 @@ use std::sync::Arc;
use anyhow::Result;
use collections::HashMap;
use command_palette_hooks::CommandPaletteFilter;
use gpui::{
prelude::*, AppContext, EntityId, Global, Model, ModelContext, Subscription, Task, View,
};
use gpui::{prelude::*, App, Context, Entity, EntityId, Global, Subscription, Task};
use jupyter_websocket_client::RemoteServer;
use language::Language;
use project::{Fs, Project, WorktreeId};
@ -16,14 +14,14 @@ use crate::kernels::{
};
use crate::{JupyterSettings, KernelSpecification, Session};
struct GlobalReplStore(Model<ReplStore>);
struct GlobalReplStore(Entity<ReplStore>);
impl Global for GlobalReplStore {}
pub struct ReplStore {
fs: Arc<dyn Fs>,
enabled: bool,
sessions: HashMap<EntityId, View<Session>>,
sessions: HashMap<EntityId, Entity<Session>>,
kernel_specifications: Vec<KernelSpecification>,
selected_kernel_for_worktree: HashMap<WorktreeId, KernelSpecification>,
kernel_specifications_for_worktree: HashMap<WorktreeId, Vec<KernelSpecification>>,
@ -33,8 +31,8 @@ pub struct ReplStore {
impl ReplStore {
const NAMESPACE: &'static str = "repl";
pub(crate) fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
let store = cx.new_model(move |cx| Self::new(fs, cx));
pub(crate) fn init(fs: Arc<dyn Fs>, cx: &mut App) {
let store = cx.new(move |cx| Self::new(fs, cx));
store
.update(cx, |store, cx| store.refresh_kernelspecs(cx))
@ -43,11 +41,11 @@ impl ReplStore {
cx.set_global(GlobalReplStore(store))
}
pub fn global(cx: &AppContext) -> Model<Self> {
pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalReplStore>().0.clone()
}
pub fn new(fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
pub fn new(fs: Arc<dyn Fs>, cx: &mut Context<Self>) -> Self {
let subscriptions = vec![cx.observe_global::<SettingsStore>(move |this, cx| {
this.set_enabled(JupyterSettings::enabled(cx), cx);
})];
@ -88,11 +86,11 @@ impl ReplStore {
self.kernel_specifications.iter()
}
pub fn sessions(&self) -> impl Iterator<Item = &View<Session>> {
pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> {
self.sessions.values()
}
fn set_enabled(&mut self, enabled: bool, cx: &mut ModelContext<Self>) {
fn set_enabled(&mut self, enabled: bool, cx: &mut Context<Self>) {
if self.enabled == enabled {
return;
}
@ -101,7 +99,7 @@ impl ReplStore {
self.on_enabled_changed(cx);
}
fn on_enabled_changed(&self, cx: &mut ModelContext<Self>) {
fn on_enabled_changed(&self, cx: &mut Context<Self>) {
if !self.enabled {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(Self::NAMESPACE);
@ -120,8 +118,8 @@ impl ReplStore {
pub fn refresh_python_kernelspecs(
&mut self,
worktree_id: WorktreeId,
project: &Model<Project>,
cx: &mut ModelContext<Self>,
project: &Entity<Project>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let kernel_specifications = python_env_kernel_specifications(project, worktree_id, cx);
cx.spawn(move |this, mut cx| async move {
@ -139,7 +137,7 @@ impl ReplStore {
fn get_remote_kernel_specifications(
&self,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> Option<Task<Result<Vec<KernelSpecification>>>> {
match (
std::env::var("JUPYTER_SERVER"),
@ -161,7 +159,7 @@ impl ReplStore {
}
}
pub fn refresh_kernelspecs(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
pub fn refresh_kernelspecs(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let local_kernel_specifications = local_kernel_specifications(self.fs.clone());
let remote_kernel_specifications = self.get_remote_kernel_specifications(cx);
@ -201,7 +199,7 @@ impl ReplStore {
&mut self,
worktree_id: WorktreeId,
kernelspec: KernelSpecification,
_cx: &mut ModelContext<Self>,
_cx: &mut Context<Self>,
) {
self.selected_kernel_for_worktree
.insert(worktree_id, kernelspec);
@ -211,7 +209,7 @@ impl ReplStore {
&self,
worktree_id: WorktreeId,
language_at_cursor: Option<Arc<Language>>,
cx: &AppContext,
cx: &App,
) -> Option<KernelSpecification> {
let selected_kernelspec = self.selected_kernel_for_worktree.get(&worktree_id).cloned();
@ -226,7 +224,7 @@ impl ReplStore {
fn kernelspec_legacy_by_lang_only(
&self,
language_at_cursor: Arc<Language>,
cx: &AppContext,
cx: &App,
) -> Option<KernelSpecification> {
let settings = JupyterSettings::get_global(cx);
let selected_kernel = settings
@ -270,11 +268,11 @@ impl ReplStore {
.cloned()
}
pub fn get_session(&self, entity_id: EntityId) -> Option<&View<Session>> {
pub fn get_session(&self, entity_id: EntityId) -> Option<&Entity<Session>> {
self.sessions.get(&entity_id)
}
pub fn insert_session(&mut self, entity_id: EntityId, session: View<Session>) {
pub fn insert_session(&mut self, entity_id: EntityId, session: Entity<Session>) {
self.sessions.insert(entity_id, session);
}

View file

@ -17,7 +17,7 @@ use editor::{
};
use futures::FutureExt as _;
use gpui::{
div, prelude::*, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, WeakView,
div, prelude::*, Context, Entity, EventEmitter, Render, Subscription, Task, WeakEntity, Window,
};
use language::Point;
use project::Fs;
@ -32,7 +32,7 @@ use util::ResultExt as _;
pub struct Session {
fs: Arc<dyn Fs>,
editor: WeakView<Editor>,
editor: WeakEntity<Editor>,
pub kernel: Kernel,
blocks: HashMap<String, EditorBlock>,
pub kernel_specification: KernelSpecification,
@ -43,19 +43,19 @@ struct EditorBlock {
code_range: Range<Anchor>,
invalidation_anchor: Anchor,
block_id: CustomBlockId,
execution_view: View<ExecutionView>,
execution_view: Entity<ExecutionView>,
}
type CloseBlockFn =
Arc<dyn for<'a> Fn(CustomBlockId, &'a mut WindowContext) + Send + Sync + 'static>;
Arc<dyn for<'a> Fn(CustomBlockId, &'a mut Window, &mut App) + Send + Sync + 'static>;
impl EditorBlock {
fn new(
editor: WeakView<Editor>,
editor: WeakEntity<Editor>,
code_range: Range<Anchor>,
status: ExecutionStatus,
on_close: CloseBlockFn,
cx: &mut ViewContext<Session>,
cx: &mut Context<Session>,
) -> anyhow::Result<Self> {
let editor = editor
.upgrade()
@ -65,8 +65,7 @@ impl EditorBlock {
.workspace()
.ok_or_else(|| anyhow::anyhow!("workspace dropped"))?;
let execution_view =
cx.new_view(|cx| ExecutionView::new(status, workspace.downgrade(), cx));
let execution_view = cx.new(|cx| ExecutionView::new(status, workspace.downgrade(), cx));
let (block_id, invalidation_anchor) = editor.update(cx, |editor, cx| {
let buffer = editor.buffer().clone();
@ -108,26 +107,31 @@ impl EditorBlock {
})
}
fn handle_message(&mut self, message: &JupyterMessage, cx: &mut ViewContext<Session>) {
fn handle_message(
&mut self,
message: &JupyterMessage,
window: &mut Window,
cx: &mut Context<Session>,
) {
self.execution_view.update(cx, |execution_view, cx| {
execution_view.push_message(&message.content, cx);
execution_view.push_message(&message.content, window, cx);
});
}
fn create_output_area_renderer(
execution_view: View<ExecutionView>,
execution_view: Entity<ExecutionView>,
on_close: CloseBlockFn,
) -> RenderBlock {
Arc::new(move |cx: &mut BlockContext| {
let execution_view = execution_view.clone();
let text_style = crate::outputs::plain::text_style(cx);
let text_style = crate::outputs::plain::text_style(cx.window, cx.app);
let gutter = cx.gutter_dimensions;
let block_id = cx.block_id;
let on_close = on_close.clone();
let rem_size = cx.rem_size();
let rem_size = cx.window.rem_size();
let text_line_height = text_style.line_height_in_pixels(rem_size);
@ -150,10 +154,10 @@ impl EditorBlock {
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Close output area", cx))
.on_click(move |_, cx| {
.tooltip(Tooltip::text("Close output area"))
.on_click(move |_, window, cx| {
if let BlockId::Custom(block_id) = block_id {
(on_close)(block_id, cx)
(on_close)(block_id, window, cx)
}
}),
);
@ -190,10 +194,11 @@ impl EditorBlock {
impl Session {
pub fn new(
editor: WeakView<Editor>,
editor: WeakEntity<Editor>,
fs: Arc<dyn Fs>,
kernel_specification: KernelSpecification,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let subscription = match editor.upgrade() {
Some(editor) => {
@ -220,11 +225,11 @@ impl Session {
_buffer_subscription: subscription,
};
session.start_kernel(cx);
session.start_kernel(window, cx);
session
}
fn start_kernel(&mut self, cx: &mut ViewContext<Self>) {
fn start_kernel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let kernel_language = self.kernel_specification.language();
let entity_id = self.editor.entity_id();
let working_directory = self
@ -240,7 +245,7 @@ impl Session {
repl_session_id = cx.entity_id().to_string(),
);
let session_view = cx.view().clone();
let session_view = cx.model().clone();
let kernel = match self.kernel_specification.clone() {
KernelSpecification::Jupyter(kernel_specification)
@ -250,12 +255,14 @@ impl Session {
working_directory,
self.fs.clone(),
session_view,
window,
cx,
),
KernelSpecification::Remote(remote_kernel_specification) => RemoteRunningKernel::new(
remote_kernel_specification,
working_directory,
session_view,
window,
cx,
),
};
@ -285,7 +292,7 @@ impl Session {
cx.notify();
}
pub fn kernel_errored(&mut self, error_message: String, cx: &mut ViewContext<Self>) {
pub fn kernel_errored(&mut self, error_message: String, cx: &mut Context<Self>) {
self.kernel(Kernel::ErroredLaunch(error_message.clone()), cx);
self.blocks.values().for_each(|block| {
@ -307,9 +314,9 @@ impl Session {
fn on_buffer_event(
&mut self,
buffer: Model<MultiBuffer>,
buffer: Entity<MultiBuffer>,
event: &multi_buffer::Event,
cx: &mut ViewContext<Self>,
cx: &mut Context<Self>,
) {
if let multi_buffer::Event::Edited { .. } = event {
let snapshot = buffer.read(cx).snapshot(cx);
@ -336,7 +343,7 @@ impl Session {
}
}
fn send(&mut self, message: JupyterMessage, _cx: &mut ViewContext<Self>) -> anyhow::Result<()> {
fn send(&mut self, message: JupyterMessage, _cx: &mut Context<Self>) -> anyhow::Result<()> {
if let Kernel::RunningKernel(kernel) = &mut self.kernel {
kernel.request_tx().try_send(message).ok();
}
@ -344,7 +351,7 @@ impl Session {
anyhow::Ok(())
}
pub fn clear_outputs(&mut self, cx: &mut ViewContext<Self>) {
pub fn clear_outputs(&mut self, cx: &mut Context<Self>) {
let blocks_to_remove: HashSet<CustomBlockId> =
self.blocks.values().map(|block| block.block_id).collect();
@ -363,7 +370,8 @@ impl Session {
anchor_range: Range<Anchor>,
next_cell: Option<Anchor>,
move_down: bool,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(editor) = self.editor.upgrade() else {
return;
@ -409,11 +417,11 @@ impl Session {
};
let parent_message_id = message.header.msg_id.clone();
let session_view = cx.view().downgrade();
let session_view = cx.model().downgrade();
let weak_editor = self.editor.clone();
let on_close: CloseBlockFn =
Arc::new(move |block_id: CustomBlockId, cx: &mut WindowContext| {
let on_close: CloseBlockFn = Arc::new(
move |block_id: CustomBlockId, _: &mut Window, cx: &mut App| {
if let Some(session) = session_view.upgrade() {
session.update(cx, |session, cx| {
session.blocks.remove(&parent_message_id);
@ -428,7 +436,8 @@ impl Session {
editor.remove_blocks(block_ids, None, cx);
});
}
});
},
);
let Ok(editor_block) =
EditorBlock::new(self.editor.clone(), anchor_range, status, on_close, cx)
@ -468,14 +477,19 @@ impl Session {
if move_down {
editor.update(cx, move |editor, cx| {
editor.change_selections(Some(Autoscroll::top_relative(8)), cx, |selections| {
selections.select_ranges([new_cursor_pos..new_cursor_pos]);
});
editor.change_selections(
Some(Autoscroll::top_relative(8)),
window,
cx,
|selections| {
selections.select_ranges([new_cursor_pos..new_cursor_pos]);
},
);
});
}
}
pub fn route(&mut self, message: &JupyterMessage, cx: &mut ViewContext<Self>) {
pub fn route(&mut self, message: &JupyterMessage, window: &mut Window, cx: &mut Context<Self>) {
let parent_message_id = match message.parent_header.as_ref() {
Some(header) => &header.msg_id,
None => return,
@ -507,7 +521,7 @@ impl Session {
self.blocks.iter_mut().for_each(|(_, block)| {
block.execution_view.update(cx, |execution_view, cx| {
execution_view.update_display_data(&update.data, &display_id, cx);
execution_view.update_display_data(&update.data, &display_id, window, cx);
});
});
return;
@ -516,11 +530,11 @@ impl Session {
}
if let Some(block) = self.blocks.get_mut(parent_message_id) {
block.handle_message(message, cx);
block.handle_message(message, window, cx);
}
}
pub fn interrupt(&mut self, cx: &mut ViewContext<Self>) {
pub fn interrupt(&mut self, cx: &mut Context<Self>) {
match &mut self.kernel {
Kernel::RunningKernel(_kernel) => {
self.send(InterruptRequest {}.into(), cx).ok();
@ -532,7 +546,7 @@ impl Session {
}
}
pub fn kernel(&mut self, kernel: Kernel, cx: &mut ViewContext<Self>) {
pub fn kernel(&mut self, kernel: Kernel, cx: &mut Context<Self>) {
if let Kernel::Shutdown = kernel {
cx.emit(SessionEvent::Shutdown(self.editor.clone()));
}
@ -550,14 +564,14 @@ impl Session {
self.kernel = kernel;
}
pub fn shutdown(&mut self, cx: &mut ViewContext<Self>) {
pub fn shutdown(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let kernel = std::mem::replace(&mut self.kernel, Kernel::ShuttingDown);
match kernel {
Kernel::RunningKernel(mut kernel) => {
let mut request_tx = kernel.request_tx().clone();
let forced = kernel.force_shutdown(cx);
let forced = kernel.force_shutdown(window, cx);
cx.spawn(|this, mut cx| async move {
let message: JupyterMessage = ShutdownRequest { restart: false }.into();
@ -584,7 +598,7 @@ impl Session {
cx.notify();
}
pub fn restart(&mut self, cx: &mut ViewContext<Self>) {
pub fn restart(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let kernel = std::mem::replace(&mut self.kernel, Kernel::Restarting);
match kernel {
@ -594,9 +608,9 @@ impl Session {
Kernel::RunningKernel(mut kernel) => {
let mut request_tx = kernel.request_tx().clone();
let forced = kernel.force_shutdown(cx);
let forced = kernel.force_shutdown(window, cx);
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
// Send shutdown request with restart flag
log::debug!("restarting kernel");
let message: JupyterMessage = ShutdownRequest { restart: true }.into();
@ -609,10 +623,10 @@ impl Session {
forced.await.log_err();
// Start a new kernel
this.update(&mut cx, |session, cx| {
this.update_in(&mut cx, |session, window, cx| {
// TODO: Differentiate between restart and restart+clear-outputs
session.clear_outputs(cx);
session.start_kernel(cx);
session.start_kernel(window, cx);
})
.ok();
})
@ -620,7 +634,7 @@ impl Session {
}
_ => {
self.clear_outputs(cx);
self.start_kernel(cx);
self.start_kernel(window, cx);
}
}
cx.notify();
@ -628,13 +642,13 @@ impl Session {
}
pub enum SessionEvent {
Shutdown(WeakView<Editor>),
Shutdown(WeakEntity<Editor>),
}
impl EventEmitter<SessionEvent> for Session {}
impl Render for Session {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (status_text, interrupt_button) = match &self.kernel {
Kernel::RunningKernel(kernel) => (
kernel
@ -644,7 +658,7 @@ impl Render for Session {
Some(
Button::new("interrupt", "Interrupt")
.style(ButtonStyle::Subtle)
.on_click(cx.listener(move |session, _, cx| {
.on_click(cx.listener(move |session, _, _, cx| {
session.interrupt(cx);
})),
),
@ -674,8 +688,8 @@ impl Render for Session {
Button::new("shutdown", "Shutdown")
.style(ButtonStyle::Subtle)
.disabled(self.kernel.is_shutting_down())
.on_click(cx.listener(move |session, _, cx| {
session.shutdown(cx);
.on_click(cx.listener(move |session, _, window, cx| {
session.shutdown(window, cx);
})),
)
.buttons(interrupt_button)