{"id":44283641,"url":"https://github.com/nicklayb/codemirror-loupe","last_synced_at":"2026-02-10T22:09:01.660Z","repository":{"id":329370195,"uuid":"1118577979","full_name":"nicklayb/codemirror-loupe","owner":"nicklayb","description":null,"archived":false,"fork":false,"pushed_at":"2025-12-20T01:42:49.000Z","size":47,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-22T01:07:49.305Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nicklayb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-18T01:17:03.000Z","updated_at":"2025-12-20T01:42:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nicklayb/codemirror-loupe","commit_stats":null,"previous_names":["nicklayb/codemirror-loupe"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/nicklayb/codemirror-loupe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicklayb%2Fcodemirror-loupe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicklayb%2Fcodemirror-loupe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicklayb%2Fcodemirror-loupe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicklayb%2Fcodemirror-loupe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nicklayb","download_url":"https://codeload.github.com/nicklayb/codemirror-loupe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicklayb%2Fcodemirror-loupe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29319439,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-10T20:44:44.282Z","status":"ssl_error","status_checked_at":"2026-02-10T20:44:43.393Z","response_time":65,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-10T22:09:00.815Z","updated_at":"2026-02-10T22:09:01.650Z","avatar_url":"https://github.com/nicklayb.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CodeMirror Language Support for Loupe\n\nCodeMirror 6 language extension providing syntax highlighting and language support for the [Loupe query language](https://github.com/nicklayb/loupe).\n\n## What is Loupe?\n\nLoupe is a query language for safe and configurable inspection of Ecto schemas in Elixir applications. It provides a declarative syntax for querying data with support for:\n\n- Quantifiers (ranges, multipliers)\n- Complex predicates with boolean logic\n- Field variants and path binding\n- JSON-like parameters\n- Safe, configurable schema inspection\n\n## Features\n\n- Full syntax highlighting for Loupe queries\n- Automatic code folding for nested structures\n- Comment support (`#` line comments)\n- Smart indentation\n- Built on CodeMirror 6 and Lezer parser\n\n## Installation\n\n```bash\nnpm install @nicklayb/codemirror-loupe\n```\n\n## Usage\n\n### Basic Setup\n\n```typescript\nimport { EditorView, basicSetup } from 'codemirror';\nimport { loupe } from '@nicklayb/codemirror-lang-loupe';\n\nnew EditorView({\n  doc: 'get User where email = \"user@example.com\"',\n  extensions: [basicSetup, loupe()],\n  parent: document.querySelector('#editor')\n});\n```\n\n### With Custom Theme\n\n```typescript\nimport { EditorView, basicSetup } from 'codemirror';\nimport { loupe } from '@nicklayb/codemirror-lang-loupe';\n\nnew EditorView({\n  doc: 'get all Post where status = \"published\"',\n  extensions: [\n    basicSetup,\n    loupe(),\n    EditorView.theme({\n      '\u0026': { fontSize: '16px' },\n      '.cm-content': { fontFamily: 'Monaco, monospace' }\n    })\n  ],\n  parent: document.querySelector('#editor')\n});\n```\n\n### With Autocompletion (Callback-Based)\n\nEnable smart, dynamic autocompletion with callback functions. This allows you to provide schemas and fields on-demand, supporting nested field navigation:\n\n```typescript\nimport { EditorView, basicSetup } from 'codemirror';\nimport { autocompletion } from '@codemirror/autocomplete';\nimport { loupe, loupeCompletion } from '@nicklayb/codemirror-lang-loupe';\n\nnew EditorView({\n  doc: 'get User where ',\n  extensions: [\n    basicSetup,\n    loupe(),\n    autocompletion({\n      override: [loupeCompletion({\n        // Provide available commands\n        getCommands: () =\u003e [\n          { label: 'get', type: 'keyword', info: 'Get records' },\n          { label: 'find', type: 'keyword', info: 'Find records' },\n          { label: 'fetch', type: 'keyword', info: 'Fetch records' }\n        ],\n\n        // Provide available schemas\n        getSchemas: () =\u003e [\n          { label: 'User', type: 'type', info: 'User account' },\n          { label: 'Post', type: 'type', info: 'Blog posts' }\n        ],\n\n        // Provide fields dynamically based on context\n        getFields: (context) =\u003e {\n          const { schema, fieldPath } = context;\n\n          // Root-level fields\n          if (fieldPath.length === 0) {\n            if (schema === 'User') {\n              return [\n                { label: 'id', type: 'property', detail: 'integer' },\n                { label: 'email', type: 'property', detail: 'string' },\n                { label: 'role', type: 'property', detail: 'Role' }\n              ];\n            }\n          }\n\n          // Nested fields (e.g., after typing \"role.\")\n          if (fieldPath[fieldPath.length - 1] === 'role') {\n            return [\n              { label: 'name', type: 'property', detail: 'string' },\n              { label: 'level', type: 'property', detail: 'integer' }\n            ];\n          }\n\n          return [];\n        }\n      })]\n    })\n  ],\n  parent: document.querySelector('#editor')\n});\n```\n\n**Autocompletion features:**\n- **Fully callback-based**: Provide commands, schemas, and fields dynamically at runtime\n- **Nested field navigation**: Type dots to navigate nested fields (e.g., `user.role.name`)\n- **Context-aware**: The `getFields` callback receives the current schema and field path\n- **Async support**: All callbacks can return Promises for fetching data from APIs\n- **Command suggestions**: Provided via `getCommands` callback at the beginning\n- **Schema suggestions**: Provided via `getSchemas` callback after the command\n- **Field suggestions**: Provided via `getFields` callback after `where` and logical operators\n- **Operator suggestions**: After field names (=, !=, \u003c, \u003e, \u003c=, \u003e=, in, like)\n- **Keyword suggestions**: where, and, or, not, in, like, empty\n\n**Context object passed to `getFields`:**\n```typescript\ninterface LoupeCompletionContext {\n  schema: string;        // Current schema (e.g., \"User\")\n  fieldPath: string[];   // Current path (e.g., [\"role\"] for \"role.\")\n  type: 'field' | ...;   // Type of completion\n}\n```\n\n## Loupe Query Syntax Examples\n\n### Basic Query\n```loupe\nget User where email = \"user@example.com\"\n```\n\n### Query with Quantifier\n```loupe\nget 10 Post where status = \"published\" and views \u003e 1k\n```\n\n### Query with Range\n```loupe\nget 5..10 Comment where created_at \u003e \"2024-01-01\"\n```\n\n### Query with Parameters\n```loupe\nget Article {title: \"Hello\", draft: false} where author_id = user_id\n```\n\n### Complex Query with Nested Conditions\n```loupe\nget all Transaction where\n  (amount \u003e= 100 and amount \u003c= 1000) or\n  status = \"pending\"\n```\n\n### Query with IN Operator\n```loupe\nget User where role in [\"admin\", \"moderator\"]\n```\n\n### Query with LIKE Operator\n```loupe\nget Product where name like \"iPhone\"\n```\n\n### Query with Field Variant\n```loupe\nget Account where balance:amount \u003e= 10k\n```\n\n### Query with Path Binding\n```loupe\nget User where role.permissions[posts, access] = \"write\"\n```\n\n### Grouped Field OR (`|`)\n```loupe\nget Post where title | description like \"search term\"\n```\nThe `|` operator checks if **any** of the grouped fields match the condition.\n\n### Grouped Field AND (`\u0026`)\n```loupe\nget Product where price \u0026 discount \u003e 100\n```\nThe `\u0026` operator checks if **all** of the grouped fields match the condition.\n\n### Query with NOT and EMPTY\n```loupe\nget Document where not status:empty and published = true\n```\n\n## Development\n\n### Prerequisites\n\n- Node.js 20 or later\n- npm or pnpm\n\n### Setup with Nix (Recommended)\n\nIf you have Nix with flakes enabled:\n\n```bash\nnix develop\nnpm install\nnpm run build\n```\n\n### Manual Setup\n\n```bash\nnpm install\nnpm run build\n```\n\n### Available Scripts\n\n- `npm run build:parser` - Generate the Lezer parser from grammar\n- `npm run build` - Build the library (runs parser generation + rollup)\n- `npm run dev` - Run the example app in development mode\n\n### Project Structure\n\n```\nloupejs/\n├── src/\n│   ├── grammar.lezer    # Lezer grammar definition\n│   ├── highlight.ts     # Syntax highlighting rules\n│   └── index.ts         # Main entry point\n├── example/\n│   ├── index.html       # Example app HTML\n│   └── main.js          # Example app code\n├── dist/                # Build output\n├── flake.nix            # Nix development environment\n├── package.json\n├── tsconfig.json\n└── rollup.config.js\n```\n\n## Running the Example\n\nTo see the language support in action:\n\n```bash\nnpm run dev\n```\n\nThen open your browser to the URL shown (typically http://localhost:5173 or http://localhost:5174).\n\n**Important**: You must access the example through the Vite dev server URL. Opening the HTML file directly (file://) won't work because ES modules with bare imports require a development server to resolve properly.\n\n## Building from Source\n\n1. Clone the repository\n2. Install dependencies: `npm install`\n3. Build the parser: `npm run build:parser`\n4. Build the library: `npm run build`\n\nThe compiled library will be in the `dist/` directory.\n\n## Language Features\n\n### Supported Tokens\n\n- **Commands**: Any identifier (e.g., `get`, `find`, `fetch`)\n- **Keywords**: `where`, `all`, `in`, `like`, `empty`, `and`, `or`, `not`\n- **Comparison Operators**: `=`, `!=`, `\u003c`, `\u003e`, `\u003c=`, `\u003e=`\n- **Grouped Field Operators**: `|` (OR for fields), `\u0026` (AND for fields)\n- **Boolean Operators**: `and`, `or`, `not`\n- **Quantifiers**: Numbers, ranges (`1..10`), multipliers (`k`, `m`)\n- **Values**: Strings, numbers, booleans, identifiers\n- **Structures**: Parameters `{key: value}`, lists `[value1, value2]`\n- **Advanced**: Field variants (`:variant`), path binding (`[field1, field2]`)\n- **Comments**: Line comments starting with `#`\n\n**Note**: The `|` and `\u0026` operators are for grouping fields (e.g., `field1 | field2 = \"value\"`), while `and`/`or` are for combining predicates (e.g., `field1 = \"a\" and field2 = \"b\"`).\n\n### Syntax Highlighting\n\nThe extension provides semantic highlighting for:\n\n- Keywords (purple/blue)\n- Operators (red/orange)\n- Strings (green)\n- Numbers (blue)\n- Comments (gray)\n- Identifiers and schema names\n- Field variants and path bindings\n\n## License\n\nMIT\n\n## Related Projects\n\n- [Loupe](https://github.com/nicklayb/loupe) - The Loupe query language for Elixir\n- [CodeMirror 6](https://codemirror.net/) - Extensible code editor\n- [Lezer](https://lezer.codemirror.net/) - Incremental parser system\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Author\n\nNicolas Boisvert\n\n## Acknowledgments\n\nBuilt with CodeMirror 6 and Lezer parser generator.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicklayb%2Fcodemirror-loupe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnicklayb%2Fcodemirror-loupe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicklayb%2Fcodemirror-loupe/lists"}