diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index a20325c..83739dc 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -270,60 +270,19 @@ impl NostrRegistry { let key_path = self.key_dir.join(format!("{}.npub", npub)); let app_keys = self.app_keys.clone(); - if let Ok(payload) = std::fs::read_to_string(key_path) { - if !payload.is_empty() { - cx.background_spawn(async move { - let decrypted = app_keys.nip44_decrypt(&public_key, &payload).await?; - let secret = SecretKey::parse(&decrypted)?; - let keys = Keys::new(secret); + if let Ok(payload) = std::fs::read_to_string(key_path) + && !payload.is_empty() + { + return cx.background_spawn(async move { + let decrypted = app_keys.nip44_decrypt(&public_key, &payload).await?; + let secret = SecretKey::parse(&decrypted)?; + let keys = Keys::new(secret); - Ok(keys.into_nostr_signer()) - }) - } else { - self.get_secret_keyring(&npub, cx) - } - } else { - self.get_secret_keyring(&npub, cx) + Ok(keys.into_nostr_signer()) + }); } - } - /// Get the secret for a given npub in the OS credentials store. - #[deprecated = "Use get_secret instead"] - fn get_secret_keyring( - &self, - user: &str, - cx: &App, - ) -> Task, Error>> { - let read = cx.read_credentials(user); - let app_keys = self.app_keys.clone(); - - cx.background_spawn(async move { - let (_, secret) = read - .await - .map_err(|_| anyhow!("Failed to get signer. Please re-import the secret key"))? - .ok_or_else(|| anyhow!("Failed to get signer. Please re-import the secret key"))?; - - // Try to parse as a direct secret key first - if let Ok(secret_key) = SecretKey::from_slice(&secret) { - return Ok(Keys::new(secret_key).into_nostr_signer()); - } - - // Convert the secret into string - let sec = String::from_utf8(secret) - .map_err(|_| anyhow!("Failed to parse secret as UTF-8"))?; - - // Try to parse as a NIP-46 URI - let uri = - NostrConnectUri::parse(&sec).map_err(|_| anyhow!("Failed to parse NIP-46 URI"))?; - - let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT); - let mut nip46 = NostrConnect::new(uri, app_keys, timeout, None)?; - - // Set the auth URL handler - nip46.auth_url_handler(CoopAuthUrlHandler); - - Ok(nip46.into_nostr_signer()) - }) + Task::ready(Err(anyhow::anyhow!("No secret found"))) } /// Add a new npub to the keys directory diff --git a/crates/ui/src/input/display_map/mod.rs b/crates/ui/src/input/display_map/mod.rs index 41fcfc6..11738a5 100644 --- a/crates/ui/src/input/display_map/mod.rs +++ b/crates/ui/src/input/display_map/mod.rs @@ -1,12 +1,4 @@ -/// Display mapping system for Editor/Input. -/// -/// This module implements a layered display mapping architecture: -/// - **WrapMap**: Handles soft-wrapping (buffer → wrap rows) -/// - **FoldMap**: Handles folding (wrap rows → display rows) -/// - **DisplayMap**: Public facade for Editor/Input -/// -/// The goal is to provide a clean, unified API where Editor only needs to know -/// about `BufferPoint ↔ DisplayPoint` mapping, without worrying about internal wrap/fold complexity. +#[allow(clippy::module_inception)] mod display_map; mod fold_map; #[cfg(not(target_family = "wasm"))] diff --git a/crates/ui/src/input/display_map/text_wrapper.rs b/crates/ui/src/input/display_map/text_wrapper.rs index 99acd14..dadb133 100644 --- a/crates/ui/src/input/display_map/text_wrapper.rs +++ b/crates/ui/src/input/display_map/text_wrapper.rs @@ -1,8 +1,7 @@ use std::ops::Range; -use gpui::Half; use gpui::{ - App, Font, LineFragment, Pixels, Point, ShapedLine, Size, TextAlign, Window, point, px, + App, Font, Half, LineFragment, Pixels, Point, ShapedLine, Size, TextAlign, Window, point, px, size, }; use ropey::Rope; @@ -97,7 +96,7 @@ impl TextWrapper { /// Get the line item by row index. #[inline] pub(crate) fn line(&self, row: usize) -> Option<&LineItem> { - self.lines.iter().skip(row).next() + self.lines.get(row) } pub(crate) fn set_wrap_width(&mut self, wrap_width: Option, cx: &mut App) { @@ -228,7 +227,7 @@ impl TextWrapper { }); } - if self.lines.len() == 0 { + if self.lines.is_empty() { self.lines = new_lines; } else { self.lines.splice(rows_range, new_lines); @@ -246,7 +245,7 @@ impl TextWrapper { /// /// If the `text` is the same as the current text, do nothing. fn update_all(&mut self, text: &Rope, cx: &mut App) { - self.update(text, &(0..text.len()), &text, cx); + self.update(text, &(0..text.len()), text, cx); } /// Return display point (with soft wrap) from the given byte offset in the text. @@ -278,7 +277,8 @@ impl TextWrapper { // Otherwise return the eof of the line. let last_range = line.wrapped_lines.last().unwrap_or(&(0..0)); let ix = line.lines_len().saturating_sub(1); - return WrapDisplayPoint::new(wrapped_row + ix, ix, last_range.len()); + + WrapDisplayPoint::new(wrapped_row + ix, ix, last_range.len()) } /// Return byte offset in the text from the given display point (with soft wrap). @@ -301,7 +301,7 @@ impl TextWrapper { wrapped_row += line.lines_len(); } - return self.text.len(); + self.text.len() } pub(crate) fn display_point_to_point(&self, point: WrapDisplayPoint) -> TreeSitterPoint { @@ -580,351 +580,3 @@ impl LineLayout { } } } - -#[cfg(test)] -mod tests { - use super::*; - use std::rc::Rc; - - use gpui::{Boundary, FontFeatures, FontStyle, FontWeight, px}; - - #[test] - fn test_update() { - let font = gpui::Font { - family: "Arial".into(), - weight: FontWeight::default(), - style: FontStyle::Normal, - features: FontFeatures::default(), - fallbacks: None, - }; - - let mut wrapper = TextWrapper::new(font, px(14.), None); - let mut text = Rope::from( - "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。", - ); - - fn fake_wrap_line(_line: &str, _wrap_width: Pixels) -> Vec { - vec![] - } - - #[track_caller] - fn assert_wrapper_lines(text: &Rope, wrapper: &TextWrapper, expected_lines: &[&[&str]]) { - let mut actual_lines = vec![]; - let mut offset = 0; - for line in wrapper.lines.iter() { - actual_lines.push( - line.wrapped_lines - .iter() - .map(|range| text.slice(offset + range.start..offset + range.end)) - .collect::>(), - ); - // +1 \n - offset += line.len() + 1; - } - assert_eq!(actual_lines, expected_lines); - } - - wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line); - assert_eq!(wrapper.lines.len(), 4); - assert_wrapper_lines( - &text, - &wrapper, - &[ - &["Hello, 世界!\r"], - &["This is second line."], - &["This is third line."], - &["这里是第 4 行。"], - ], - ); - - // Add a new text to end - let range = text.len()..text.len(); - let new_text = "New text"; - text.replace(range.clone(), new_text); - wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line); - assert_eq!( - text.to_string(), - "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text" - ); - assert_eq!(wrapper.lines.len(), 4); - assert_eq!(wrapper.lines.len(), 4); - assert_wrapper_lines( - &text, - &wrapper, - &[ - &["Hello, 世界!\r"], - &["This is second line."], - &["This is third line."], - &["这里是第 4 行。New text"], - ], - ); - - // Replace first line `Hello` to `AAA` - let range = 0..5; - let new_text = "AAA"; - text.replace(range.clone(), new_text); - wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line); - assert_eq!( - text.to_string(), - "AAA, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text" - ); - assert_eq!(wrapper.lines.len(), 4); - assert_wrapper_lines( - &text, - &wrapper, - &[ - &["AAA, 世界!\r"], - &["This is second line."], - &["This is third line."], - &["这里是第 4 行。New text"], - ], - ); - - // Remove the second line - let start_offset = text.line_start_offset(1); - let end_offset = text.line_end_offset(1); - let range = start_offset..end_offset + 1; - text.replace(range.clone(), ""); - wrapper._update(&text, &range, &Rope::from(""), &mut fake_wrap_line); - assert_eq!( - text.to_string(), - "AAA, 世界!\r\nThis is third line.\n这里是第 4 行。New text" - ); - assert_eq!(wrapper.lines.len(), 3); - assert_wrapper_lines( - &text, - &wrapper, - &[ - &["AAA, 世界!\r"], - &["This is third line."], - &["这里是第 4 行。New text"], - ], - ); - - // Replace the first 2 lines to "This is a new line." - let range = text.line_start_offset(0)..text.line_end_offset(1) + 1; - let new_text = "This is a new line.\nThis is new line 2.\n"; - text.replace(range.clone(), new_text); - wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line); - assert_eq!( - text.to_string(), - "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text" - ); - assert_eq!(wrapper.lines.len(), 3); - assert_wrapper_lines( - &text, - &wrapper, - &[ - &["This is a new line."], - &["This is new line 2."], - &["这里是第 4 行。New text"], - ], - ); - - // Add a new line at the end - let range = text.len()..text.len(); - let new_text = "\nThis is a new line at the end."; - text.replace(range.clone(), new_text); - wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line); - assert_eq!( - text.to_string(), - "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end." - ); - assert_eq!(wrapper.lines.len(), 4); - assert_wrapper_lines( - &text, - &wrapper, - &[ - &["This is a new line."], - &["This is new line 2."], - &["这里是第 4 行。New text"], - &["This is a new line at the end."], - ], - ); - - // Add a new line at the beginning - let range = 0..0; - let new_text = "This is a new line at the beginning.\n"; - text.replace(range.clone(), new_text); - wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line); - assert_eq!( - text.to_string(), - "This is a new line at the beginning.\nThis is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end." - ); - assert_eq!(wrapper.lines.len(), 5); - assert_wrapper_lines( - &text, - &wrapper, - &[ - &["This is a new line at the beginning."], - &["This is a new line."], - &["This is new line 2."], - &["这里是第 4 行。New text"], - &["This is a new line at the end."], - ], - ); - - // Remove all to at least one line in `lines`. - let range = 0..text.len(); - let new_text = ""; - text.replace(range.clone(), new_text); - wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line); - assert_eq!(text.to_string(), ""); - assert_eq!(wrapper.lines.len(), 1); - assert_eq!(wrapper.lines[0].wrapped_lines, vec![0..0]); - - // Test update_all - let range = 0..text.len(); - let new_text = "This is a full text.\nThis is a second line."; - text.replace(range.clone(), new_text); - wrapper._update(&text, &range, &text, &mut fake_wrap_line); - assert_eq!( - text.to_string(), - "This is a full text.\nThis is a second line." - ); - assert_eq!(wrapper.lines.len(), 2); - } - - #[test] - fn test_line_layout() { - let mut line_layout = LineLayout::new(); - - let line1 = ShapedLine::default().with_len(100); - let line2 = ShapedLine::default().with_len(50); - let wrapped_lines = smallvec::smallvec![line1, line2]; - line_layout.set_wrapped_lines(wrapped_lines); - assert_eq!(line_layout.len(), 150); - assert_eq!(line_layout.wrapped_lines.len(), 2); - } - - #[test] - fn test_position_for_index_prefers_first_leading_empty_visual_line() { - let mut line_layout = LineLayout::new(); - line_layout.set_wrapped_lines(smallvec::smallvec![ - ShapedLine::default(), - ShapedLine::default(), - ShapedLine::default().with_len(3), - ]); - - let last_layout = LastLayout { - visible_range: 0..1, - visible_buffer_lines: vec![0], - visible_line_byte_offsets: vec![0], - visible_top: px(0.), - visible_range_offset: 0..0, - lines: Rc::new(vec![]), - line_height: px(20.), - wrap_width: None, - line_number_width: px(0.), - cursor_bounds: None, - text_align: TextAlign::Left, - content_width: px(0.), - }; - - assert_eq!( - line_layout.position_for_index(0, &last_layout, false), - Some(point(px(0.), px(0.))) - ); - } - - #[test] - fn test_offset_to_display_point() { - let font = gpui::Font { - family: "Arial".into(), - weight: FontWeight::default(), - style: FontStyle::Normal, - features: FontFeatures::default(), - fallbacks: None, - }; - - let mut wrapper = TextWrapper::new(font, px(14.), None); - wrapper.text = Rope::from( - "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。", - ); - wrapper.lines = vec![ - // range: 0..15 - LineItem { - line: Rope::from("Hello, 世界!\r"), - wrapped_lines: vec![0..15], - }, - // range: 16..36 - LineItem { - line: Rope::from("This is second line."), - wrapped_lines: vec![0..10, 10..20], - }, - // range: 37..56 - LineItem { - line: Rope::from("This is third line."), - wrapped_lines: vec![0..9, 9..15, 15..20], - }, - // range: 57..79 - LineItem { - line: Rope::from("这里是第 4 行。"), - wrapped_lines: vec![0..22], - }, - ]; - - assert_eq!( - wrapper.offset_to_display_point(12), - WrapDisplayPoint::new(0, 0, 12) - ); - assert_eq!( - wrapper.offset_to_display_point(15), - WrapDisplayPoint::new(0, 0, 15) - ); - - assert_eq!( - wrapper.offset_to_display_point(16), - WrapDisplayPoint::new(1, 0, 0) - ); - assert_eq!( - wrapper.offset_to_display_point(21), - WrapDisplayPoint::new(1, 0, 5) - ); - assert_eq!( - wrapper.offset_to_display_point(27), - WrapDisplayPoint::new(2, 1, 1) - ); - assert_eq!( - wrapper.offset_to_display_point(37), - WrapDisplayPoint::new(3, 0, 0) - ); - assert_eq!( - wrapper.offset_to_display_point(54), - WrapDisplayPoint::new(5, 2, 2) - ); - assert_eq!( - wrapper.offset_to_display_point(59), - WrapDisplayPoint::new(6, 0, 2) - ); - - assert_eq!( - wrapper.display_point_to_offset(WrapDisplayPoint::new(6, 0, 2)), - 59 - ); - assert_eq!( - wrapper.display_point_to_offset(WrapDisplayPoint::new(5, 2, 2)), - 54 - ); - assert_eq!( - wrapper.display_point_to_offset(WrapDisplayPoint::new(3, 0, 0)), - 37 - ); - assert_eq!( - wrapper.display_point_to_offset(WrapDisplayPoint::new(2, 1, 1)), - 27 - ); - assert_eq!( - wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 5)), - 21 - ); - assert_eq!( - wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 0)), - 16 - ); - assert_eq!( - wrapper.display_point_to_offset(WrapDisplayPoint::new(0, 0, 15)), - 15 - ); - } -} diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index c53166e..8367e27 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -131,11 +131,14 @@ impl Element for EditorScrollbar { window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); - style.position = Position::Absolute; - style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); - + let style = Style { + position: Position::Absolute, + size: Size { + width: relative(1.).into(), + height: relative(1.).into(), + }, + ..Default::default() + }; (window.request_layout(style, [], cx), ()) } @@ -309,13 +312,12 @@ impl TextElement { let mut cursor_bounds = None; // If the input has a fixed height (Otherwise is auto-grow), we need to add a bottom margin to the input. - let top_bottom_margin = if state.mode.is_auto_grow() { - line_height - } else if visible_range.len() < BOTTOM_MARGIN_ROWS * 8 { - line_height - } else { - BOTTOM_MARGIN_ROWS * line_height - }; + let top_bottom_margin = + if state.mode.is_auto_grow() || visible_range.len() < BOTTOM_MARGIN_ROWS * 8 { + line_height + } else { + BOTTOM_MARGIN_ROWS * line_height + }; // The cursor corresponds to the current cursor position in the text no only the line. let mut cursor_pos = None;