Skip to content

Commit c9b9a48

Browse files
committed
use indentService, indentUnit for smart indent on non-asm files, last-line indent for asm files
1 parent 6cca8e8 commit c9b9a48

4 files changed

Lines changed: 37 additions & 99 deletions

File tree

src/ide/settings.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { indentUnit } from "@codemirror/language";
33
import { Compartment, EditorState, Extension } from "@codemirror/state";
44
import { EditorView, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, keymap } from "@codemirror/view";
55
import { debugHighlightTagsTooltip } from "./views/debug";
6-
import { insertTabKeymap, spacesSpacesKeymap } from "./views/tabs";
6+
import { insertTabKeymap, smartIndentKeymap } from "./views/tabs";
77

88
declare var bootbox;
99
declare var $: JQueryStatic;
@@ -31,10 +31,10 @@ const SETTINGS_KEY = "8bitworkshop/editorSettings";
3131
const defaultSettings: EditorSettings = {
3232
tabSize: 8,
3333
tabsToSpaces: true,
34-
highlightSpecialChars: true,
35-
highlightWhitespace: true,
36-
highlightTrailingWhitespace: true,
37-
closeBrackets: true,
34+
highlightSpecialChars: false,
35+
highlightWhitespace: false,
36+
highlightTrailingWhitespace: false,
37+
closeBrackets: false,
3838
debugHighlightTags: false,
3939
};
4040

