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
This commit is contained in:
2026-03-04 15:50:31 +05:30
parent affe1a7e33
commit 37502fb733
4 changed files with 67 additions and 25 deletions
+5
View File
@@ -43,6 +43,7 @@ const { oneDark } = require('@codemirror/theme-one-dark');
* @param {Object} options * @param {Object} options
* @param {string} options.content - initial document content (default '') * @param {string} options.content - initial document content (default '')
* @param {Function} options.onChange - called with new content string on every doc change * @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.isDark - apply oneDark theme when true (default false)
* @param {boolean} options.showLineNumbers - show line-number gutter (default true) * @param {boolean} options.showLineNumbers - show line-number gutter (default true)
* @returns {EditorView} the created editor view * @returns {EditorView} the created editor view
@@ -51,6 +52,7 @@ function createEditor(parentElement, options = {}) {
const { const {
content = '', content = '',
onChange = () => {}, onChange = () => {},
onUpdate = null,
isDark = false, isDark = false,
showLineNumbers = true, showLineNumbers = true,
} = options; } = options;
@@ -76,6 +78,9 @@ function createEditor(parentElement, options = {}) {
if (update.docChanged) { if (update.docChanged) {
onChange(update.state.doc.toString()); onChange(update.state.doc.toString());
} }
if (onUpdate && (update.docChanged || update.selectionSet)) {
onUpdate(update.view);
}
}), }),
EditorView.lineWrapping, EditorView.lineWrapping,
]; ];
+33 -20
View File
@@ -165,6 +165,9 @@ class TabManager {
this.updateWordCount(); this.updateWordCount();
this.updateTabBar(); this.updateTabBar();
}, },
onUpdate: (view) => {
this.updateCursorPosition(view);
},
isDark, isDark,
showLineNumbers: this.showLineNumbers, showLineNumbers: this.showLineNumbers,
}); });
@@ -183,7 +186,14 @@ class TabManager {
this.updateUI(); this.updateUI();
this.restoreTabState(tabId); this.restoreTabState(tabId);
this.focusActiveEditor(); 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 // Notify main process about current file for exports
const tab = this.tabs.get(tabId); const tab = this.tabs.get(tabId);
if (tab?.filePath) { if (tab?.filePath) {
@@ -417,29 +427,28 @@ class TabManager {
const content = tab.content; const content = tab.content;
const words = content.trim() ? content.trim().split(/\s+/).filter(word => word.length > 0).length : 0; const words = content.trim() ? content.trim().split(/\s+/).filter(word => word.length > 0).length : 0;
const chars = content.length; const chars = content.length;
const charsNoSpaces = content.replace(/\s/g, '').length;
// Enhanced statistics const wordEl = document.getElementById('word-count');
const lines = content.split('\n').length; const charEl = document.getElementById('char-count');
const paragraphs = content.split(/\n\s*\n/).filter(p => p.trim()).length; if (wordEl) wordEl.textContent = `Words: ${words}`;
const readingTime = Math.ceil(words / 200); // Average reading speed: 200 words/minute if (charEl) charEl.textContent = `Chars: ${chars}`;
const sentences = content.split(/[.!?]+/).filter(s => s.trim()).length; }
// Update the word count display with enhanced stats updateCursorPosition(view) {
const basicStats = `Words: ${words} | Characters: ${chars} (${charsNoSpaces} no spaces)`; if (!view) return;
const enhancedStats = `Lines: ${lines} | Paragraphs: ${paragraphs} | Sentences: ${sentences} | Reading time: ${readingTime} min`; 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; updateFilePath() {
const tab = this.tabs.get(this.activeTabId);
// Add enhanced stats to a separate element const el = document.getElementById('status-file-path');
let enhancedEl = document.getElementById('enhanced-stats'); if (el && tab) {
if (!enhancedEl) { el.textContent = tab.filePath || 'Untitled';
enhancedEl = document.createElement('div'); el.title = tab.filePath || '';
enhancedEl.id = 'enhanced-stats';
enhancedEl.className = 'enhanced-stats';
document.querySelector('.status-bar').appendChild(enhancedEl);
} }
enhancedEl.textContent = enhancedStats;
} }
setupEditorEvents() { setupEditorEvents() {
@@ -931,6 +940,7 @@ class TabManager {
this.startAutoSave(); this.startAutoSave();
this.addToRecentFiles(filePath); this.addToRecentFiles(filePath);
this.updateTabBar(); this.updateTabBar();
this.updateFilePath();
// Notify main process about current file for exports // Notify main process about current file for exports
ipcRenderer.send('set-current-file', filePath); ipcRenderer.send('set-current-file', filePath);
@@ -1040,6 +1050,9 @@ document.addEventListener('DOMContentLoaded', () => {
tabManager.updateWordCount(); tabManager.updateWordCount();
tabManager.updateTabBar(); tabManager.updateTabBar();
}, },
onUpdate: (view) => {
tabManager.updateCursorPosition(view);
},
isDark, isDark,
showLineNumbers: tabManager.showLineNumbers, showLineNumbers: tabManager.showLineNumbers,
}); });
+3 -1
View File
@@ -323,10 +323,12 @@ body {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 8px 20px; padding: 2px 12px;
/* Background and color controlled by theme */ /* Background and color controlled by theme */
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
min-height: 24px;
flex-shrink: 0;
} }
/* Preview Styles - Modern Typography */ /* Preview Styles - Modern Typography */
+26 -4
View File
@@ -139,11 +139,18 @@ body {
background: #d0d0d0; background: #d0d0d0;
} }
.toolbar-group {
display: flex;
align-items: center;
gap: 2px;
}
.toolbar-separator { .toolbar-separator {
width: 1px; width: 1px;
height: 24px; height: 20px;
background: #ccc; background: rgba(0, 0, 0, 0.12);
margin: 0 8px; margin: 0 6px;
align-self: center;
flex-shrink: 0;
} }
/* Editor Container */ /* Editor Container */
@@ -287,11 +294,26 @@ body {
.status-bar { .status-bar {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 4px 12px; align-items: center;
padding: 2px 12px;
background: #f5f5f5; background: #f5f5f5;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
font-size: 12px; font-size: 12px;
color: #666; 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 */ /* Preview Styles */