From 37502fb733f162864ae23120dfb45a2c20622eda Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Wed, 4 Mar 2026 15:50:31 +0530 Subject: [PATCH] feat: enhanced status bar with word count, char count, line/col, encoding - Restructured status bar into left/right sections with separators - Added character count, cursor line/column position, encoding, and language mode indicators - Added cursor position tracking via CodeMirror onUpdate callback - Added file path display that updates on tab switch - Simplified word count display for cleaner status bar layout --- src/editor/codemirror-setup.js | 5 ++++ src/renderer.js | 53 +++++++++++++++++++++------------- src/styles-modern.css | 4 ++- src/styles.css | 30 ++++++++++++++++--- 4 files changed, 67 insertions(+), 25 deletions(-) diff --git a/src/editor/codemirror-setup.js b/src/editor/codemirror-setup.js index 8c7cfab..cf4084f 100644 --- a/src/editor/codemirror-setup.js +++ b/src/editor/codemirror-setup.js @@ -43,6 +43,7 @@ const { oneDark } = require('@codemirror/theme-one-dark'); * @param {Object} options * @param {string} options.content - initial document content (default '') * @param {Function} options.onChange - called with new content string on every doc change + * @param {Function} options.onUpdate - called with the EditorView on every update (selection, doc change, etc.) * @param {boolean} options.isDark - apply oneDark theme when true (default false) * @param {boolean} options.showLineNumbers - show line-number gutter (default true) * @returns {EditorView} the created editor view @@ -51,6 +52,7 @@ function createEditor(parentElement, options = {}) { const { content = '', onChange = () => {}, + onUpdate = null, isDark = false, showLineNumbers = true, } = options; @@ -76,6 +78,9 @@ function createEditor(parentElement, options = {}) { if (update.docChanged) { onChange(update.state.doc.toString()); } + if (onUpdate && (update.docChanged || update.selectionSet)) { + onUpdate(update.view); + } }), EditorView.lineWrapping, ]; diff --git a/src/renderer.js b/src/renderer.js index 4959a40..96c3874 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -165,6 +165,9 @@ class TabManager { this.updateWordCount(); this.updateTabBar(); }, + onUpdate: (view) => { + this.updateCursorPosition(view); + }, isDark, showLineNumbers: this.showLineNumbers, }); @@ -183,7 +186,14 @@ class TabManager { this.updateUI(); this.restoreTabState(tabId); this.focusActiveEditor(); - + this.updateFilePath(); + + // Update cursor position for the newly active tab + const tabForCursor = this.tabs.get(tabId); + if (tabForCursor?.editorView) { + this.updateCursorPosition(tabForCursor.editorView); + } + // Notify main process about current file for exports const tab = this.tabs.get(tabId); if (tab?.filePath) { @@ -417,29 +427,28 @@ class TabManager { const content = tab.content; const words = content.trim() ? content.trim().split(/\s+/).filter(word => word.length > 0).length : 0; const chars = content.length; - const charsNoSpaces = content.replace(/\s/g, '').length; - // Enhanced statistics - const lines = content.split('\n').length; - const paragraphs = content.split(/\n\s*\n/).filter(p => p.trim()).length; - const readingTime = Math.ceil(words / 200); // Average reading speed: 200 words/minute - const sentences = content.split(/[.!?]+/).filter(s => s.trim()).length; + const wordEl = document.getElementById('word-count'); + const charEl = document.getElementById('char-count'); + if (wordEl) wordEl.textContent = `Words: ${words}`; + if (charEl) charEl.textContent = `Chars: ${chars}`; + } - // Update the word count display with enhanced stats - const basicStats = `Words: ${words} | Characters: ${chars} (${charsNoSpaces} no spaces)`; - const enhancedStats = `Lines: ${lines} | Paragraphs: ${paragraphs} | Sentences: ${sentences} | Reading time: ${readingTime} min`; + updateCursorPosition(view) { + if (!view) return; + const pos = view.state.selection.main.head; + const line = view.state.doc.lineAt(pos); + const lineCol = document.getElementById('line-col'); + if (lineCol) lineCol.textContent = `Ln ${line.number}, Col ${pos - line.from + 1}`; + } - document.getElementById('word-count').textContent = basicStats; - - // Add enhanced stats to a separate element - let enhancedEl = document.getElementById('enhanced-stats'); - if (!enhancedEl) { - enhancedEl = document.createElement('div'); - enhancedEl.id = 'enhanced-stats'; - enhancedEl.className = 'enhanced-stats'; - document.querySelector('.status-bar').appendChild(enhancedEl); + updateFilePath() { + const tab = this.tabs.get(this.activeTabId); + const el = document.getElementById('status-file-path'); + if (el && tab) { + el.textContent = tab.filePath || 'Untitled'; + el.title = tab.filePath || ''; } - enhancedEl.textContent = enhancedStats; } setupEditorEvents() { @@ -931,6 +940,7 @@ class TabManager { this.startAutoSave(); this.addToRecentFiles(filePath); this.updateTabBar(); + this.updateFilePath(); // Notify main process about current file for exports ipcRenderer.send('set-current-file', filePath); @@ -1040,6 +1050,9 @@ document.addEventListener('DOMContentLoaded', () => { tabManager.updateWordCount(); tabManager.updateTabBar(); }, + onUpdate: (view) => { + tabManager.updateCursorPosition(view); + }, isDark, showLineNumbers: tabManager.showLineNumbers, }); diff --git a/src/styles-modern.css b/src/styles-modern.css index 5ac7d76..b82a3a3 100644 --- a/src/styles-modern.css +++ b/src/styles-modern.css @@ -323,10 +323,12 @@ body { display: flex; justify-content: space-between; align-items: center; - padding: 8px 20px; + padding: 2px 12px; /* Background and color controlled by theme */ font-size: 12px; font-weight: 500; + min-height: 24px; + flex-shrink: 0; } /* Preview Styles - Modern Typography */ diff --git a/src/styles.css b/src/styles.css index 2cd1eb9..b3f2ed6 100644 --- a/src/styles.css +++ b/src/styles.css @@ -139,11 +139,18 @@ body { background: #d0d0d0; } +.toolbar-group { + display: flex; + align-items: center; + gap: 2px; +} .toolbar-separator { width: 1px; - height: 24px; - background: #ccc; - margin: 0 8px; + height: 20px; + background: rgba(0, 0, 0, 0.12); + margin: 0 6px; + align-self: center; + flex-shrink: 0; } /* Editor Container */ @@ -287,11 +294,26 @@ body { .status-bar { display: flex; justify-content: space-between; - padding: 4px 12px; + align-items: center; + padding: 2px 12px; background: #f5f5f5; border-top: 1px solid #ddd; font-size: 12px; color: #666; + min-height: 24px; + flex-shrink: 0; +} +.status-bar-left, .status-bar-right { + display: flex; + align-items: center; + gap: 4px; +} +.status-separator { + color: var(--gray-300, #d1d5db); + margin: 0 2px; +} +.status-item { + white-space: nowrap; } /* Preview Styles */