mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 00:02:16 +00:00
fixed duplicate include file badges
This commit is contained in:
1 parent
5811892262
commit
e6d7ee5ca5
4 files changed
+81
-19
No files matched your search
@@ -41,6 +41,7 @@ See [extension/CHANGELOG.md](extension/CHANGELOG.md).
|
||||
|
||||
- Editor moved from CodeMirror 5 to CodeMirror 6 with Lezer grammars for 6502, Z80, 6809, Verilog, BASIC, batari BASIC, FastBasic, Inform 6 and Wiz; new Cobalt and MBO themes and better indent handling.
|
||||
- Rust syntax highlighting for `.rs` files, via `@codemirror/lang-rust`; the urust toolchain now claims the `rust` editor style instead of falling back to C.
|
||||
- A `mod foo;` line shows one include-link arrow instead of two (it had linked to both `foo.rs` and `foo/mod.rs`); the arrow now points at whichever file exists.
|
||||
- Source search across the project and toolchain headers (F1 for help), with arrow-key navigation, plus symbol search without source.
|
||||
- Shortcut bar and keyboard shortcut help, F8 pause/resume, Cmd+Shift+G go to offset.
|
||||
- Settings menu with developer options (highlight whitespace, highlight executed lines, line numbers off by default).
|
||||
|
||||
@@ -364,6 +364,9 @@ export class SourceEditor implements ProjectView {
|
||||
// filesystem via the read-only viewer
|
||||
openHeaderFile(filename);
|
||||
},
|
||||
// one directive can offer several candidates (`mod foo;` -> foo.rs
|
||||
// or foo/mod.rs); link to whichever file exists
|
||||
(filename: string) => resolveIncludeFile(filename) != null,
|
||||
),
|
||||
|
||||
textTransformFilterCompartment.of([]),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EditorState, Text } from "@codemirror/state";
|
||||
import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate, WidgetType } from "@codemirror/view";
|
||||
import { ToolIncludePattern } from "../../common/toolmeta";
|
||||
|
||||
@@ -26,12 +27,31 @@ class IncludeLinkWidget extends WidgetType {
|
||||
ignoreEvent() { return false; }
|
||||
}
|
||||
|
||||
function buildIncludeLinkDecorations(view: EditorView, patterns: (RegExp | ToolIncludePattern)[], handleClick: (filename: string, system: boolean) => void): DecorationSet {
|
||||
const widgets: any[] = [];
|
||||
const doc = view.state.doc;
|
||||
const seen = new Set<string>(); // dedupe (line number, filename)
|
||||
for (let { from, to } of view.visibleRanges) {
|
||||
const text = view.state.sliceDoc(from, to);
|
||||
/** One include badge: the document position to draw it at (end of its line). */
|
||||
export interface IncludeLink {
|
||||
pos: number;
|
||||
filename: string;
|
||||
system: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the include badges for the given document ranges. A single directive
|
||||
* can match several patterns -- urust's `mod foo;` resolves to both foo.rs
|
||||
* and foo/mod.rs -- and gets one badge, not one per pattern, so `mod` lines
|
||||
* no longer show two arrows. When `resolveFile` is given, the candidate it
|
||||
* names is preferred, so the badge links to the file that exists. Pure: no
|
||||
* editor or DOM needed.
|
||||
*/
|
||||
export function findIncludeLinks(
|
||||
doc: Text,
|
||||
ranges: readonly { from: number, to: number }[],
|
||||
patterns: (RegExp | ToolIncludePattern)[],
|
||||
resolveFile?: (filename: string) => boolean,
|
||||
): IncludeLink[] {
|
||||
// group matches by the directive that produced them: line number + match offset
|
||||
const groups = new Map<string, { line: number, candidates: { filename: string, system: boolean }[] }>();
|
||||
for (let { from, to } of ranges) {
|
||||
const text = doc.sliceString(from, to);
|
||||
for (let pat of patterns) {
|
||||
const p: ToolIncludePattern = pat instanceof RegExp ? { re: pat } : pat;
|
||||
p.re.lastIndex = 0; // patterns are shared and global, so rewind first
|
||||
@@ -42,29 +62,53 @@ function buildIncludeLinkDecorations(view: EditorView, patterns: (RegExp | ToolI
|
||||
// apply any implicit extension (e.g. wiz `import "nes"` -> "nes.wiz"),
|
||||
// same as matchDependencyPatterns() does for build deps
|
||||
const filename = p.suffix ? matched + p.suffix : matched;
|
||||
// place widget at end of the line containing the match
|
||||
const matchEnd = Math.min(from + m.index + m[0].length, doc.length);
|
||||
const line = doc.lineAt(matchEnd);
|
||||
const key = line.number + ':' + filename;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
widgets.push(
|
||||
Decoration.widget({
|
||||
widget: new IncludeLinkWidget(filename, !!p.system, handleClick),
|
||||
side: 1,
|
||||
}).range(line.to)
|
||||
);
|
||||
const key = line.number + ':' + (from + m.index);
|
||||
let group = groups.get(key);
|
||||
if (!group) {
|
||||
group = { line: line.number, candidates: [] };
|
||||
groups.set(key, group);
|
||||
}
|
||||
group.candidates.push({ filename, system: !!p.system });
|
||||
}
|
||||
}
|
||||
}
|
||||
const links: IncludeLink[] = [];
|
||||
for (let group of groups.values()) {
|
||||
let chosen = group.candidates[0];
|
||||
if (resolveFile) {
|
||||
chosen = group.candidates.find(c => resolveFile(c.filename)) || chosen;
|
||||
}
|
||||
links.push({ pos: doc.line(group.line).to, filename: chosen.filename, system: chosen.system });
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
function buildIncludeLinkDecorations(
|
||||
state: EditorState,
|
||||
ranges: readonly { from: number, to: number }[],
|
||||
patterns: (RegExp | ToolIncludePattern)[],
|
||||
handleClick: (filename: string, system: boolean) => void,
|
||||
resolveFile?: (filename: string) => boolean,
|
||||
): DecorationSet {
|
||||
const widgets = findIncludeLinks(state.doc, ranges, patterns, resolveFile).map(link =>
|
||||
Decoration.widget({
|
||||
widget: new IncludeLinkWidget(link.filename, link.system, handleClick),
|
||||
side: 1,
|
||||
}).range(link.pos));
|
||||
return Decoration.set(widgets, true);
|
||||
}
|
||||
|
||||
export function createIncludeLinkPlugin(getPatterns: () => (RegExp | ToolIncludePattern)[], onClick: (filename: string, system: boolean) => void) {
|
||||
export function createIncludeLinkPlugin(
|
||||
getPatterns: () => (RegExp | ToolIncludePattern)[],
|
||||
onClick: (filename: string, system: boolean) => void,
|
||||
resolveFile?: (filename: string) => boolean,
|
||||
) {
|
||||
function build(view: EditorView): DecorationSet {
|
||||
const patterns = getPatterns();
|
||||
if (!patterns || !patterns.length) return Decoration.none;
|
||||
return buildIncludeLinkDecorations(view, patterns, onClick);
|
||||
return buildIncludeLinkDecorations(view.state, view.visibleRanges, patterns, onClick, resolveFile);
|
||||
}
|
||||
return ViewPlugin.fromClass(class {
|
||||
decorations: DecorationSet;
|
||||
|
||||
@@ -3,7 +3,8 @@ import { describe, it } from "mocha";
|
||||
import { rust } from "@codemirror/lang-rust";
|
||||
import { syntaxTree } from "@codemirror/language";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { TOOL_META, getToolMetaForFilename } from "../../src/common/toolmeta";
|
||||
import { TOOL_META, URUST_INCLUDE_PATTERNS, getToolMetaForFilename } from "../../src/common/toolmeta";
|
||||
import { findIncludeLinks } from "../../src/ide/views/includedecorations";
|
||||
import { extractSymbols } from "../../src/common/symbols";
|
||||
|
||||
describe('Rust editor support', function () {
|
||||
@@ -22,6 +23,19 @@ describe('Rust editor support', function () {
|
||||
}
|
||||
});
|
||||
|
||||
it('shows one include badge per mod line, preferring the file that exists', function () {
|
||||
const doc = EditorState.create({ doc: 'mod audio;\nmod bcd;\n' }).doc;
|
||||
const ranges = [{ from: 0, to: doc.length }];
|
||||
// both foo.rs and foo/mod.rs patterns match each mod line; keep one badge
|
||||
assert.deepStrictEqual(
|
||||
findIncludeLinks(doc, ranges, URUST_INCLUDE_PATTERNS).map(l => l.filename),
|
||||
['audio.rs', 'bcd.rs']);
|
||||
// if only the subdirectory form exists, link to that one
|
||||
assert.deepStrictEqual(
|
||||
findIncludeLinks(doc, ranges, URUST_INCLUDE_PATTERNS, fn => fn === 'audio/mod.rs').map(l => l.filename),
|
||||
['audio/mod.rs', 'bcd.rs']);
|
||||
});
|
||||
|
||||
it('extracts Rust fn declarations as symbols', function () {
|
||||
const records = extractSymbols('fn draw(x: u8) {}\nfn main() {}\n', 'main.rs', 'rust', 'project');
|
||||
assert.deepStrictEqual(records.filter(r => r.kind === 'func').map(r => r.name), ['draw', 'main']);
|
||||
|
||||
Reference in new issue
Block a user