Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ Splitmark automatically detects the correct Documents folder for your platform:
"layout": "side",
"showPreview": true,
"columnWidthRatio": 75,
"format": {
"wrapColumn": 80
},
"theme": {
"editor": {
"background": "#1e1e1e",
Expand Down Expand Up @@ -85,6 +88,11 @@ Splitmark automatically detects the correct Documents folder for your platform:
- **Default**: `75`
- **Description**: Editor width percentage in side-by-side layout (75%, 50%, or 25%)

### `format.wrapColumn`
- **Type**: Number
- **Default**: `80`
- **Description**: Target column width used by the in-editor Markdown formatter (Ctrl+Shift+F)

### `theme`
- **Type**: Object
- **Description**: Color theme settings for editor, syntax highlighting, and preview
Expand Down Expand Up @@ -170,7 +178,10 @@ Edit `~/.splitmarkrc` with your preferred settings:
"defaultLocation": "/path/to/your/notes",
"layout": "bottom",
"showPreview": true,
"columnWidthRatio": 50
"columnWidthRatio": 50,
"format": {
"wrapColumn": 72
}
}
```

Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ _Distraction-free web editing with the same files from your CLI_
- **Syntax highlighting** for Markdown (headers, bold, italic, code, links, lists)
- **Text selection** with Shift+Arrow keys
- **Undo/Redo** (Ctrl+Z/Ctrl+Y)
- **Auto-formatting** (Ctrl+Shift+F) for clean Markdown structure
- **Smart indentation** (Tab/Shift+Tab)
- **Word jumping** (Ctrl+Arrow keys)
- **Line operations** (join lines with backspace at start)
Expand Down Expand Up @@ -192,14 +193,15 @@ See [CLOUD.md](CLOUD.md) for detailed cloud documentation.

### Editing

| Shortcut | Action |
| ------------- | --------------------------------------- |
| **Ctrl+Z** | Undo |
| **Ctrl+Y** | Redo |
| **Tab** | Indent (2 spaces) |
| **Shift+Tab** | Unindent |
| **Enter** | New line |
| **Backspace** | Delete character (joins lines at start) |
| Shortcut | Action |
| ---------------- | --------------------------------------- |
| **Ctrl+Z** | Undo |
| **Ctrl+Y** | Redo |
| **Ctrl+Shift+F** | Format Markdown document |
| **Tab** | Indent (2 spaces) |
| **Shift+Tab** | Unindent |
| **Enter** | New line |
| **Backspace** | Delete character (joins lines at start) |

### Navigation

Expand Down
26 changes: 26 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Editor from './components/Editor.jsx';
import Preview from './components/Preview.jsx';
import StatusBar from './components/StatusBar.jsx';
import { syncFileOnSave } from './cloud/sync/syncOnSave.js';
import { formatMarkdown } from './utils/formatMarkdown.js';

export default function App({ filePath: initialFilePath, initialContent, layout: initialLayout, showPreview: initialShowPreview, config, onExit }) {
const [filePath, setFilePath] = useState(initialFilePath);
Expand Down Expand Up @@ -118,11 +119,36 @@ export default function App({ filePath: initialFilePath, initialContent, layout:
}
}, [filePath, content, addTimeout]);

const handleFormatDocument = useCallback(() => {
try {
const formatted = formatMarkdown(content, {
wrapColumn: config?.format?.wrapColumn,
});

if (formatted === content) {
setMessage('Document already formatted');
addTimeout(() => setMessage(''), 1500);
return;
}

setContent(formatted);
setMessage('Markdown formatted');
addTimeout(() => setMessage(''), 2000);
} catch (error) {
setMessage(`Format failed: ${error.message}`);
addTimeout(() => setMessage(''), 3000);
}
}, [content, config, addTimeout]);

useInput((input, key) => {
// Clear exit warning on any key press except Ctrl+X
if (!(key.ctrl && input === 'x') && exitWarningShown) {
setExitWarningShown(false);
}
// Ctrl+Shift+F: Format Markdown
if (key.ctrl && key.shift && input === 'f') {
handleFormatDocument();
}

// Ctrl+S: Save
if (key.ctrl && input === 's') {
Expand Down
62 changes: 53 additions & 9 deletions src/components/TextBuffer.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo } from 'react';
import React, { useState, useEffect } from 'react';
import { Box, Text, useInput, measureElement } from 'ink';
import { highlightMarkdownLine } from '../utils/syntaxHighlight.js';

Expand Down Expand Up @@ -269,16 +269,60 @@ export default function TextBuffer({ content, onChange, isFocused = true, viewpo
onChange(lines.map(l => l.text).join('\n'));
}, [lines, onChange]);

// Reset when content prop changes (initial load only)
// React to external content changes (e.g. formatting commands)
useEffect(() => {
if (lines.length === 0 || (lines.length === 1 && lines[0].text === '')) {
const newLines = content.split('\n').map((line) => ({
id: `line-${lineIdCounter++}`,
text: line
}));
setLines(newLines);
const currentContent = lines.map((line) => line.text).join('\n');
if (content === currentContent) {
return;
}
}, []);

const updatedLines = content.split('\n').map((line) => ({
id: `line-${lineIdCounter++}`,
text: line,
}));

const newCursorLine = Math.min(cursorLine, Math.max(updatedLines.length - 1, 0));
const newCursorCol = Math.min(cursorCol, updatedLines[newCursorLine]?.text.length || 0);

const previousState = {
lines: lines.map((line) => ({ ...line })),
cursorLine,
cursorCol,
};

const newState = {
lines: updatedLines.map((line) => ({ ...line })),
cursorLine: newCursorLine,
cursorCol: newCursorCol,
};

setHistory((prevHistory) => {
const truncated = prevHistory.slice(0, historyIndex + 1).map((state) => ({
lines: state.lines.map((line) => ({ ...line })),
cursorLine: state.cursorLine,
cursorCol: state.cursorCol,
}));

let nextHistory = [...truncated, previousState, newState];
if (nextHistory.length > 100) {
nextHistory = nextHistory.slice(nextHistory.length - 100);
}

setHistoryIndex(nextHistory.length - 1);
return nextHistory;
});

setLines(updatedLines);
setCursorLine(newCursorLine);
setCursorCol(newCursorCol);
setSelection(null);

const maxScroll = Math.max(updatedLines.length - viewportHeight, 0);
const adjustedScroll = Math.min(newCursorLine, maxScroll);
setScrollOffset(adjustedScroll);
// We intentionally depend only on `content` so external updates run once per change.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [content]);

// Keep cursor visible by adjusting scroll offset
useEffect(() => {
Expand Down
7 changes: 7 additions & 0 deletions src/utils/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const DEFAULT_CONFIG = {
layout: 'side', // 'side' or 'bottom'
showPreview: true,
columnWidthRatio: 75, // Editor width percentage for side-by-side
format: {
wrapColumn: 80,
},
theme: {
// VSCode Dark+ inspired colors
editor: {
Expand Down Expand Up @@ -94,6 +97,10 @@ export function loadConfig() {
return {
...DEFAULT_CONFIG,
...userConfig,
format: {
...DEFAULT_CONFIG.format,
...(userConfig.format || {}),
},
theme: {
...DEFAULT_CONFIG.theme,
...(userConfig.theme || {}),
Expand Down
Loading