From 57673d5d65da9a598de78481b55e827a62a9fa1c Mon Sep 17 00:00:00 2001 From: TC Date: Sun, 14 Jun 2026 20:58:38 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84=20display=20number=20of=20words=20?= =?UTF-8?q?in=20sentence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Editor/MarkdownEditor.tsx | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/Editor/MarkdownEditor.tsx b/src/renderer/components/Editor/MarkdownEditor.tsx index 47adcd6..25d1bce 100644 --- a/src/renderer/components/Editor/MarkdownEditor.tsx +++ b/src/renderer/components/Editor/MarkdownEditor.tsx @@ -677,6 +677,22 @@ function countWords(text: string): number { return text.trim() === '' ? 0 : text.trim().split(/\s+/).length } +function getSentenceWordCount(text: string, pos: number): number { + // Scan back to find sentence start (after .!? followed by space, or paragraph break, or doc start) + let start = 0 + for (let i = pos - 1; i >= 0; i--) { + if (text[i] === '\n' && i > 0 && text[i - 1] === '\n') { start = i + 1; break } + if (/[.!?]/.test(text[i]) && i + 1 < text.length && /\s/.test(text[i + 1])) { start = i + 1; break } + } + // Scan forward to find sentence end + let end = text.length + for (let i = pos; i < text.length; i++) { + if (text[i] === '\n' && i + 1 < text.length && text[i + 1] === '\n') { end = i; break } + if (/[.!?]/.test(text[i])) { end = i + 1; break } + } + return countWords(text.slice(start, end)) +} + export function MarkdownEditor(): JSX.Element { const containerRef = useRef(null) const viewRef = useRef(null) @@ -685,6 +701,7 @@ export function MarkdownEditor(): JSX.Element { const [pendingComment, setPendingComment] = useState<{ from: number; to: number; text: string } | null>(null) const [commentDraft, setCommentDraft] = useState('') const [wordStats, setWordStats] = useState<{ atCursor: number; total: number }>({ atCursor: 0, total: 0 }) + const [sentenceWordCount, setSentenceWordCount] = useState(0) const wordTotalRef = useRef(0) const { activeFilePath, activeFileContent, setContent, annotations, fontSize, theme, focusMode, scrollPositions, pendingScrollToLine, clearPendingScrollToLine, selectionWordCount, projectWordCount } = useEditorStore() @@ -836,6 +853,8 @@ export function MarkdownEditor(): JSX.Element { useEditorStore.getState().setSelectionWordCount(words) } else { useEditorStore.getState().setSelectionWordCount(null) + const text = update.state.doc.toString() + setSentenceWordCount(getSentenceWordCount(text, to)) } } // When the user Cmd+Z's through an Apply, invertedEffects fires a @@ -1147,9 +1166,11 @@ export function MarkdownEditor(): JSX.Element { {activeFilePath && (
word {wordStats.atCursor.toLocaleString()} of {wordStats.total.toLocaleString()} - {selectionWordCount !== null && ( + {selectionWordCount !== null ? ( {selectionWordCount.toLocaleString()} selected - )} + ) : sentenceWordCount > 0 ? ( + {sentenceWordCount} in sentence + ) : null}
{(() => { const docWc = countWords(activeFileContent)