From 7a14094572e8667d624faa26c083c3754101e371 Mon Sep 17 00:00:00 2001 From: Pascal Garber Date: Thu, 7 Nov 2024 21:27:54 +0100 Subject: [PATCH] Inject more IterableIterator and AsyncIterableIterator types, fixes #193 --- .prettierignore | 1 + examples/gio-2-iterate/main.ts | 75 ++++++++++++++++ examples/gio-2-iterate/package.json | 24 +++++ examples/gio-2-iterate/tsconfig.json | 18 ++++ examples/gio-2-list-model/main.ts | 122 ++++++++++++-------------- packages/lib/src/injections/gio.ts | 78 ++++++++++++++++ packages/lib/src/injections/gtk4.ts | 35 ++++++++ packages/lib/src/injections/inject.ts | 2 + yarn.lock | 11 +++ 9 files changed, 302 insertions(+), 64 deletions(-) create mode 100644 .prettierignore create mode 100644 examples/gio-2-iterate/main.ts create mode 100644 examples/gio-2-iterate/package.json create mode 100644 examples/gio-2-iterate/tsconfig.json create mode 100644 packages/lib/src/injections/gtk4.ts diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..18e8f0bf3 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +@types/ \ No newline at end of file diff --git a/examples/gio-2-iterate/main.ts b/examples/gio-2-iterate/main.ts new file mode 100644 index 000000000..193e56a24 --- /dev/null +++ b/examples/gio-2-iterate/main.ts @@ -0,0 +1,75 @@ +import GLib from 'gi://GLib' +import Gio from 'gi://Gio' + +Gio._promisify(Gio.File.prototype, 'enumerate_children_async', 'enumerate_children_finish') + +const textDecoder = new TextDecoder('utf-8') + +function readFileInChunksSync() { + // Open a file + const file = Gio.File.new_for_path('/etc/os-release') + const inputStream = file.read(null) + + // Read file in chunks of 4 bytes using SyncIterator + console.log('\nReading file in chunks using SyncIterator:') + for (const bytes of inputStream.createSyncIterator(4)) { + const text = textDecoder.decode(bytes.toArray()) + console.log('Chunk:', text) + } +} + +async function readFileInChunksAsync() { + // Open a file + const file = Gio.File.new_for_path('/etc/os-release') + const inputStream = file.read(null) + + // Read file in chunks of 4 bytes using AsyncIterator + console.log('Reading file in chunks using AsyncIterator:') + for await (const bytes of inputStream.createAsyncIterator(4)) { + const text = textDecoder.decode(bytes.toArray()) + console.log('Chunk:', text) + } +} + +function listDirectoryContentsSync() { + // List root directory contents synchronously + console.log('\nListing . directory contents (sync):') + const dir = Gio.File.new_for_path('.') + const enumerator = dir.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null) + + for (const fileInfo of enumerator) { + console.log(fileInfo.get_name()) + } +} + +async function listDirectoryContentsAsync() { + // List root directory contents asynchronously + console.log('\nListing . directory contents (async):') + const dir = Gio.File.new_for_path('.') + const enumerator = await dir.enumerate_children_async( + 'standard::name', + Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, + GLib.PRIORITY_DEFAULT, + null, + ) + + for await (const file_info of enumerator) { + console.log(file_info.get_name()) + } +} + +// Run all examples +const loop = new GLib.MainLoop(null, false) + +const start = async () => { + readFileInChunksSync() + listDirectoryContentsSync() + await readFileInChunksAsync() + await listDirectoryContentsAsync() +} + +start().then(() => { + loop.quit() +}) + +loop.run() diff --git a/examples/gio-2-iterate/package.json b/examples/gio-2-iterate/package.json new file mode 100644 index 000000000..c50c717f9 --- /dev/null +++ b/examples/gio-2-iterate/package.json @@ -0,0 +1,24 @@ +{ + "name": "@ts-for-gir-example/gio-2-iterate", + "version": "4.0.0-beta.18", + "description": "Example demonstrating GIO async and sync iterators", + "type": "module", + "private": true, + "scripts": { + "build:app": "tsc", + "build": "yarn build:app", + "start:app": "gjs -m dist/main.js", + "start": "yarn build && yarn start:app", + "validate": "yarn validate:types", + "validate:types": "tsc --noEmit", + "clear": "rm -rf dist" + }, + "devDependencies": { + "typescript": "^5.6.3" + }, + "dependencies": { + "@girs/gio-2.0": "workspace:^", + "@girs/gjs": "workspace:^", + "@girs/glib-2.0": "workspace:^" + } +} diff --git a/examples/gio-2-iterate/tsconfig.json b/examples/gio-2-iterate/tsconfig.json new file mode 100644 index 000000000..93b116986 --- /dev/null +++ b/examples/gio-2-iterate/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "types": ["@girs/gjs", "@girs/gjs/dom", "@girs/glib-2.0", "@girs/gio-2.0"], + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "outDir": "./dist" + }, + "files": [ + "main.ts" + ] +} diff --git a/examples/gio-2-list-model/main.ts b/examples/gio-2-list-model/main.ts index c29ee2a79..af15bbe15 100644 --- a/examples/gio-2-list-model/main.ts +++ b/examples/gio-2-list-model/main.ts @@ -3,57 +3,56 @@ * SPDX-FileCopyrightText: 2020 Andy Holmes * @source https://gitlab.gnome.org/GNOME/gjs/-/blob/master/examples/glistmodel.js */ -import GObject from 'gi://GObject?version=2.0'; -import Gio from 'gi://Gio?version=2.0'; +import GObject from 'gi://GObject?version=2.0' +import Gio from 'gi://Gio?version=2.0' /** * An example of implementing the GListModel interface in GJS. The only real * requirement here is that the class be derived from some GObject. */ export class IGjsListStore extends GObject.Object implements Gio.ListModel { - - _items: GObject.Object[] = []; + _items: GObject.Object[] = [] _init() { - super._init(); + super._init() /* We'll use a native Array as internal storage for the list model */ - this._items = []; + this._items = [] } // TODO: It should not be necessary to implement this method get_item_type() { - const res = GObject.Object.$gtype; - print("get_item_type", res); - return res; + const res = GObject.Object.$gtype + print('get_item_type', res) + return res } // TODO: It should not be necessary to implement this method get_item(position: number) { - const res = this._items[position] || null; - print("get_item", res); - return res; + const res = this._items[position] || null + print('get_item', res) + return res } // TODO: It should not be necessary to implement this method items_changed(position: number, removed: number, added: number) { - print(`items_changed position: ${position}, removed: ${removed}, added: ${added}`); + print(`items_changed position: ${position}, removed: ${removed}, added: ${added}`) } // TODO: It should not be necessary to implement this method get_n_items() { - const res = this._items.length; - print("get_n_items", res); - return res; + const res = this._items.length + print('get_n_items', res) + return res } /* Implementing this function amounts to returning a GType. This could be a * more specific GType, but must be a subclass of GObject. */ vfunc_get_item_type() { - const res = GObject.Object.$gtype; - print("vfunc_get_item_type", res); - return res; + const res = GObject.Object.$gtype + print('vfunc_get_item_type', res) + return res } /* Implementing this function just requires returning the GObject at @@ -61,18 +60,18 @@ export class IGjsListStore extends GObject.Object implements Gio.ListModel { * not `undefined`. */ vfunc_get_item(position: number) { - const res = this._items[position] || null; - print("vfunc_get_item", res); - return res; + const res = this._items[position] || null + print('vfunc_get_item', res) + return res } /* Implementing this function is as simple as return the length of the * storage object, in this case an Array. */ vfunc_get_n_items() { - const res = this._items.length; - print("vfunc_get_n_items", res); - return res; + const res = this._items.length + print('vfunc_get_n_items', res) + return res } /** @@ -84,14 +83,12 @@ export class IGjsListStore extends GObject.Object implements Gio.ListModel { * @param position - the position to add the item */ insertItem(item: GObject.Object, position: number) { - if (!(item instanceof GObject.Object)) - throw new TypeError('not a GObject'); + if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject') - if (position < 0 || position > this._items.length) - position = this._items.length; + if (position < 0 || position > this._items.length) position = this._items.length - this._items.splice(position, 0, item); - this.items_changed(position, 0, 1); + this._items.splice(position, 0, item) + this.items_changed(position, 0, 1) } /** @@ -100,13 +97,12 @@ export class IGjsListStore extends GObject.Object implements Gio.ListModel { * @param item - the item to add */ appendItem(item: GObject.Object) { - if (!(item instanceof GObject.Object)) - throw new TypeError('not a GObject'); + if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject') - let position = this._items.length; + let position = this._items.length - this._items.push(item); - this.items_changed(position, 0, 1); + this._items.push(item) + this.items_changed(position, 0, 1) } /** @@ -115,11 +111,10 @@ export class IGjsListStore extends GObject.Object implements Gio.ListModel { * @param item - the item to add */ prependItem(item: GObject.Object) { - if (!(item instanceof GObject.Object)) - throw new TypeError('not a GObject'); + if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject') - this._items.unshift(item); - this.items_changed(0, 0, 1); + this._items.unshift(item) + this.items_changed(0, 0, 1) } /** @@ -129,16 +124,14 @@ export class IGjsListStore extends GObject.Object implements Gio.ListModel { * @param item - the item to remove */ removeItem(item: GObject.Object) { - if (!(item instanceof GObject.Object)) - throw new TypeError('not a GObject'); + if (!(item instanceof GObject.Object)) throw new TypeError('not a GObject') - let position = this._items.indexOf(item); + let position = this._items.indexOf(item) - if (position === -1) - return; + if (position === -1) return - this._items.splice(position, 1); - this.items_changed(position, 1, 0); + this._items.splice(position, 1) + this.items_changed(position, 1, 0) } /** @@ -148,24 +141,22 @@ export class IGjsListStore extends GObject.Object implements Gio.ListModel { * @param position - the position of the item to remove */ removePosition(position: number) { - if (position < 0 || position >= this._items.length) - return; + if (position < 0 || position >= this._items.length) return - this._items.splice(position, 1); - this.items_changed(position, 1, 0); + this._items.splice(position, 1) + this.items_changed(position, 1, 0) } /** * Clear the list of all items. */ clear() { - let length = this._items.length; + let length = this._items.length - if (length === 0) - return; + if (length === 0) return - this._items = []; - this.items_changed(0, length, 0); + this._items = [] + this.items_changed(0, length, 0) } } @@ -173,11 +164,14 @@ export class IGjsListStore extends GObject.Object implements Gio.ListModel { * An example of implementing the GListModel interface in GJS. The only real * requirement here is that the class be derived from some GObject. */ -export const GjsListStore = GObject.registerClass({ - GTypeName: 'GjsListStore', - Implements: [Gio.ListModel], -}, IGjsListStore); - -// TODO: Expand the example which also demonstrates the use of the listStore -const listStore = new GjsListStore(); -listStore.insertItem(new GObject.Object(), 0); \ No newline at end of file +export const GjsListStore = GObject.registerClass( + { + GTypeName: 'GjsListStore', + Implements: [Gio.ListModel], + }, + IGjsListStore, +) + +// TODO: Expand the example which also demonstrates the use of the listStore +const listStore = new GjsListStore() +listStore.insertItem(new GObject.Object(), 0) diff --git a/packages/lib/src/injections/gio.ts b/packages/lib/src/injections/gio.ts index e14d15180..5ed9955f4 100644 --- a/packages/lib/src/injections/gio.ts +++ b/packages/lib/src/injections/gio.ts @@ -33,6 +33,7 @@ export default { modifier(namespace: IntrospectedNamespace) { // For IterableIterator... namespace.___dts___addReference('/// '); + namespace.___dts___addReference('/// '); { const DBusNodeInfo = namespace.assertClass("DBusNodeInfo"); @@ -88,6 +89,31 @@ export default { ); } + { + const FileEnumerator = namespace.assertClass("FileEnumerator"); + + FileEnumerator.fields.push( + new JSField({ + name: "Symbol.iterator", + parent: FileEnumerator, + computed: true, + type: new FunctionType( + {}, + new GenerifiedType(new NativeType("IterableIterator"), new GenericType("FileInfo")) + ) + }), + new JSField({ + name: "Symbol.asyncIterator", + parent: FileEnumerator, + computed: true, + type: new FunctionType( + {}, + new GenerifiedType(new NativeType("AsyncIterableIterator"), new GenericType("FileInfo")) + ) + }) + ); + } + { const SettingsSchema = namespace.assertClass("SettingsSchema"); @@ -540,5 +566,57 @@ export default { }) ); } + + { + const InputStream = namespace.assertClass("InputStream"); + const Bytes = namespace.assertInstalledImport("GLib").assertClass("Bytes"); + + InputStream.members.push( + new IntrospectedClassFunction({ + name: "createAsyncIterator", + parent: InputStream, + parameters: [ + new IntrospectedFunctionParameter({ + name: "count", + type: new NativeType("number"), + isOptional: true, + direction: GirDirection.In + }), + new IntrospectedFunctionParameter({ + name: "priority", + type: new NativeType("number"), + isOptional: true, + direction: GirDirection.In + }) + ], + return_type: new GenerifiedType( + new NativeType("AsyncIterableIterator"), + new GenericType(`${Bytes.namespace.namespace}.${Bytes.name}`) + ) + }), + new IntrospectedClassFunction({ + name: "createSyncIterator", + parent: InputStream, + parameters: [ + new IntrospectedFunctionParameter({ + name: "count", + type: new NativeType("number"), + isOptional: true, + direction: GirDirection.In + }), + new IntrospectedFunctionParameter({ + name: "priority", + type: new NativeType("number"), + isOptional: true, + direction: GirDirection.In + }) + ], + return_type: new GenerifiedType( + new NativeType("IterableIterator"), + new GenericType(`${Bytes.namespace.namespace}.${Bytes.name}`) + ) + }) + ); + } } }; diff --git a/packages/lib/src/injections/gtk4.ts b/packages/lib/src/injections/gtk4.ts new file mode 100644 index 000000000..fae5dfc5c --- /dev/null +++ b/packages/lib/src/injections/gtk4.ts @@ -0,0 +1,35 @@ +import { IntrospectedNamespace } from "../gir/namespace.js"; +import { NativeType, FunctionType } from "../gir.js"; +import { IntrospectedField, JSField } from "../gir/property.js"; + +export default { + namespace: "Gtk", + version: "4.0", + modifier(namespace: IntrospectedNamespace) { + { + const Widget = namespace.assertClass("Widget"); + + Widget.fields.push( + new IntrospectedField({ + name: "Symbol.iterator", + parent: Widget, + computed: true, + type: new FunctionType({}, new NativeType("IterableIterator")) + }) + ); + } + + { + const ListBox = namespace.assertClass("ListBox"); + + ListBox.fields.push( + new JSField({ + name: "Symbol.iterator", + parent: ListBox, + computed: true, + type: new FunctionType({}, new NativeType("IterableIterator")) + }) + ); + } + } +}; diff --git a/packages/lib/src/injections/inject.ts b/packages/lib/src/injections/inject.ts index 4fc2f0e85..b0867daf4 100644 --- a/packages/lib/src/injections/inject.ts +++ b/packages/lib/src/injections/inject.ts @@ -4,6 +4,7 @@ import gio from "./gio.js"; import tracker1 from "./tracker1.js"; import gee08 from "./gee08.js"; import gee1 from "./gee1.js"; +import gtk4 from "./gtk4.js"; import { IntrospectedNamespace } from "../gir/namespace.js"; import { NSRegistry } from "../gir/registry.js"; @@ -35,4 +36,5 @@ export function inject(registry: NSRegistry) { $_(tracker1); $_(gee08); $_(gee1); + $_(gtk4); } diff --git a/yarn.lock b/yarn.lock index c5fd9f888..558dd67a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12702,6 +12702,17 @@ __metadata: languageName: unknown linkType: soft +"@ts-for-gir-example/gio-2-iterate@workspace:examples/gio-2-iterate": + version: 0.0.0-use.local + resolution: "@ts-for-gir-example/gio-2-iterate@workspace:examples/gio-2-iterate" + dependencies: + "@girs/gio-2.0": "workspace:^" + "@girs/gjs": "workspace:^" + "@girs/glib-2.0": "workspace:^" + typescript: "npm:^5.6.3" + languageName: unknown + linkType: soft + "@ts-for-gir-example/gio-2-list-model-example@workspace:examples/gio-2-list-model": version: 0.0.0-use.local resolution: "@ts-for-gir-example/gio-2-list-model-example@workspace:examples/gio-2-list-model"