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 704 }, 705 705 ); 706 706 707 - // === Dedicated editing keys (for custom keyboards, etc.) === 708 707 bindings.insert(KeyCombo::new(Key::Undo), EditorAction::Undo); 709 708 bindings.insert(KeyCombo::new(Key::Redo), EditorAction::Redo); 710 709 bindings.insert(KeyCombo::new(Key::Copy), EditorAction::Copy); ··· 722 721 723 722 /// Look up an action for the given key combo, with the current range applied. 724 723 pub fn lookup(&self, combo: KeyCombo, range: Range) -> Option<EditorAction> { 725 - self.bindings.get(&combo).cloned().map(|a| a.with_range(range)) 724 + self.bindings 725 + .get(&combo) 726 + .cloned() 727 + .map(|a| a.with_range(range)) 726 728 } 727 729 728 730 /// Look up an action for the given key and modifiers, with the current range applied. 731 + #[allow(dead_code)] 729 732 pub fn lookup_key(&self, key: Key, modifiers: Modifiers, range: Range) -> Option<EditorAction> { 730 733 self.lookup(KeyCombo::with_modifiers(key, modifiers), range) 731 734 } 732 735 733 736 /// Add or replace a keybinding. 737 + #[allow(dead_code)] 734 738 pub fn bind(&mut self, combo: KeyCombo, action: EditorAction) { 735 739 self.bindings.insert(combo, action); 736 740 } 737 741 738 742 /// Remove a keybinding. 743 + #[allow(dead_code)] 739 744 pub fn unbind(&mut self, combo: KeyCombo) { 740 745 self.bindings.remove(&combo); 741 746 } ··· 792 797 let range = range.normalize(); 793 798 doc.pending_snap.set(Some(SnapDirection::Forward)); 794 799 800 + let offset = range.start; 795 801 if !range.is_caret() { 796 - let _ = doc.remove_tracked(range.start, range.len()); 802 + let _ = doc.remove_tracked(offset, range.len()); 797 803 } 798 804 799 - let mut offset = range.start; 800 - 801 805 // Check if we're right after a soft break (newline + zero-width char). 802 806 // If so, convert to paragraph break by replacing the zero-width char 803 807 // with a newline. 804 - let mut is_double_enter = false; 805 - if offset >= 2 { 808 + let is_double_enter = if offset >= 2 { 806 809 let prev_char = get_char_at(doc.loro_text(), offset - 1); 807 810 let prev_prev_char = get_char_at(doc.loro_text(), offset - 2); 808 811 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; 812 + true 813 + } else { 814 + false 813 815 } 814 - } 816 + } else { 817 + false 818 + }; 815 819 816 820 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; 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; 822 861 } 823 862 824 863 doc.selection.set(None); ··· 829 868 let range = range.normalize(); 830 869 doc.pending_snap.set(Some(SnapDirection::Forward)); 831 870 871 + let cursor_offset = range.start; 832 872 if !range.is_caret() { 833 - let _ = doc.remove_tracked(range.start, range.len()); 873 + let _ = doc.remove_tracked(cursor_offset, range.len()); 834 874 } 835 - 836 - let cursor_offset = range.start; 837 875 838 876 // Check for list context 839 877 if let Some(ctx) = detect_list_context(doc.loro_text(), cursor_offset) {
+1 -1
crates/weaver-renderer/src/css.rs
··· 148 148 149 149 /* Lists */ 150 150 ul, ol {{ 151 - margin-left: 2rem; 151 + margin-left: 1rem; 152 152 margin-bottom: 1rem; 153 153 }} 154 154