From ac528dda6455b41e06fbd1099bc778cc5c51f911 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jul 2024 15:04:13 -0700 Subject: [PATCH] Fix panic when evaluating a code snippet containing multi-byte characters (#14269) Also, don't retrieve code snippets when rendering the repl quick action button Release Notes: - N/A --------- Co-authored-by: Kyle Kelley Co-authored-by: Kyle Kelley --- crates/repl/src/runtime_panel.rs | 88 ++++++++++++-------------------- 1 file changed, 32 insertions(+), 56 deletions(-) diff --git a/crates/repl/src/runtime_panel.rs b/crates/repl/src/runtime_panel.rs index 4662153b1b..1b3361dea3 100644 --- a/crates/repl/src/runtime_panel.rs +++ b/crates/repl/src/runtime_panel.rs @@ -11,7 +11,8 @@ use gpui::{ actions, prelude::*, AppContext, AsyncWindowContext, EntityId, EventEmitter, FocusHandle, FocusOutEvent, FocusableView, Subscription, Task, View, WeakView, }; -use language::Point; +use language::{Language, Point}; +use multi_buffer::MultiBufferRow; use project::Fs; use settings::{Settings as _, SettingsStore}; use std::{ops::Range, sync::Arc}; @@ -174,34 +175,14 @@ impl RuntimePanel { let range = if selection.is_empty() { let cursor = selection.head(); - let line_start = multi_buffer_snapshot.offset_to_point(cursor).row; - let mut start_offset = multi_buffer_snapshot.point_to_offset(Point::new(line_start, 0)); + let cursor_row = multi_buffer_snapshot.offset_to_point(cursor).row; + let start_offset = multi_buffer_snapshot.point_to_offset(Point::new(cursor_row, 0)); - // Iterate backwards to find the start of the line - while start_offset > 0 { - let ch = multi_buffer_snapshot - .chars_at(start_offset - 1) - .next() - .unwrap_or('\0'); - if ch == '\n' { - break; - } - start_offset -= 1; - } - - let mut end_offset = cursor; - - // Iterate forwards to find the end of the line - while end_offset < multi_buffer_snapshot.len() { - let ch = multi_buffer_snapshot - .chars_at(end_offset) - .next() - .unwrap_or('\0'); - if ch == '\n' { - break; - } - end_offset += 1; - } + let end_point = Point::new( + cursor_row, + multi_buffer_snapshot.line_len(MultiBufferRow(cursor_row)), + ); + let end_offset = start_offset.saturating_add(end_point.column as usize); // Create a range from the start to the end of the line start_offset..end_offset @@ -216,7 +197,7 @@ impl RuntimePanel { &self, editor: WeakView, cx: &mut ViewContext, - ) -> Option<(String, Arc, Range)> { + ) -> Option<(String, Arc, Range)> { let editor = editor.upgrade()?; let buffer = editor.read(cx).buffer().read(cx).snapshot(cx); @@ -226,30 +207,24 @@ impl RuntimePanel { .text_for_range(anchor_range.clone()) .collect::(); - let start_language = buffer.language_at(anchor_range.start); - let end_language = buffer.language_at(anchor_range.end); - - let language_name = if start_language == end_language { - start_language - .map(|language| language.code_fence_block_name()) - .filter(|lang| **lang != *"markdown")? - } else { - // If the selection spans multiple languages, don't run it + let start_language = buffer.language_at(anchor_range.start)?; + let end_language = buffer.language_at(anchor_range.end)?; + if start_language != end_language { return None; - }; + } - Some((selected_text, language_name, anchor_range)) + Some((selected_text, start_language.clone(), anchor_range)) } pub fn language( &self, editor: WeakView, cx: &mut ViewContext, - ) -> Option> { - match self.snippet(editor, cx) { - Some((_, language, _)) => Some(language), - None => None, - } + ) -> Option> { + let editor = editor.upgrade()?; + let selection = editor.read(cx).selections.newest::(cx); + let buffer = editor.read(cx).buffer().read(cx).snapshot(cx); + buffer.language_at(selection.head()).cloned() } pub fn refresh_kernelspecs(&mut self, cx: &mut ViewContext) -> Task> { @@ -266,11 +241,12 @@ impl RuntimePanel { pub fn kernelspec( &self, - language_name: &str, + language: &Language, cx: &mut ViewContext, ) -> Option { let settings = JupyterSettings::get_global(cx); - let selected_kernel = settings.kernel_selections.get(language_name); + let language_name = language.code_fence_block_name(); + let selected_kernel = settings.kernel_selections.get(language_name.as_ref()); self.kernel_specifications .iter() @@ -296,7 +272,7 @@ impl RuntimePanel { return Ok(()); } - let (selected_text, language_name, anchor_range) = match self.snippet(editor.clone(), cx) { + let (selected_text, language, anchor_range) = match self.snippet(editor.clone(), cx) { Some(snippet) => snippet, None => return Ok(()), }; @@ -304,8 +280,8 @@ impl RuntimePanel { let entity_id = editor.entity_id(); let kernel_specification = self - .kernelspec(&language_name, cx) - .with_context(|| format!("No kernel found for language: {language_name}"))?; + .kernelspec(&language, cx) + .with_context(|| format!("No kernel found for language: {}", language.name()))?; let session = self.sessions.entry(entity_id).or_insert_with(|| { let view = @@ -320,7 +296,6 @@ impl RuntimePanel { panel.sessions.remove(&shutdown_event.entity_id()); } } - // }, ); @@ -350,7 +325,7 @@ impl RuntimePanel { pub enum SessionSupport { ActiveSession(View), Inactive(KernelSpecification), - RequiresSetup(String), + RequiresSetup(Arc), Unsupported, } @@ -377,11 +352,12 @@ impl RuntimePanel { match kernelspec { Some(kernelspec) => SessionSupport::Inactive(kernelspec), None => { - let language: String = language.to_lowercase(); - // If no kernelspec but language is one of typescript, python, r, or julia + // If no kernelspec but language is one of typescript or python // then we return RequiresSetup - match language.as_str() { - "typescript" | "python" => SessionSupport::RequiresSetup(language), + match language.name().as_ref() { + "TypeScript" | "Python" => { + SessionSupport::RequiresSetup(language.name()) + } _ => SessionSupport::Unsupported, } }