@@ -54,7 +54,7 @@ export function saveSettings(settings: EditorSettings) {
5454

5555
const compartmentValues: [Compartment, (s: EditorSettings) => Extension][] = [
5656
[tabSizeCompartment, s => [EditorState.tabSize.of(s.tabSize), indentUnit.of(" ".repeat(s.tabSize))]],
57-
[tabsToSpacesCompartment, s => keymap.of(s.tabsToSpaces ? spacesSpacesKeymap : insertTabKeymap)],
57+
[tabsToSpacesCompartment, s => keymap.of(s.tabsToSpaces ? smartIndentKeymap : insertTabKeymap)],
5858
[highlightSpecialCharsCompartment, s => s.highlightSpecialChars ? highlightSpecialChars() : []],
5959
[highlightWhitespaceCompartment, s => s.highlightWhitespace ? highlightWhitespace() : []],
6060
[highlightTrailingWhitespaceCompartment, s => s.highlightTrailingWhitespace ? highlightTrailingWhitespace() : []],

src/ide/views/editors.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { defaultKeymap, history, historyKeymap, isolateHistory, redo, undo } from "@codemirror/commands";
1+
import { defaultKeymap, history, historyKeymap, indentSelection, isolateHistory, redo, undo } from "@codemirror/commands";
22
import { cpp } from "@codemirror/lang-cpp";
33
import { markdown } from "@codemirror/lang-markdown";
4-
import { bracketMatching, foldGutter, indentOnInput } from "@codemirror/language";
4+
import { bracketMatching, foldGutter, indentOnInput, indentService, indentUnit } from "@codemirror/language";
55
import { highlightSelectionMatches, search, searchKeymap } from "@codemirror/search";
66
import { EditorState, Extension } from "@codemirror/state";
77
import { crosshairCursor, drawSelection, dropCursor, EditorView, highlightActiveLine, highlightActiveLineGutter, keymap, lineNumbers, rectangularSelection, ViewUpdate } from "@codemirror/view";
@@ -43,8 +43,8 @@ const MODEDEFS = {
4343
inform6: { theme: cobalt },
4444
markdown: { lineWrap: true },
4545
fastbasic: { noGutters: true },
46-
basic: { noLineNumbers: true, noGutters: true }, // TODO: not used?
47-
ecs: { theme: mbo, isAsm: true },
46+
basic: { noLineNumbers: true, noGutters: true },
47+
ecs: { theme: mbo }, // TODO: is actually mixed-mode, as is verilog
4848
}
4949

5050
export var textMapFunctions = {
@@ -92,7 +92,7 @@ export class SourceEditor implements ProjectView {
9292
var isAsm = isAsmOverride || modedef.isAsm;
9393
var lineWrap = !!modedef.lineWrap;
9494
var theme = modedef.theme || MODEDEFS.default.theme;
95-
var lineNums = !modedef.noLineNumbers && !isMobileDevice;
95+
var lineNums = !isAsm && !modedef.noLineNumbers && !isMobileDevice;
9696
if (qs['embed']) {
9797
lineNums = false; // no line numbers while embedded
9898
isAsm = false; // no opcode bytes either
@@ -140,8 +140,26 @@ export class SourceEditor implements ProjectView {
140140
doc: text,
141141
extensions: [
142142

143+
// Non-asm: 2-space indent (placed before settings so it takes precedence over tabSize-based indentUnit)
144+
isAsm ? [] : indentUnit.of(" "),
145+
// Asm: copy previous line's indentation since asm parsers lack proper indent rules
146+
isAsm ? indentService.of((context, pos) => {
147+
let lineNum = context.state.doc.lineAt(pos).number;
148+
if (lineNum >= 0) {
149+
let prevLine = context.state.doc.line(lineNum);
150+
if (prevLine.text.trim()) {
151+
return context.lineIndent(prevLine.from);
152+
}
153+
}
154+
return 0;
155+
}) : [],
156+
143157
// Keybindings from settings must appear before default keymap.
144158
...settingsExtensions(loadSettings()),
159+
keymap.of([
160+
{ key: "Ctrl-Shift-i", run: indentSelection },
161+
{ key: "Cmd-Shift-i", run: indentSelection },
162+
]),
145163
keymap.of(defaultKeymap),
146164

147165
lineNums ? lineNumbers() : [],

src/ide/views/tabs.ts

Lines changed: 6 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,12 @@
1-
import { insertTab } from "@codemirror/commands";
2-
import { countColumn, EditorSelection } from "@codemirror/state";
3-
import { EditorView, KeyBinding } from "@codemirror/view";
1+
import { indentLess, indentMore, insertTab } from "@codemirror/commands";
2+
import { KeyBinding } from "@codemirror/view";
43

5-
function tab(view: EditorView): boolean {
6-
const tabSize = view.state.tabSize;
7-
view.dispatch(view.state.changeByRange(sel => {
8-
const startLine = view.state.doc.lineAt(sel.from);
9-
const endLine = view.state.doc.lineAt(sel.to);
10-
if (sel.empty) {
11-
// Cursor only: insert spaces to next tab stop
12-
const col = sel.head - startLine.from;
13-
const spaces = tabSize - (col % tabSize);
14-
const insert = " ".repeat(spaces);
15-
return {
16-
changes: { from: sel.from, insert },
17-
range: EditorSelection.cursor(sel.from + spaces),
18-
};
19-
} else if (startLine.number === endLine.number) {
20-
// Single-line selection: replace with spaces, cursor at end of whitespace
21-
const col = sel.from - startLine.from;
22-
const spaces = tabSize - (col % tabSize);
23-
const insert = " ".repeat(spaces);
24-
return {
25-
changes: { from: sel.from, to: sel.to, insert },
26-
range: EditorSelection.cursor(sel.from + spaces),
27-
};
28-
} else {
29-
// Multi-line selection: move first non-ws char to next tab stop on each line
30-
const changes = [];
31-
for (let i = startLine.number; i <= endLine.number; i++) {
32-
const line = view.state.doc.line(i);
33-
const firstNonWs = line.text.search(/\S/);
34-
if (firstNonWs < 0) continue;
35-
const newCol = (Math.floor(firstNonWs / tabSize) + 1) * tabSize;
36-
const spaces = newCol - firstNonWs;
37-
changes.push({ from: line.from + firstNonWs, insert: " ".repeat(spaces) });
38-
}
39-
const changeSet = view.state.changes(changes);
40-
return {
41-
changes: changeSet,
42-
range: EditorSelection.range(changeSet.mapPos(sel.from), changeSet.mapPos(sel.to)),
43-
};
44-
}
45-
}));
46-
return true;
47-
}
48-
49-
function shiftTab(view: EditorView): boolean {
50-
const tabSize = view.state.tabSize;
51-
const changes = [];
52-
const seen = new Set<number>();
53-
for (const sel of view.state.selection.ranges) {
54-
const startLine = view.state.doc.lineAt(sel.from);
55-
const endLine = view.state.doc.lineAt(sel.to);
56-
for (let i = startLine.number; i <= endLine.number; i++) {
57-
if (seen.has(i)) continue;
58-
seen.add(i);
59-
const line = view.state.doc.line(i);
60-
const text = line.text;
61-
const firstNonWs = text.search(/\S|$/);
62-
if (firstNonWs == -1) continue;
63-
const wsVisualCol = countColumn(text, tabSize, firstNonWs);
64-
const targetCol = Math.floor((wsVisualCol - 1) / tabSize) * tabSize;
65-
66-
// Find the character index at targetCol
67-
let col = 0;
68-
let idx = 0;
69-
while (idx < firstNonWs && col < targetCol) {
70-
if (text[idx] === '\t') {
71-
const nextStop = (Math.floor(col / tabSize) + 1) * tabSize;
72-
if (nextStop > targetCol) break;
73-
col = nextStop;
74-
} else {
75-
col++;
76-
}
77-
idx++;
78-
}
79-
changes.push({ from: line.from + idx, to: line.from + firstNonWs });
80-
}
81-
}
82-
if (changes.length) view.dispatch({ changes });
83-
return true;
84-
}
85-
86-
export const spacesSpacesKeymap: KeyBinding[] = [
87-
{ key: "Tab", run: tab },
88-
{ key: "Shift-Tab", run: shiftTab },
4+
export const smartIndentKeymap: KeyBinding[] = [
5+
{ key: "Tab", run: indentMore },
6+
{ key: "Shift-Tab", run: indentLess },
897
];
908

919
export const insertTabKeymap: KeyBinding[] = [
9210
{ key: "Tab", run: insertTab },
93-
{ key: "Shift-Tab", run: shiftTab },
11+
{ key: "Shift-Tab", run: indentLess },
9412
];

src/parser/lang-basic.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const basicParser: StreamParser<BasicState> = {
3030

3131
indent(state, textAfter, context) {
3232
const unit = context.unit;
33+
// Numbered BASIC lines don't use indentation
34+
if (/^\s*\d/.test(textAfter)) return 0;
3335
const closing = /^(?:next|end|wend|else|to|then)\b/i;
3436
if (closing.test(textAfter)) {
3537
return (state.currentIndent - 1) * unit;

0 commit comments

Comments
 (0)