reworked a bit of list behaviour post input refactor

Orual 856e420f db383970

+59 -21
+58 -20
crates/weaver-app/src/components/editor/actions.rs
··· 704 }, 705 ); 706 707 - // === Dedicated editing keys (for custom keyboards, etc.) === 708 bindings.insert(KeyCombo::new(Key::Undo), EditorAction::Undo); 709 bindings.insert(KeyCombo::new(Key::Redo), EditorAction::Redo); 710 bindings.insert(KeyCombo::new(Key::Copy), EditorAction::Copy); ··· 722 723 /// Look up an action for the given key combo, with the current range applied. 724 pub fn lookup(&self, combo: KeyCombo, range: Range) -> Option<EditorAction> { 725 - self.bindings.get(&combo).cloned().map(|a| a.with_range(range)) 726 } 727 728 /// Look up an action for the given key and modifiers, with the current range applied. 729 pub fn lookup_key(&self, key: Key, modifiers: Modifiers, range: Range) -> Option<EditorAction> { 730 self.lookup(KeyCombo::with_modifiers(key, modifiers), range) 731 } 732 733 /// Add or replace a keybinding. 734 pub fn bind(&mut self, combo: KeyCombo, action: EditorAction) { 735 self.bindings.insert(combo, action); 736 } 737 738 /// Remove a keybinding. 739 pub fn unbind(&mut self, combo: KeyCombo) { 740 self.bindings.remove(&combo); 741 } ··· 792 let range = range.normalize(); 793 doc.pending_snap.set(Some(SnapDirection::Forward)); 794 795 if !range.is_caret() { 796 - let _ = doc.remove_tracked(range.start, range.len()); 797 } 798 799 - let mut offset = range.start; 800 - 801 // Check if we're right after a soft break (newline + zero-width char). 802 // If so, convert to paragraph break by replacing the zero-width char 803 // with a newline. 804 - let mut is_double_enter = false; 805 - if offset >= 2 { 806 let prev_char = get_char_at(doc.loro_text(), offset - 1); 807 let prev_prev_char = get_char_at(doc.loro_text(), offset - 2); 808 if prev_char == Some('\u{200C}') && prev_prev_char == Some('\n') { 809 - // Replace zero-width char with newline 810 - let _ = doc.replace_tracked(offset - 1, 1, "\n"); 811 - doc.cursor.write().offset = offset; 812 - is_double_enter = true; 813 } 814 - } 815 816 if !is_double_enter { 817 - // Normal soft break: insert newline + zero-width char for cursor positioning. 818 - // The renderer emits <br> for soft breaks, so we don't need 819 - // trailing spaces for visual line breaks. 820 - let _ = doc.insert_tracked(offset, "\n\u{200C}"); 821 - doc.cursor.write().offset = offset + 2; 822 } 823 824 doc.selection.set(None); ··· 829 let range = range.normalize(); 830 doc.pending_snap.set(Some(SnapDirection::Forward)); 831 832 if !range.is_caret() { 833 - let _ = doc.remove_tracked(range.start, range.len()); 834 } 835 - 836 - let cursor_offset = range.start; 837 838 // Check for list context 839 if let Some(ctx) = detect_list_context(doc.loro_text(), cursor_offset) {
··· 704 }, 705 ); 706 707 bindings.insert(KeyCombo::new(Key::Undo), EditorAction::Undo); 708 bindings.insert(KeyCombo::new(Key::Redo), EditorAction::Redo); 709 bindings.insert(KeyCombo::new(Key::Copy), EditorAction::Copy); ··· 721 722 /// Look up an action for the given key combo, with the current range applied. 723 pub fn lookup(&self, combo: KeyCombo, range: Range) -> Option<EditorAction> { 724 + self.bindings 725 + .get(&combo) 726 + .cloned() 727 + .map(|a| a.with_range(range)) 728 } 729 730 /// Look up an action for the given key and modifiers, with the current range applied. 731 + #[allow(dead_code)] 732 pub fn lookup_key(&self, key: Key, modifiers: Modifiers, range: Range) -> Option<EditorAction> { 733 self.lookup(KeyCombo::with_modifiers(key, modifiers), range) 734 } 735 736 /// Add or replace a keybinding. 737 + #[allow(dead_code)] 738 pub fn bind(&mut self, combo: KeyCombo, action: EditorAction) { 739 self.bindings.insert(combo, action); 740 } 741 742 /// Remove a keybinding. 743 + #[allow(dead_code)] 744 pub fn unbind(&mut self, combo: KeyCombo) { 745 self.bindings.remove(&combo); 746 } ··· 797 let range = range.normalize(); 798 doc.pending_snap.set(Some(SnapDirection::Forward)); 799 800 + let offset = range.start; 801 if !range.is_caret() { 802 + let _ = doc.remove_tracked(offset, range.len()); 803 } 804 805 // Check if we're right after a soft break (newline + zero-width char). 806 // If so, convert to paragraph break by replacing the zero-width char 807 // with a newline. 808 + let is_double_enter = if offset >= 2 { 809 let prev_char = get_char_at(doc.loro_text(), offset - 1); 810 let prev_prev_char = get_char_at(doc.loro_text(), offset - 2); 811 if prev_char == Some('\u{200C}') && prev_prev_char == Some('\n') { 812 + true 813 + } else { 814 + false 815 } 816 + } else { 817 + false 818 + }; 819 820 if !is_double_enter { 821 + // Check for list context 822 + if let Some(ctx) = detect_list_context(doc.loro_text(), offset) { 823 + tracing::debug!("List context detected: {:?}", ctx); 824 + if is_list_item_empty(doc.loro_text(), offset, &ctx) { 825 + // Empty item - exit list 826 + let line_start = find_line_start(doc.loro_text(), offset); 827 + let line_end = find_line_end(doc.loro_text(), offset); 828 + let delete_end = (line_end + 1).min(doc.len_chars()); 829 + 830 + let _ = doc.replace_tracked( 831 + line_start, 832 + delete_end.saturating_sub(line_start), 833 + "\n\n\u{200C}\n", 834 + ); 835 + doc.cursor.write().offset = line_start + 2; 836 + tracing::debug!("empty list"); 837 + } else { 838 + // Continue list 839 + let continuation = match ctx { 840 + super::input::ListContext::Unordered { indent, marker } => { 841 + format!("\n{}{} ", indent, marker) 842 + } 843 + super::input::ListContext::Ordered { indent, number } => { 844 + format!("\n{}{}. ", indent, number + 1) 845 + } 846 + }; 847 + let len = continuation.chars().count(); 848 + let _ = doc.insert_tracked(offset, &continuation); 849 + doc.cursor.write().offset = offset + len; 850 + tracing::debug!("continuation {}", continuation); 851 + } 852 + } else { 853 + // Normal soft break: insert newline + zero-width char for cursor positioning. 854 + let _ = doc.insert_tracked(offset, "\n\u{200C}"); 855 + doc.cursor.write().offset = offset + 2; 856 + } 857 + } else { 858 + // Replace zero-width char with newline 859 + let _ = doc.replace_tracked(offset - 1, 1, "\n"); 860 + doc.cursor.write().offset = offset; 861 } 862 863 doc.selection.set(None); ··· 868 let range = range.normalize(); 869 doc.pending_snap.set(Some(SnapDirection::Forward)); 870 871 + let cursor_offset = range.start; 872 if !range.is_caret() { 873 + let _ = doc.remove_tracked(cursor_offset, range.len()); 874 } 875 876 // Check for list context 877 if let Some(ctx) = detect_list_context(doc.loro_text(), cursor_offset) {
+1 -1
crates/weaver-renderer/src/css.rs
··· 148 149 /* Lists */ 150 ul, ol {{ 151 - margin-left: 2rem; 152 margin-bottom: 1rem; 153 }} 154
··· 148 149 /* Lists */ 150 ul, ol {{ 151 + margin-left: 1rem; 152 margin-bottom: 1rem; 153 }} 154