npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@swiftbrowser/code

v0.29.2

Published

A code editor for SwiftBrowser apps: line numbers, long files, syntax highlighting, on a text system shaped after TextKit 2 that drives the page's own view and, natively, NSTextLayoutManager.

Readme

@swiftbrowser/code

A code editor for SwiftBrowser apps: line numbers, long files and syntax highlighting, on a text system shaped after TextKit 2 (docs/notes/textkit.md in the repository) so that one JavaScript surface drives the page's own view and, natively, NSTextLayoutManager.

It is optional, as @swiftbrowser/shader and @swiftbrowser/dsp are: an app that does not import it pays nothing for it.

import { CodeEditor } from '@swiftbrowser/code';

<CodeEditor text={source} onChange={setSource} language="swift" theme="github-dark" frame={{ maxHeight: Infinity }} />

The app gives the editor its text and hears each edit (onChange(text, changes), the changes as offsets into the text before); the view keeps the text between, so a long file never crosses the bridge twice. lineNumbers (on), wrap (off: long lines scroll), readOnly, fontSize, indentUnit (two spaces), language, theme and colors are the options.

marks tints ranges of the text behind it (a search's matches, the statement an error is in): { from, to, color }, offsets into the text as onChange's are and a CSS color, a later mark over an earlier. They take no focus and leave the selection alone. focused is a binding, as TextEditor's is: [isFocused, setFocused] gives the editor the focus or takes it away (a tap elsewhere putting the keyboard down) when the value changes, and hears the viewer's own taps through the setter.

<CodeEditor
  text={source}
  onChange={setSource}
  marks={[{ from: error.start, to: error.end, color: 'rgba(255, 69, 58, 0.25)' }]}
  focused={[editing, setEditing]}
/>

completions offers suggestions in a list under the caret: an array of items, when they never depend on where the caret is, or a function of the word being typed, (context) => items, sync or async, which the view asks once per word (as the word begins, at one of completionTriggers, . say, which opens the list with an empty word, or when the user presses Ctrl-Space) with the whole text, the caret, the word so far (prefix, from from), the line it is on and an AbortSignal that a newer request aborts. Filtering and ranking as the user goes on typing are the view's, so the function answers for the word, not for each keystroke; it may answer { items, incomplete: true } to be asked again at each one. An item is a label, shown and matched, with an insert that goes into the text in its place (the label when absent), a caret within it (adsr(|) is the name's length plus one), a detail for the second column (a signature, a type), a kind (keyword, function, variable, type…) for its icon, a filter matched instead of the label, a from to replace from instead of the word's start, and documentation, the item in full (a function's doc, its parameters' types and defaults; plain text, line breaks kept), shown in a pane beside the list while its row is selected. Arrow Up and Down pick an item, Return or Tab takes it, Escape puts the list away; the item goes in as an ordinary edit, which the app hears through onChange and the user undoes as any other, and onCompletionAccept(item) hears the choice after it.

<CodeEditor
  text={source}
  onChange={setSource}
  completionTriggers={['.']}
  completions={({ prefix, trigger }) => (trigger === '.' ? members : names).filter((name) => name.startsWith(prefix[0] ?? '')).map((name) => ({ label: name, kind: 'function', detail: signatures[name], insert: `${name}()`, caret: name.length + 1 }))}
  onCompletionAccept={(item) => recent.add(item.label)}
/>

