@gjsify/adwaita-web 0.57.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/types/gio.spec.d.ts +1 -0
- package/lib/types/gtksource/gtk-source-view.d.ts +4 -3
- package/lib/types/gtksource/web-editor-driver.d.ts +9 -0
- package/lib/types/namespace/adw.d.ts +1 -0
- package/lib/types/namespace/gio.d.ts +1 -1
- package/lib/types/namespace/glib.d.ts +5 -0
- package/lib/types/namespace/gobject.d.ts +1 -0
- package/lib/types/namespace/gtksource.d.ts +1 -1
- package/lib/types/style-manager.d.ts +5 -0
- package/lib/types/style-manager.spec.d.ts +1 -0
- package/package.json +9 -9
- package/src/application.spec.ts +1 -1
- package/src/gio.spec.ts +23 -0
- package/src/glib.spec.ts +17 -2
- package/src/gtksource/gtk-source-view.spec.ts +74 -2
- package/src/gtksource/gtk-source-view.ts +16 -9
- package/src/gtksource/web-editor-driver.ts +96 -10
- package/src/namespace/adw.ts +1 -0
- package/src/namespace/gio.ts +2 -2
- package/src/namespace/glib.ts +17 -2
- package/src/namespace/gobject.ts +1 -0
- package/src/namespace/gtksource.ts +14 -3
- package/src/style-manager.spec.ts +36 -0
- package/src/style-manager.ts +27 -0
- package/src/test.browser.mts +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const GioActionsTest: () => Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GtkAdjustment } from '@gjsify/adwaita-core';
|
|
2
|
-
import type { Buffer } from '@gjsify/gtksource-core';
|
|
2
|
+
import type { Buffer, Gutter } from '@gjsify/gtksource-core';
|
|
3
3
|
export declare class GtkSourceView extends HTMLElement {
|
|
4
4
|
static get observedAttributes(): string[];
|
|
5
5
|
private readonly session;
|
|
@@ -12,6 +12,7 @@ export declare class GtkSourceView extends HTMLElement {
|
|
|
12
12
|
private readonly vadj;
|
|
13
13
|
private readonly hadj;
|
|
14
14
|
private direction;
|
|
15
|
+
private readonly gutters;
|
|
15
16
|
constructor();
|
|
16
17
|
private syncAdjustments;
|
|
17
18
|
private readonly onAreaScroll;
|
|
@@ -59,8 +60,8 @@ export declare class GtkSourceView extends HTMLElement {
|
|
|
59
60
|
get_first_child(): null;
|
|
60
61
|
get_next_sibling(): Element | null;
|
|
61
62
|
get_parent(): Element | null;
|
|
62
|
-
/**
|
|
63
|
-
get_gutter(
|
|
63
|
+
/** `null` for any side but LEFT and RIGHT, as in GtkSourceView (ADR 0103). */
|
|
64
|
+
get_gutter(window_type: number): Gutter | null;
|
|
64
65
|
get leftMargin(): number;
|
|
65
66
|
set leftMargin(value: number | string);
|
|
66
67
|
get rightMargin(): number;
|
|
@@ -14,6 +14,7 @@ export declare class WebEditorDriver implements EditorDriver {
|
|
|
14
14
|
private layout;
|
|
15
15
|
private readonly runs;
|
|
16
16
|
private scheduled;
|
|
17
|
+
private numbers;
|
|
17
18
|
private listening;
|
|
18
19
|
private palette;
|
|
19
20
|
private attached;
|
|
@@ -35,10 +36,18 @@ export declare class WebEditorDriver implements EditorDriver {
|
|
|
35
36
|
private applyLayout;
|
|
36
37
|
setPalette(palette: EditorPalette): void;
|
|
37
38
|
private applyPalette;
|
|
39
|
+
invalidateGutter(): void;
|
|
38
40
|
spliceLines(first: number, removed: number, inserted: number): void;
|
|
39
41
|
paintLine(line: number, _lineStart: number, runs: readonly StyledRun[]): void;
|
|
40
42
|
clearAll(): void;
|
|
41
43
|
/** One repaint per task: a burst of `paintLine` calls from one edit costs one DOM rebuild. */
|
|
42
44
|
private scheduleRender;
|
|
45
|
+
private hasColumns;
|
|
43
46
|
private render;
|
|
47
|
+
/** The lines in view, or all of them where nothing is laid out (a detached element, a test DOM). */
|
|
48
|
+
private visibleLines;
|
|
49
|
+
private cellElement;
|
|
50
|
+
private columnElement;
|
|
51
|
+
/** The gutter: the renderers left of the line numbers, the numbers, the renderers right of them. */
|
|
52
|
+
private renderGutter;
|
|
44
53
|
}
|
|
@@ -55,3 +55,4 @@ export { AdwWindow as Window } from '../elements/adw-window.js';
|
|
|
55
55
|
export { AdwWindowTitle as WindowTitle } from '../elements/adw-window-title.js';
|
|
56
56
|
export { AdwWrapBox as WrapBox } from '../elements/adw-wrap-box.js';
|
|
57
57
|
export { AdwApplication as Application } from '../application.js';
|
|
58
|
+
export { AdwStyleManager as StyleManager } from '../style-manager.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { GioMenu as Menu, GioMenuItem as MenuItem } from '@gjsify/adwaita-core';
|
|
2
|
-
export { SimpleAction, ApplicationFlags } from '@gjsify/adwaita-core';
|
|
2
|
+
export { SimpleAction, SimpleActionGroup, ApplicationFlags } from '@gjsify/adwaita-core';
|
|
3
3
|
export { GioApplication as Application } from '../application.js';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildFilenamev, getCurrentDir, getSystemDataDirs } from '@gjsify/adwaita-core';
|
|
1
2
|
export declare const PRIORITY_HIGH: number;
|
|
2
3
|
export declare const PRIORITY_DEFAULT: number;
|
|
3
4
|
export declare const PRIORITY_HIGH_IDLE: number;
|
|
@@ -8,3 +9,7 @@ export declare const SOURCE_CONTINUE: boolean;
|
|
|
8
9
|
export declare const idle_add: (priority: number, run: import("@gjsify/adwaita-core").SourceFunc) => number;
|
|
9
10
|
export declare const timeout_add: (priority: number, milliseconds: number, run: import("@gjsify/adwaita-core").SourceFunc) => number;
|
|
10
11
|
export declare const source_remove: (sourceId: number) => boolean;
|
|
12
|
+
export declare const MAXUINT32 = 4294967295;
|
|
13
|
+
export declare const build_filenamev: typeof buildFilenamev;
|
|
14
|
+
export declare const get_current_dir: typeof getCurrentDir;
|
|
15
|
+
export declare const get_system_data_dirs: typeof getSystemDataDirs;
|
|
@@ -2,6 +2,7 @@ declare const GObjectObject: import("@gjsify/adwaita-core").GObjectConstructor;
|
|
|
2
2
|
export { GObjectObject as Object };
|
|
3
3
|
export declare const registerClass: import("@gjsify/adwaita-core").RegisterClass;
|
|
4
4
|
export declare const type_ensure: (gtype: import("@gjsify/adwaita-core").GType) => void;
|
|
5
|
+
export declare const signal_stop_emission_by_name: (instance: import("@gjsify/adwaita-core").GObjectInstance, detailedSignal: string) => void;
|
|
5
6
|
export declare const ParamSpec: typeof import("@gjsify/adwaita-core").ParamSpec;
|
|
6
7
|
export declare const ParamFlags: {
|
|
7
8
|
readonly READABLE: 1;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Buffer, Language, LanguageManager, StyleScheme, StyleSchemeManager, init } from '@gjsify/gtksource-core';
|
|
1
|
+
export { Buffer, Gutter, GutterLines, GutterRenderer, GutterRendererText, Language, LanguageManager, StyleScheme, StyleSchemeManager, init, } from '@gjsify/gtksource-core';
|
|
2
2
|
export { GtkSourceView as View } from '../gtksource/gtk-source-view.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const StyleManagerTest: () => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/adwaita-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "Adwaita/Libadwaita web components for browser targets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -50,17 +50,17 @@
|
|
|
50
50
|
"@codemirror/language": "^6.12.4",
|
|
51
51
|
"@codemirror/state": "^6.7.0",
|
|
52
52
|
"@codemirror/view": "^6.43.4",
|
|
53
|
-
"@gjsify/adwaita-core": "^0.
|
|
54
|
-
"@gjsify/adwaita-fonts": "^0.
|
|
55
|
-
"@gjsify/adwaita-icons": "^0.
|
|
56
|
-
"@gjsify/gtksource-core": "^0.
|
|
53
|
+
"@gjsify/adwaita-core": "^0.58.0",
|
|
54
|
+
"@gjsify/adwaita-fonts": "^0.58.0",
|
|
55
|
+
"@gjsify/adwaita-icons": "^0.58.0",
|
|
56
|
+
"@gjsify/gtksource-core": "^0.58.0",
|
|
57
57
|
"@lezer/highlight": "^1.2.3"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@gjsify/blueprint": "^0.
|
|
61
|
-
"@gjsify/cli": "^0.
|
|
62
|
-
"@gjsify/unit": "^0.
|
|
63
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
60
|
+
"@gjsify/blueprint": "^0.58.0",
|
|
61
|
+
"@gjsify/cli": "^0.58.0",
|
|
62
|
+
"@gjsify/unit": "^0.58.0",
|
|
63
|
+
"@gjsify/vite-plugin-blueprint": "^0.58.0",
|
|
64
64
|
"sass": "^1.101.0",
|
|
65
65
|
"typescript": "^6.0.3"
|
|
66
66
|
},
|
package/src/application.spec.ts
CHANGED
|
@@ -58,7 +58,7 @@ export const ApplicationTest = async () => {
|
|
|
58
58
|
|
|
59
59
|
await describe('adwaita-web: Application (ADR 0098 § 3)', async () => {
|
|
60
60
|
await it('has a vector for every unlocked vfunc', () => {
|
|
61
|
-
expect(unprovenVfuncs(APPLICATION_VECTORS).length).toBe(0);
|
|
61
|
+
expect(unprovenVfuncs(APPLICATION_VECTORS, 'Gio.').length).toBe(0);
|
|
62
62
|
});
|
|
63
63
|
await it('Adw.Application is a Gtk.Application', () => {
|
|
64
64
|
expect(Adw.Application.prototype instanceof Gtk.Application).toBe(true);
|
package/src/gio.spec.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The `Gio.SimpleActionGroup` door of adwaita-web, held to the shared vectors (real GJS is their
|
|
2
|
+
// oracle, in adwaita-core's `simple-action-group.gjs.spec.ts`). The door is the namespace module itself.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
SIMPLE_ACTION_GROUP_VECTORS,
|
|
6
|
+
driveSimpleActionGroupVectors,
|
|
7
|
+
type GioActionsLike,
|
|
8
|
+
} from '@gjsify/adwaita-core/conformance';
|
|
9
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
10
|
+
|
|
11
|
+
import * as Gio from './namespace/gio.js';
|
|
12
|
+
|
|
13
|
+
export const GioActionsTest = async () => {
|
|
14
|
+
await driveSimpleActionGroupVectors(
|
|
15
|
+
{ name: 'adwaita-web', isOracle: false, Gio: Gio as unknown as GioActionsLike },
|
|
16
|
+
{ describe, it, expect },
|
|
17
|
+
);
|
|
18
|
+
await describe('adwaita-web: Gio door', async () => {
|
|
19
|
+
await it('has a vector row for every vector', () => {
|
|
20
|
+
expect(SIMPLE_ACTION_GROUP_VECTORS.length > 0).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
};
|
package/src/glib.spec.ts
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
// adwaita-core's `glib-timers.gjs.spec.ts`). The door is the namespace module itself, so a member
|
|
3
3
|
// that stops being exported fails here.
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
GLIB_PATH_VECTORS,
|
|
7
|
+
GLIB_TIMER_VECTORS,
|
|
8
|
+
driveGLibPathVectors,
|
|
9
|
+
driveGLibTimerVectors,
|
|
10
|
+
type GLibPathsLike,
|
|
11
|
+
type GLibTimersLike,
|
|
12
|
+
} from '@gjsify/adwaita-core/conformance';
|
|
6
13
|
import { describe, expect, it } from '@gjsify/unit';
|
|
7
14
|
|
|
8
15
|
import * as GLib from './namespace/glib.js';
|
|
@@ -26,9 +33,17 @@ export const GLibTest = async () => {
|
|
|
26
33
|
},
|
|
27
34
|
{ describe, it, expect },
|
|
28
35
|
);
|
|
36
|
+
await driveGLibPathVectors(
|
|
37
|
+
{ name: 'adwaita-web', isOracle: false, GLib: GLib as unknown as GLibPathsLike },
|
|
38
|
+
{ describe, it, expect },
|
|
39
|
+
);
|
|
29
40
|
await describe('adwaita-web: GLib door', async () => {
|
|
41
|
+
await it('answers the host-bound path questions with the root and no data dirs', () => {
|
|
42
|
+
expect(GLib.get_current_dir()).toBe('/');
|
|
43
|
+
expect(GLib.get_system_data_dirs().length).toBe(0);
|
|
44
|
+
});
|
|
30
45
|
await it('has a vector row for every vector', () => {
|
|
31
|
-
expect(GLIB_TIMER_VECTORS.length > 0).toBe(true);
|
|
46
|
+
expect(GLIB_TIMER_VECTORS.length > 0 && GLIB_PATH_VECTORS.length > 0).toBe(true);
|
|
32
47
|
});
|
|
33
48
|
});
|
|
34
49
|
};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { describe, expect, it } from '@gjsify/unit';
|
|
2
2
|
import {
|
|
3
|
+
GUTTER_PAINT_VECTORS,
|
|
3
4
|
GTKSOURCE_BUFFER_VECTORS,
|
|
4
5
|
GTKSOURCE_VIEW_DEFAULT_VECTORS,
|
|
5
6
|
GTKSOURCE_VIEW_SURFACE_VECTORS,
|
|
6
7
|
type GtkSourceViewSurfaceLike,
|
|
7
8
|
type GtkSourceBufferLike,
|
|
9
|
+
type GutterPaintSurface,
|
|
10
|
+
type ViewLike,
|
|
8
11
|
} from '@gjsify/gtksource-core/conformance';
|
|
9
12
|
|
|
13
|
+
import * as GObject from '../namespace/gobject.js';
|
|
10
14
|
import * as Gtk from '../namespace/gtk.js';
|
|
11
15
|
import * as GtkSource from '../namespace/gtksource.js';
|
|
12
16
|
|
|
@@ -36,8 +40,76 @@ export const GtkSourceViewTest = async () => {
|
|
|
36
40
|
});
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const task = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
44
|
+
for (const vector of GUTTER_PAINT_VECTORS) {
|
|
45
|
+
await it(`gutter paint: ${vector.rule}`, async () => {
|
|
46
|
+
const views: HTMLElement[] = [];
|
|
47
|
+
const harness = {
|
|
48
|
+
async present(view: ViewLike) {
|
|
49
|
+
document.body.append(view as unknown as HTMLElement);
|
|
50
|
+
views.push(view as unknown as HTMLElement);
|
|
51
|
+
await task();
|
|
52
|
+
},
|
|
53
|
+
settle: task,
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
expect(
|
|
57
|
+
await vector.observe({ Gtk, GtkSource, GObject } as unknown as GutterPaintSurface, harness),
|
|
58
|
+
).toStrictEqual(vector.shows);
|
|
59
|
+
} finally {
|
|
60
|
+
for (const view of views) view.remove();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await it("paints a renderer's text, in a column left of the line numbers when its position is negative", async () => {
|
|
66
|
+
const view = document.createElement('gtk-source-view') as GtkSourceView;
|
|
67
|
+
view.showLineNumbers = true;
|
|
68
|
+
document.body.append(view);
|
|
69
|
+
view.buffer.text = 'a\nb';
|
|
70
|
+
const renderer = new GtkSource.GutterRendererText({ width_request: 24, margin_start: 2, margin_end: 3 });
|
|
71
|
+
(renderer as unknown as { vfunc_query_data(lines: unknown, line: number): void }).vfunc_query_data =
|
|
72
|
+
function (this: { text: string }, _lines, line) {
|
|
73
|
+
this.text = `r${line}`;
|
|
74
|
+
};
|
|
75
|
+
view.get_gutter(Gtk.TextWindowType.LEFT)!.insert(renderer, -1);
|
|
76
|
+
await task();
|
|
77
|
+
const columns = view.querySelectorAll<HTMLElement>('.gsv-columns > *');
|
|
78
|
+
expect([...columns].map((column) => column.className)).toStrictEqual(['gsv-column', 'gsv-numbers']);
|
|
79
|
+
const first = columns[0];
|
|
80
|
+
expect([...first.querySelectorAll('.gsv-cell')].map((cell) => cell.textContent)).toStrictEqual([
|
|
81
|
+
'r0',
|
|
82
|
+
'r1',
|
|
83
|
+
]);
|
|
84
|
+
expect([first.style.minWidth, first.style.paddingLeft, first.style.paddingRight]).toStrictEqual([
|
|
85
|
+
'24px',
|
|
86
|
+
'2px',
|
|
87
|
+
'3px',
|
|
88
|
+
]);
|
|
89
|
+
expect(view.classList.contains('has-columns')).toBe(true);
|
|
90
|
+
view.remove();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await it('paints markup as styled spans and drops the column with its renderer', async () => {
|
|
94
|
+
const view = document.createElement('gtk-source-view') as GtkSourceView;
|
|
95
|
+
document.body.append(view);
|
|
96
|
+
view.buffer.text = 'a';
|
|
97
|
+
const renderer = new GtkSource.GutterRendererText({ width_request: 10 });
|
|
98
|
+
(renderer as unknown as { vfunc_query_data(lines: unknown, line: number): void }).vfunc_query_data =
|
|
99
|
+
function (this: { markup: string }) {
|
|
100
|
+
this.markup = '<b>x</b> & y';
|
|
101
|
+
};
|
|
102
|
+
const gutter = view.get_gutter(Gtk.TextWindowType.LEFT)!;
|
|
103
|
+
gutter.insert(renderer, 0);
|
|
104
|
+
await task();
|
|
105
|
+
const cell = view.querySelector<HTMLElement>('.gsv-cell')!;
|
|
106
|
+
expect(cell.textContent).toBe('x & y');
|
|
107
|
+
expect(cell.querySelector<HTMLElement>('span')!.style.fontWeight).toBe('bold');
|
|
108
|
+
gutter.remove(renderer);
|
|
109
|
+
await task();
|
|
110
|
+
expect(view.querySelector('.gsv-column')).toBe(null);
|
|
111
|
+
expect(view.classList.contains('has-columns')).toBe(false);
|
|
112
|
+
view.remove();
|
|
41
113
|
});
|
|
42
114
|
|
|
43
115
|
await it('writes through attributes with GTK semantics', () => {
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
// IN THIS SLICE: `buffer`, `auto-indent`, `indent-width` (held and read back, as on Android),
|
|
11
11
|
// `show-line-numbers`, `highlight-current-line`, `monospace`, `editable`, `cursor-visible`, the four
|
|
12
12
|
// margins, `connect`/`disconnect`, the GJS snake_case accessors, `vadjustment`/`hadjustment`,
|
|
13
|
-
// `set_direction`, `get_first_child`/`get_next_sibling
|
|
13
|
+
// `set_direction`, `get_first_child`/`get_next_sibling`, `get_gutter` (the LEFT gutter paints its renderers' `vfunc_query_data`; the right one is a model).
|
|
14
14
|
//
|
|
15
15
|
// Reference: GtkSourceView 5 gtksourceview.c, upstream GNOME/gtksourceview (properties and defaults)
|
|
16
16
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
17
17
|
|
|
18
18
|
import { adwaitaColorScheme, GtkAdjustment, onAdwaitaColorSchemeChanged } from '@gjsify/adwaita-core';
|
|
19
|
-
import type { Buffer } from '@gjsify/gtksource-core';
|
|
20
|
-
import { EditorSession, toBoolean, toNumber } from '@gjsify/gtksource-core';
|
|
19
|
+
import type { Buffer, Gutter } from '@gjsify/gtksource-core';
|
|
20
|
+
import { EditorSession, GutterSet, toBoolean, toNumber } from '@gjsify/gtksource-core';
|
|
21
21
|
|
|
22
22
|
import { WebEditorDriver } from './web-editor-driver.js';
|
|
23
23
|
|
|
@@ -44,15 +44,20 @@ gtk-source-view .gsv-gutter {
|
|
|
44
44
|
flex: none;
|
|
45
45
|
overflow: hidden;
|
|
46
46
|
box-sizing: content-box;
|
|
47
|
-
padding: var(--gsv-margin-top, 0px)
|
|
48
|
-
width: var(--gsv-gutter-width, 2ch);
|
|
47
|
+
padding: var(--gsv-margin-top, 0px) 0 0;
|
|
49
48
|
text-align: end;
|
|
50
49
|
line-height: var(--gsv-line-height);
|
|
51
50
|
color: var(--gsv-number-fg);
|
|
52
51
|
background: var(--gsv-number-bg);
|
|
53
52
|
user-select: none;
|
|
54
53
|
}
|
|
55
|
-
gtk-source-view.show-line-numbers .gsv-gutter
|
|
54
|
+
gtk-source-view.show-line-numbers .gsv-gutter,
|
|
55
|
+
gtk-source-view.has-columns .gsv-gutter { display: block; }
|
|
56
|
+
gtk-source-view .gsv-columns { display: flex; }
|
|
57
|
+
gtk-source-view .gsv-numbers,
|
|
58
|
+
gtk-source-view .gsv-column { flex: none; box-sizing: content-box; }
|
|
59
|
+
gtk-source-view .gsv-numbers { width: var(--gsv-gutter-width, 2ch); padding: 0 var(--gsv-gutter-padding, 8px); }
|
|
60
|
+
gtk-source-view .gsv-cell { height: calc(1em * var(--gsv-line-height)); white-space: pre; text-align: start; }
|
|
56
61
|
gtk-source-view .gsv-number.current { color: var(--gsv-current-number-fg); background: var(--gsv-current-number-bg); }
|
|
57
62
|
gtk-source-view .gsv-stage { position: relative; flex: 1 1 auto; min-width: 0; }
|
|
58
63
|
gtk-source-view .gsv-backdrop,
|
|
@@ -138,6 +143,7 @@ export class GtkSourceView extends HTMLElement {
|
|
|
138
143
|
private readonly vadj = new GtkAdjustment();
|
|
139
144
|
private readonly hadj = new GtkAdjustment();
|
|
140
145
|
private direction = 1;
|
|
146
|
+
private readonly gutters = new GutterSet(this);
|
|
141
147
|
|
|
142
148
|
constructor() {
|
|
143
149
|
super();
|
|
@@ -155,6 +161,7 @@ export class GtkSourceView extends HTMLElement {
|
|
|
155
161
|
gutter: this.gutter,
|
|
156
162
|
});
|
|
157
163
|
this.session = new EditorSession(this.driver, undefined, adwaitaColorScheme());
|
|
164
|
+
this.session.bindGutters(this.gutters);
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
private syncAdjustments(): void {
|
|
@@ -328,9 +335,9 @@ export class GtkSourceView extends HTMLElement {
|
|
|
328
335
|
return this.parentElement;
|
|
329
336
|
}
|
|
330
337
|
|
|
331
|
-
/**
|
|
332
|
-
get_gutter(
|
|
333
|
-
|
|
338
|
+
/** `null` for any side but LEFT and RIGHT, as in GtkSourceView (ADR 0103). */
|
|
339
|
+
get_gutter(window_type: number): Gutter | null {
|
|
340
|
+
return this.gutters.get(window_type);
|
|
334
341
|
}
|
|
335
342
|
|
|
336
343
|
get leftMargin(): number {
|
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
// NO WORD WRAP, as on Android: `wrap="off"` and `white-space: pre` keep every logical line on
|
|
8
8
|
// one layout line, so the backdrop and the gutter stay aligned by a fixed line height.
|
|
9
9
|
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
EditorDriver,
|
|
12
|
+
EditorHost,
|
|
13
|
+
EditorLayout,
|
|
14
|
+
EditorPalette,
|
|
15
|
+
GutterCell,
|
|
16
|
+
GutterColumn,
|
|
17
|
+
StyledRun,
|
|
18
|
+
} from '@gjsify/gtksource-core';
|
|
11
19
|
import { digitCount, LineStore } from '@gjsify/gtksource-core';
|
|
12
20
|
|
|
13
21
|
const GUTTER_PADDING_PX = 8;
|
|
@@ -32,7 +40,8 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
32
40
|
private reported = { start: 0, end: 0 };
|
|
33
41
|
private layout: EditorLayout | null = null;
|
|
34
42
|
private readonly runs = new LineStore<readonly StyledRun[]>();
|
|
35
|
-
private scheduled =
|
|
43
|
+
private scheduled: 'none' | 'gutter' | 'all' = 'none';
|
|
44
|
+
private numbers: HTMLElement | null = null;
|
|
36
45
|
private listening: Array<() => void> = [];
|
|
37
46
|
private palette: EditorPalette | null = null;
|
|
38
47
|
private attached = false;
|
|
@@ -60,6 +69,8 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
60
69
|
'transform',
|
|
61
70
|
`translateY(${-area.scrollTop}px)`,
|
|
62
71
|
);
|
|
72
|
+
// Only the lines in view are asked; a scroll brings new ones.
|
|
73
|
+
if (this.hasColumns) this.scheduleRender('gutter');
|
|
63
74
|
});
|
|
64
75
|
on(area, 'select', () => this.onSelection());
|
|
65
76
|
on(area, 'keyup', () => this.onSelection());
|
|
@@ -188,6 +199,10 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
188
199
|
style.setProperty('--gsv-current-number-bg', cssColor(palette.currentLineNumberBackground));
|
|
189
200
|
}
|
|
190
201
|
|
|
202
|
+
invalidateGutter(): void {
|
|
203
|
+
this.scheduleRender('gutter');
|
|
204
|
+
}
|
|
205
|
+
|
|
191
206
|
spliceLines(first: number, removed: number, inserted: number): void {
|
|
192
207
|
this.runs.splice(first, removed, inserted);
|
|
193
208
|
}
|
|
@@ -205,17 +220,23 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
205
220
|
// --- painting ----------------------------------------------------------------------------
|
|
206
221
|
|
|
207
222
|
/** One repaint per task: a burst of `paintLine` calls from one edit costs one DOM rebuild. */
|
|
208
|
-
private scheduleRender(): void {
|
|
209
|
-
if (
|
|
210
|
-
this.scheduled
|
|
223
|
+
private scheduleRender(only: 'gutter' | 'all' = 'all'): void {
|
|
224
|
+
if (!this.attached) return;
|
|
225
|
+
const pending = this.scheduled;
|
|
226
|
+
this.scheduled = only === 'all' || pending === 'all' ? 'all' : 'gutter';
|
|
227
|
+
if (pending !== 'none') return;
|
|
211
228
|
queueMicrotask(() => {
|
|
212
|
-
this.scheduled
|
|
213
|
-
this.
|
|
229
|
+
const level = this.scheduled;
|
|
230
|
+
this.scheduled = 'none';
|
|
231
|
+
if (level === 'all') this.render();
|
|
232
|
+
else if (level === 'gutter') this.renderGutter();
|
|
214
233
|
});
|
|
215
234
|
}
|
|
216
235
|
|
|
236
|
+
private hasColumns = false;
|
|
237
|
+
|
|
217
238
|
private render(): void {
|
|
218
|
-
const { backdrop
|
|
239
|
+
const { backdrop } = this.parts;
|
|
219
240
|
const lines = this.text.split('\n');
|
|
220
241
|
const highlight = this.layout?.highlightCurrentLine === true;
|
|
221
242
|
const caretLine = this.text.slice(0, this.caret).split('\n').length - 1;
|
|
@@ -252,9 +273,74 @@ export class WebEditorDriver implements EditorDriver {
|
|
|
252
273
|
const column = document.createElement('div');
|
|
253
274
|
column.className = 'gsv-numbers';
|
|
254
275
|
column.append(...numbers);
|
|
255
|
-
|
|
256
|
-
|
|
276
|
+
this.numbers = column;
|
|
277
|
+
const { root } = this.parts;
|
|
257
278
|
root.style.setProperty('--gsv-gutter-width', `${digitCount(lines.length) + 0}ch`);
|
|
258
279
|
root.style.setProperty('--gsv-gutter-padding', `${GUTTER_PADDING_PX}px`);
|
|
280
|
+
this.renderGutter();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The lines in view, or all of them where nothing is laid out (a detached element, a test DOM). */
|
|
284
|
+
private visibleLines(count: number): [number, number] {
|
|
285
|
+
const { area, backdrop } = this.parts;
|
|
286
|
+
const row = backdrop.firstElementChild;
|
|
287
|
+
const height = row === null ? 0 : row.getBoundingClientRect().height;
|
|
288
|
+
if (height <= 0 || area.clientHeight <= 0) return [0, count - 1];
|
|
289
|
+
const top = Math.max(0, area.scrollTop - (this.layout?.topMargin ?? 0));
|
|
290
|
+
const first = Math.min(count - 1, Math.floor(top / height));
|
|
291
|
+
const last = Math.min(count - 1, Math.max(first, Math.ceil((top + area.clientHeight) / height) - 1));
|
|
292
|
+
return [first, last];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private cellElement(cell: GutterCell): HTMLElement {
|
|
296
|
+
const element = document.createElement('div');
|
|
297
|
+
element.className = 'gsv-cell';
|
|
298
|
+
if (cell.runs === null) {
|
|
299
|
+
element.textContent = cell.text;
|
|
300
|
+
return element;
|
|
301
|
+
}
|
|
302
|
+
for (const run of cell.runs) {
|
|
303
|
+
const span = document.createElement('span');
|
|
304
|
+
span.textContent = run.text;
|
|
305
|
+
if (run.bold) span.style.fontWeight = 'bold';
|
|
306
|
+
if (run.italic) span.style.fontStyle = 'italic';
|
|
307
|
+
const decorations = [run.underline ? 'underline' : '', run.strikethrough ? 'line-through' : ''];
|
|
308
|
+
span.style.textDecoration = decorations.filter(Boolean).join(' ');
|
|
309
|
+
element.append(span);
|
|
310
|
+
}
|
|
311
|
+
return element;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private columnElement(column: GutterColumn, first: number): HTMLElement {
|
|
315
|
+
const element = document.createElement('div');
|
|
316
|
+
element.className = 'gsv-column';
|
|
317
|
+
element.style.minWidth = `${column.widthRequest}px`;
|
|
318
|
+
element.style.paddingLeft = `${column.marginStart}px`;
|
|
319
|
+
element.style.paddingRight = `${column.marginEnd}px`;
|
|
320
|
+
const spacer = document.createElement('div');
|
|
321
|
+
spacer.style.height = `calc(${first} * 1em * var(--gsv-line-height))`;
|
|
322
|
+
element.append(spacer, ...column.cells.map((cell) => this.cellElement(cell)));
|
|
323
|
+
return element;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** The gutter: the renderers left of the line numbers, the numbers, the renderers right of them. */
|
|
327
|
+
private renderGutter(): void {
|
|
328
|
+
const { gutter, root, area } = this.parts;
|
|
329
|
+
const host = this.host;
|
|
330
|
+
const count = this.text.split('\n').length;
|
|
331
|
+
const [first, last] = this.visibleLines(count);
|
|
332
|
+
const columns = host?.queryGutter(first, last) ?? [];
|
|
333
|
+
this.hasColumns = columns.length > 0;
|
|
334
|
+
const showNumbers = this.layout?.showLineNumbers === true;
|
|
335
|
+
root.classList.toggle('has-columns', this.hasColumns);
|
|
336
|
+
const elements: HTMLElement[] = [];
|
|
337
|
+
for (const column of columns.filter((c) => c.position < 0)) elements.push(this.columnElement(column, first));
|
|
338
|
+
if (showNumbers && this.numbers !== null) elements.push(this.numbers);
|
|
339
|
+
for (const column of columns.filter((c) => c.position >= 0)) elements.push(this.columnElement(column, first));
|
|
340
|
+
const wrapper = document.createElement('div');
|
|
341
|
+
wrapper.className = 'gsv-columns';
|
|
342
|
+
wrapper.append(...elements);
|
|
343
|
+
wrapper.style.transform = `translateY(${-area.scrollTop}px)`;
|
|
344
|
+
gutter.replaceChildren(wrapper);
|
|
259
345
|
}
|
|
260
346
|
}
|
package/src/namespace/adw.ts
CHANGED
|
@@ -110,3 +110,4 @@ export { AdwWindow as Window } from '../elements/adw-window.js';
|
|
|
110
110
|
export { AdwWindowTitle as WindowTitle } from '../elements/adw-window-title.js';
|
|
111
111
|
export { AdwWrapBox as WrapBox } from '../elements/adw-wrap-box.js';
|
|
112
112
|
export { AdwApplication as Application } from '../application.js';
|
|
113
|
+
export { AdwStyleManager as StyleManager } from '../style-manager.js';
|
package/src/namespace/gio.ts
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
// `check-vocabulary-alignment.mjs` derives their members from the elements this package
|
|
13
13
|
// defines. GIO defines none: what lands here is the handful of GObject types an Adwaita
|
|
14
14
|
// author CONSTRUCTS while building a tree — today the menu a `menuModel` property takes.
|
|
15
|
-
// `Gio.File`, `Gio.Settings
|
|
15
|
+
// `Gio.File`, `Gio.Settings` and the rest of the library are NOT this
|
|
16
16
|
// package's business; a namespace that grows past what the elements consume is a second,
|
|
17
17
|
// unheld vocabulary.
|
|
18
18
|
|
|
19
19
|
export { GioMenu as Menu, GioMenuItem as MenuItem } from '@gjsify/adwaita-core';
|
|
20
|
-
export { SimpleAction, ApplicationFlags } from '@gjsify/adwaita-core';
|
|
20
|
+
export { SimpleAction, SimpleActionGroup, ApplicationFlags } from '@gjsify/adwaita-core';
|
|
21
21
|
export { GioApplication as Application } from '../application.js';
|
package/src/namespace/glib.ts
CHANGED
|
@@ -3,10 +3,20 @@
|
|
|
3
3
|
// behaviour lives once in `@gjsify/adwaita-core` (`glib-timers.ts`, which states where a host
|
|
4
4
|
// event loop cannot honour GLib); this file is the namespace door over the host's `setTimeout`.
|
|
5
5
|
//
|
|
6
|
+
// The path helpers (`MAXUINT32`, `build_filenamev`, `get_current_dir`, `get_system_data_dirs`) live
|
|
7
|
+
// in `glib-paths.ts` of the core; the host has no filesystem, so `get_current_dir()` is `"/"` and
|
|
8
|
+
// `get_system_data_dirs()` is `[]`. A port that knows better replaces the member in its own file.
|
|
9
|
+
//
|
|
6
10
|
// A TRUE SUBSET: everything else in GLib (`MainLoop`, `Source`, `timeout_add_seconds`, `Bytes`,
|
|
7
|
-
// `
|
|
11
|
+
// `get_home_dir`, …) is absent, and the `gi://GLib` arm refuses it by name.
|
|
8
12
|
|
|
9
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
buildFilenamev,
|
|
15
|
+
createGLibTimers,
|
|
16
|
+
getCurrentDir,
|
|
17
|
+
getSystemDataDirs,
|
|
18
|
+
MAXUINT32 as GLIB_MAXUINT32,
|
|
19
|
+
} from '@gjsify/adwaita-core';
|
|
10
20
|
|
|
11
21
|
const glib = createGLibTimers();
|
|
12
22
|
|
|
@@ -20,3 +30,8 @@ export const SOURCE_CONTINUE = glib.SOURCE_CONTINUE;
|
|
|
20
30
|
export const idle_add = glib.idle_add;
|
|
21
31
|
export const timeout_add = glib.timeout_add;
|
|
22
32
|
export const source_remove = glib.source_remove;
|
|
33
|
+
|
|
34
|
+
export const MAXUINT32 = GLIB_MAXUINT32;
|
|
35
|
+
export const build_filenamev = buildFilenamev;
|
|
36
|
+
export const get_current_dir = getCurrentDir;
|
|
37
|
+
export const get_system_data_dirs = getSystemDataDirs;
|
package/src/namespace/gobject.ts
CHANGED
|
@@ -18,6 +18,7 @@ const GObjectObject = gobject.Object;
|
|
|
18
18
|
export { GObjectObject as Object };
|
|
19
19
|
export const registerClass = gobject.registerClass;
|
|
20
20
|
export const type_ensure = gobject.type_ensure;
|
|
21
|
+
export const signal_stop_emission_by_name = gobject.signal_stop_emission_by_name;
|
|
21
22
|
export const ParamSpec = gobject.ParamSpec;
|
|
22
23
|
export const ParamFlags = gobject.ParamFlags;
|
|
23
24
|
export const TYPE_STRING = gobject.TYPE_STRING;
|
|
@@ -3,7 +3,18 @@
|
|
|
3
3
|
// the NativeScript port publishes. `export * as GtkSource from './namespace/gtksource.js'` in
|
|
4
4
|
// `src/index.ts` makes it the namespace the `gi://GtkSource` arm answers from.
|
|
5
5
|
//
|
|
6
|
-
// A TRUE SUBSET: GtkSource's
|
|
7
|
-
//
|
|
8
|
-
export {
|
|
6
|
+
// A TRUE SUBSET: GtkSource's completion, search and the rest are absent, and the arm refuses each
|
|
7
|
+
// by name.
|
|
8
|
+
export {
|
|
9
|
+
Buffer,
|
|
10
|
+
Gutter,
|
|
11
|
+
GutterLines,
|
|
12
|
+
GutterRenderer,
|
|
13
|
+
GutterRendererText,
|
|
14
|
+
Language,
|
|
15
|
+
LanguageManager,
|
|
16
|
+
StyleScheme,
|
|
17
|
+
StyleSchemeManager,
|
|
18
|
+
init,
|
|
19
|
+
} from '@gjsify/gtksource-core';
|
|
9
20
|
export { GtkSourceView as View } from '../gtksource/gtk-source-view.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The `Adw.StyleManager` door of adwaita-web, held to the shared vectors (real GJS is their oracle, in
|
|
2
|
+
// adwaita-core's `style-manager.gjs.spec.ts`). The page is made dark the way an app does it: the
|
|
3
|
+
// `.theme-dark` / `.theme-light` class on the root.
|
|
4
|
+
|
|
5
|
+
import { STYLE_MANAGER_VECTORS, driveStyleManagerVectors, type AdwStyleLike } from '@gjsify/adwaita-core/conformance';
|
|
6
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
7
|
+
|
|
8
|
+
import * as Adw from './namespace/adw.js';
|
|
9
|
+
|
|
10
|
+
export const StyleManagerTest = async () => {
|
|
11
|
+
const root = document.documentElement;
|
|
12
|
+
const before = root.className;
|
|
13
|
+
try {
|
|
14
|
+
await driveStyleManagerVectors(
|
|
15
|
+
{
|
|
16
|
+
name: 'adwaita-web',
|
|
17
|
+
isOracle: false,
|
|
18
|
+
Adw: Adw as unknown as AdwStyleLike,
|
|
19
|
+
setDark(dark) {
|
|
20
|
+
root.classList.remove('theme-dark', 'theme-light');
|
|
21
|
+
root.classList.add(dark ? 'theme-dark' : 'theme-light');
|
|
22
|
+
},
|
|
23
|
+
// A MutationObserver reports in a microtask; a task later it has been heard.
|
|
24
|
+
settle: () => new Promise((resolve) => setTimeout(resolve, 0)),
|
|
25
|
+
},
|
|
26
|
+
{ describe, it, expect },
|
|
27
|
+
);
|
|
28
|
+
await describe('adwaita-web: Adw.StyleManager door', async () => {
|
|
29
|
+
await it('has a vector row for every vector', () => {
|
|
30
|
+
expect(STYLE_MANAGER_VECTORS.length > 0).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
} finally {
|
|
34
|
+
root.className = before;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// `Adw.StyleManager` on the browser: `dark` is what the page shows — a `.theme-dark` / `.theme-light`
|
|
2
|
+
// on the root outranks `prefers-color-scheme` (ADR 0078, `isAdwaitaDark`). The behaviour is the
|
|
3
|
+
// core's (`style-manager.ts`); this file is the SOURCE: the root's `class` attribute and the media
|
|
4
|
+
// query are the two things that can flip it. Without a `document` (SSR) it is light and never changes.
|
|
5
|
+
|
|
6
|
+
import { StyleManagerBase, type StyleSource } from '@gjsify/adwaita-core';
|
|
7
|
+
|
|
8
|
+
import { isAdwaitaDark } from './accent.js';
|
|
9
|
+
|
|
10
|
+
export const webStyleSource: StyleSource = {
|
|
11
|
+
dark: () => typeof document !== 'undefined' && isAdwaitaDark(document.documentElement),
|
|
12
|
+
subscribe(changed) {
|
|
13
|
+
if (typeof document === 'undefined') return () => {};
|
|
14
|
+
const media = globalThis.matchMedia?.('(prefers-color-scheme: dark)');
|
|
15
|
+
media?.addEventListener('change', changed);
|
|
16
|
+
const observer = new MutationObserver(changed);
|
|
17
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
18
|
+
return () => {
|
|
19
|
+
media?.removeEventListener('change', changed);
|
|
20
|
+
observer.disconnect();
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export class AdwStyleManager extends StyleManagerBase {
|
|
26
|
+
static override source = webStyleSource;
|
|
27
|
+
}
|
package/src/test.browser.mts
CHANGED
|
@@ -142,6 +142,8 @@ import { AdwConstructVectorsTest } from './construct-vectors.spec.js';
|
|
|
142
142
|
import { GObjectDoorTest } from './gobject-door.spec.js';
|
|
143
143
|
import { ApplicationTest } from './application.spec.js';
|
|
144
144
|
import { GLibTest } from './glib.spec.js';
|
|
145
|
+
import { StyleManagerTest } from './style-manager.spec.js';
|
|
146
|
+
import { GioActionsTest } from './gio.spec.js';
|
|
145
147
|
import { MenuActionsTest } from './menu-actions.spec.js';
|
|
146
148
|
import { AdwSharedTreesTest } from './shared-trees.spec.js';
|
|
147
149
|
import { AdwBlueprintTreeTest } from './blueprint-tree.spec.js';
|
|
@@ -159,6 +161,8 @@ run({
|
|
|
159
161
|
GObjectDoorTest,
|
|
160
162
|
ApplicationTest,
|
|
161
163
|
GLibTest,
|
|
164
|
+
StyleManagerTest,
|
|
165
|
+
GioActionsTest,
|
|
162
166
|
MenuActionsTest,
|
|
163
167
|
AdwBlueprintTreeTest,
|
|
164
168
|
AdwValueListsTest,
|