1use std::sync::Arc;
2
3use anyhow::Result;
4use collections::HashMap;
5use command_palette_hooks::CommandPaletteFilter;
6use gpui::{
7 prelude::*, AppContext, EntityId, Global, Model, ModelContext, Subscription, Task, View,
8};
9use language::Language;
10use project::Fs;
11use settings::{Settings, SettingsStore};
12
13use crate::kernels::kernel_specifications;
14use crate::{JupyterSettings, KernelSpecification, Session};
15
16struct GlobalReplStore(Model<ReplStore>);
17
18impl Global for GlobalReplStore {}
19
20pub struct ReplStore {
21 fs: Arc<dyn Fs>,
22 enabled: bool,
23 sessions: HashMap<EntityId, View<Session>>,
24 kernel_specifications: Vec<KernelSpecification>,
25 _subscriptions: Vec<Subscription>,
26}
27
28impl ReplStore {
29 const NAMESPACE: &'static str = "repl";
30
31 pub(crate) fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
32 let store = cx.new_model(move |cx| Self::new(fs, cx));
33
34 store
35 .update(cx, |store, cx| store.refresh_kernelspecs(cx))
36 .detach_and_log_err(cx);
37
38 cx.set_global(GlobalReplStore(store))
39 }
40
41 pub fn global(cx: &AppContext) -> Model<Self> {
42 cx.global::<GlobalReplStore>().0.clone()
43 }
44
45 pub fn new(fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
46 let subscriptions = vec![cx.observe_global::<SettingsStore>(move |this, cx| {
47 this.set_enabled(JupyterSettings::enabled(cx), cx);
48 })];
49
50 let this = Self {
51 fs,
52 enabled: JupyterSettings::enabled(cx),
53 sessions: HashMap::default(),
54 kernel_specifications: Vec::new(),
55 _subscriptions: subscriptions,
56 };
57 this.on_enabled_changed(cx);
58 this
59 }
60
61 pub fn fs(&self) -> &Arc<dyn Fs> {
62 &self.fs
63 }
64
65 pub fn is_enabled(&self) -> bool {
66 self.enabled
67 }
68
69 pub fn kernel_specifications(&self) -> impl Iterator<Item = &KernelSpecification> {
70 self.kernel_specifications.iter()
71 }
72
73 pub fn sessions(&self) -> impl Iterator<Item = &View<Session>> {
74 self.sessions.values()
75 }
76
77 fn set_enabled(&mut self, enabled: bool, cx: &mut ModelContext<Self>) {
78 if self.enabled == enabled {
79 return;
80 }
81
82 self.enabled = enabled;
83 self.on_enabled_changed(cx);
84 }
85
86 fn on_enabled_changed(&self, cx: &mut ModelContext<Self>) {
87 if !self.enabled {
88 CommandPaletteFilter::update_global(cx, |filter, _cx| {
89 filter.hide_namespace(Self::NAMESPACE);
90 });
91
92 return;
93 }
94
95 CommandPaletteFilter::update_global(cx, |filter, _cx| {
96 filter.show_namespace(Self::NAMESPACE);
97 });
98
99 cx.notify();
100 }
101
102 pub fn refresh_kernelspecs(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
103 let kernel_specifications = kernel_specifications(self.fs.clone());
104 cx.spawn(|this, mut cx| async move {
105 let kernel_specifications = kernel_specifications.await?;
106
107 this.update(&mut cx, |this, cx| {
108 this.kernel_specifications = kernel_specifications;
109 cx.notify();
110 })
111 })
112 }
113
114 pub fn kernelspec(
115 &self,
116 language: &Language,
117 cx: &mut ModelContext<Self>,
118 ) -> Option<KernelSpecification> {
119 let settings = JupyterSettings::get_global(cx);
120 let language_name = language.code_fence_block_name();
121 let selected_kernel = settings.kernel_selections.get(language_name.as_ref());
122
123 self.kernel_specifications
124 .iter()
125 .find(|runtime_specification| {
126 if let Some(selected) = selected_kernel {
127 // Top priority is the selected kernel
128 runtime_specification.name.to_lowercase() == selected.to_lowercase()
129 } else {
130 // Otherwise, we'll try to find a kernel that matches the language
131 runtime_specification.kernelspec.language.to_lowercase()
132 == language_name.to_lowercase()
133 }
134 })
135 .cloned()
136 }
137
138 pub fn get_session(&self, entity_id: EntityId) -> Option<&View<Session>> {
139 self.sessions.get(&entity_id)
140 }
141
142 pub fn insert_session(&mut self, entity_id: EntityId, session: View<Session>) {
143 self.sessions.insert(entity_id, session);
144 }
145
146 pub fn remove_session(&mut self, entity_id: EntityId) {
147 self.sessions.remove(&entity_id);
148 }
149}