service hands the editor to a language service (docs/notes/language-service.md in the repository): a language's analysis in a worker, answering completions that know the scope, with documentation, and pushing diagnostics. A service is a class in its language's package (the .dsp language's is DSPLanguageService in @swiftbrowser/dsp); the app puts it in a worker entry that calls serveLanguage(self, new DSPLanguageService()) and gives the editor a LanguageClient over that worker (languageWorker starts it the way the highlighter's starts, inline runs the service on the page where no worker answers), all from @swiftbrowser/code/language. The editor opens its text with the service when it mounts (and again when the app sets a new text, named by fileName if given), sends it each edit, closes it on unmount, and asks the service for the list in place of completions (the service wins when both are given; completionTriggers still names the trigger characters). The service's diagnostics, { from, to, severity, message }, are drawn as wavy underlines under their text, red for an error, yellow for a warning, blue for information and the line numbers' color for a hint (colors' diagnosticError, diagnosticWarning, diagnosticInfo and diagnosticHint change them), with the message on hover. They arrive a moment after each edit, so in between they may sit a few characters off.

Signature help: as ( or , is typed (or Ctrl-Shift-Space is pressed), the editor asks for the call around the caret, and again at each edit or caret move while the help shows; the service answers with the call's signatures, the one that applies and the parameter the caret is on (signatureHelp(doc, at), { signatures, active, activeParameter }, each parameter's label as it appears in the signature's), and the editor shows it in a small tooltip over the caret's line, the active parameter in bold with its documentation under it. A null answer (the caret is not in a call) closes it, as do Escape, the caret leaving the line and the focus going; the completions list covers it while it shows.

Hover: when the mouse rests over a word for a moment, the editor asks what is there (hover(doc, at), the offset of the character under the pointer); the service answers { from, to, contents }, plain text with paragraphs separated by blank lines, or null for nothing, and the editor shows the contents in a tooltip over the word until the mouse leaves it, the editor scrolls or the text is edited (a touch screen has no hover). Both are answered only by a service; without one nothing shows.

// dsp-language.worker.ts
import { serveLanguage, type PortLike } from '@swiftbrowser/code/language';
import { DSPLanguageService } from '@swiftbrowser/dsp';
serveLanguage(self as unknown as PortLike, new DSPLanguageService());

// the app
import { LanguageClient, languageWorker } from '@swiftbrowser/code/language';
import { DSPLanguageService } from '@swiftbrowser/dsp';

const dsp = new LanguageClient({
  port: languageWorker('dsp-language', () => new Worker(new URL('./dsp-language.worker.ts', import.meta.url), { type: 'module' })),
  inline: () => new DSPLanguageService(),
});

<CodeEditor text={program} onChange={setProgram} service={dsp} fileName="synth.dsp" completionTriggers={['.']} />

Highlighting

language names one of shiki's grammars: swift, typescript, tsx, javascript, json, css, html, markdown, python, rust, go, c, cpp, objective-c, java, kotlin, ruby, php, shellscript, yaml, toml, sql, glsl, wgsl, diff… and their usual aliases (ts, js, md, py, sh); registerLanguage(name, () => import('@shikijs/langs/haskell')) adds any other, or an app's own TextMate grammar. theme is one of shiki's: github-light and github-dark (the defaults, following the page's color scheme), one-dark-pro, vitesse-light, solarized-dark, catppuccin-mocha, nord, dracula…; registerTheme adds one. On a page each built-in grammar is a chunk of its own, loaded when a document first asks for it; in a native app that bundles the highlighter worker, the worker holds every built-in grammar and theme and the app's own script none of them, so the app launches without parsing them. The theme colors the editor's chrome too (its background, the gutter, the selection, the caret), and colors overrides any of those.

The tokenizer runs in a worker: shiki's TextMate engine over its JavaScript regex engine, each line's rule stack kept so an edit re-tokenizes only until the lines after it are as they were. Grammars and themes load on the page, lazily, and go to the worker once; the text goes once and each edit after, as paragraphs replaced; the viewport's lines come back as tokens. configureHighlighter({ engine: 'oniguruma' }) before the first editor runs Oniguruma, the engine the grammars are written for, as WebAssembly (600 KB more) instead, matching every pattern as an editor does.

A page bundles the worker from the package's entry (new Worker(new URL('./highlighter.worker.js', import.meta.url)), which Vite and the other bundlers understand). A native app bundles it by name: swiftbrowser-native build --worker highlighter=@swiftbrowser/code/highlighter.worker (or highlighter-oniguruma.worker, for Oniguruma), and the editor runs it in a WKWebView, where WebAssembly and the regexes have a JIT. Without a worker, the engine runs inline.

Through a host

An app drawn by a host that registers the editor's view and runs the highlighter itself (an app in NoyaOS's sandbox, drawn by the host's RemoteTree) imports @swiftbrowser/code/react: the CodeEditor component and its types alone, with no grammar, theme, worker or text system behind it, so a small bundle. The host imports @swiftbrowser/code once, which registers the page's view. (Natively, that entry also installs the highlighting the native view needs; through /react alone the native view draws plain text.)

On a page

The view lays out only the lines on screen (and a margin beyond), the rest at estimated heights, so a hundred thousand lines open at once. The lines sit in a contenteditable, so the browser supplies the caret, selection, keyboard and IME, on a phone included; the editor's own commands (words and lines by Option and Command on a Mac, Control elsewhere; Tab and Shift-Tab to indent; Return keeps the indentation; undo and redo) are the controller's, EditorController, written once for both platforms. The completions list is an element over the lines, beside the caret, and the keys reach it before the controller's while it is open; the selected item's documentation is a pane beside it, to its right where there is the room and under it where there is not. Diagnostics are the lines' own spans, cut where one begins and ends, underlined, with the message as the span's title.

Natively

SwiftBrowserCode (Native/Packages/SwiftBrowserCode in the repository) registers the same view: a text view on TextKit 2 (UITextView on iOS, NSTextView on the Mac) with the gutter, editing its text with the platform's keyboard, selection, undo and input methods and reporting each edit as a delta. The highlighter runs in the app's JavaScript, in the worker the app bundles beside its app.js (swiftbrowser-native build --worker highlighter=@swiftbrowser/code/highlighter.worker), which the host runs in a WKWebView; the tokens go to the view as colors. swiftbrowser native init --with code (or native add code) links the package and installs it (SwiftBrowserCode.install()). The completions list is the view's own, drawn over the text view at the caret: the hardware keyboard's arrows, Return, Tab and Escape drive it, and on a phone without one the user taps an item.