diff --git a/CHANGELOG.md b/CHANGELOG.md index 025157f..9afa44d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to Simple DB are documented in this file. +## 0.1.7 - 2026-08-08 + +### Fixed + +- The **Simple DB** right-click submenu now appears for every `.sql` file even when VS Code has not assigned that editor the `sql` language ID. +- Connection selection, query execution, status-bar attachment, keyboard shortcuts, and database navigation now recognize `.sql` files by their file extension as well as by the VS Code SQL language ID. +- Definition/declaration providers now attach to `.sql` resources directly, so `F12`, package specification, and package body navigation do not depend on a separate SQL language extension. + +### Changed + +- Simple DB now contributes the `.sql` to `sql` language association itself instead of relying on another installed extension to provide it. + ## 0.1.6 - 2026-08-08 ### Added diff --git a/README.md b/README.md index 3a1bc11..f24f1a2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Simple DB -Simple DB `0.1.6` is a Visual Studio Code extension written entirely in JavaScript for working with **SQLite, PostgreSQL, MySQL, SQL Server, and Oracle** through a single interface. +Simple DB `0.1.7` is a Visual Studio Code extension written entirely in JavaScript for working with **SQLite, PostgreSQL, MySQL, SQL Server, and Oracle** through a single interface. The extension opens regular VS Code SQL documents and loads `src/extension.js` directly: there is no TypeScript, `tsconfig.json`, `dist` folder, or compilation step. @@ -42,7 +42,7 @@ The extension opens regular VS Code SQL documents and loads `src/extension.js` d | SQL Server | `mssql` | databases, schemas, tables, views, procedures/functions, indexes, triggers, sequences, types, synonyms, `GO` | | Oracle | `oracledb` Thin | schemas, tables, views/materialized views, procedures/functions, packages, indexes, triggers, sequences, types, synonyms, PL/SQL | -Oracle uses the default `node-oracledb` Thin mode, so normal connections do not require Oracle Client to be installed. Simple DB `0.1.6` uses SQL authentication with a username and password for SQL Server. +Oracle uses the default `node-oracledb` Thin mode, so normal connections do not require Oracle Client to be installed. Simple DB `0.1.7` uses SQL authentication with a username and password for SQL Server. ## No imposed row limit by default @@ -143,7 +143,7 @@ The `id` is generated and managed by Simple DB. Do not change it. Use **Simple D ### Editor context menu -Right-clicking inside any regular SQL editor shows a native **Simple DB** submenu with the complete working set: +Right-clicking inside any regular `.sql` editor shows a native **Simple DB** submenu with the complete working set. Simple DB recognizes the `.sql` extension directly, so this menu does not depend on another extension assigning the editor's SQL language mode: - Open Configuration - Refresh Connections diff --git a/package-lock.json b/package-lock.json index 6232aa5..038873d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "simple-db-suzdalenko", - "version": "0.1.6", + "version": "0.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simple-db-suzdalenko", - "version": "0.1.6", + "version": "0.1.7", "license": "MIT", "dependencies": { "exceljs": "4.4.0", diff --git a/package.json b/package.json index 52944b3..229b829 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "simple-db-suzdalenko", "displayName": "Simple DB - SQL Client for SQLite, MySQL, PostgreSQL, SQL Server & Oracle", "description": "SQL client and database editor for SQLite, MySQL, PostgreSQL, SQL Server (MSSQL) and Oracle. Browse database objects, run queries, manage DDL and export results.", - "version": "0.1.6", + "version": "0.1.7", "publisher": "suzdalenko-dev", "license": "MIT", "icon": "resources/simple-db-marketplace.png", @@ -60,6 +60,18 @@ ], "main": "./src/extension.js", "contributes": { + "languages": [ + { + "id": "sql", + "aliases": [ + "SQL", + "sql" + ], + "extensions": [ + ".sql" + ] + } + ], "commands": [ { "command": "simpleDb.openConfiguration", @@ -479,146 +491,141 @@ "editor/title": [ { "command": "simpleDb.changeEditorConnection", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "(editorLangId == sql || resourceExtname == .sql) && resourceScheme != simple-db-definition", "group": "navigation@1" }, { "command": "simpleDb.executeCurrent", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "(editorLangId == sql || resourceExtname == .sql) && resourceScheme != simple-db-definition", "group": "navigation@2" }, { "command": "simpleDb.executeDocument", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "(editorLangId == sql || resourceExtname == .sql) && resourceScheme != simple-db-definition", "group": "navigation@3" }, { "command": "simpleDb.cancelQuery", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "(editorLangId == sql || resourceExtname == .sql) && resourceScheme != simple-db-definition", "group": "navigation@4" } ], "editor/context": [ { "submenu": "simpleDb.editorContextMenu", - "when": "editorLangId == sql", + "when": "editorLangId == sql || resourceExtname == .sql", "group": "simpleDb@1" } ], "simpleDb.editorContextMenu": [ { "command": "simpleDb.openConfiguration", - "when": "editorLangId == sql", "group": "1_configuration@1" }, { "command": "simpleDb.refreshConnections", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "1_configuration@2" }, { "command": "simpleDb.openConnectionsFolder", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "1_configuration@3" }, { "command": "simpleDb.changeEditorConnection", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "2_connection@1" }, { "command": "simpleDb.addConnection", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "2_connection@2" }, { "command": "simpleDb.connect", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "2_connection@3" }, { "command": "simpleDb.disconnect", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "2_connection@4" }, { "command": "simpleDb.testConnection", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "2_connection@5" }, { "command": "simpleDb.editConnection", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "2_connection@6" }, { "command": "simpleDb.setPassword", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "2_connection@7" }, { "command": "simpleDb.deleteConnection", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "2_connection@8" }, { "command": "simpleDb.newQuery", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "3_run@1" }, { "command": "simpleDb.executeCurrent", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "3_run@2" }, { "command": "simpleDb.executeSelection", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "3_run@3" }, { "command": "simpleDb.executeDocument", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "3_run@4" }, { "command": "simpleDb.cancelQuery", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "3_run@5" }, { "command": "simpleDb.beginTransaction", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "4_transaction@1" }, { "command": "simpleDb.commit", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "4_transaction@2" }, { "command": "simpleDb.rollback", - "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "when": "resourceScheme != simple-db-definition", "group": "4_transaction@3" }, { "command": "simpleDb.goToDefinition", - "when": "editorLangId == sql", "group": "5_navigation@1" }, { "command": "simpleDb.goToDeclaration", - "when": "editorLangId == sql", "group": "5_navigation@2" }, { "command": "simpleDb.goToPackageSpecification", - "when": "editorLangId == sql", "group": "5_navigation@3" }, { "command": "simpleDb.goToPackageBody", - "when": "editorLangId == sql", "group": "5_navigation@4" } ] @@ -628,18 +635,18 @@ "command": "simpleDb.executeCurrent", "key": "ctrl+enter", "mac": "cmd+enter", - "when": "editorTextFocus && editorLangId == sql && resourceScheme != simple-db-definition" + "when": "editorTextFocus && (editorLangId == sql || resourceExtname == .sql) && resourceScheme != simple-db-definition" }, { "command": "simpleDb.executeDocument", "key": "f5", "mac": "f5", - "when": "editorTextFocus && editorLangId == sql && resourceScheme != simple-db-definition" + "when": "editorTextFocus && (editorLangId == sql || resourceExtname == .sql) && resourceScheme != simple-db-definition" }, { "command": "simpleDb.goToDefinition", "key": "f12", - "when": "editorTextFocus && editorLangId == sql" + "when": "editorTextFocus && (editorLangId == sql || resourceExtname == .sql)" } ], "configuration": { diff --git a/src/extension.js b/src/extension.js index a541aa3..7d724d4 100644 --- a/src/extension.js +++ b/src/extension.js @@ -3,10 +3,12 @@ const path = require('node:path'); const vscode = require('vscode'); const { ConnectionManager } = require('./managers/connectionManager'); +const { EditorSessionManager } = require('./managers/editorSessionManager'); const { DEFINITION_SCHEME, - EditorSessionManager, -} = require('./managers/editorSessionManager'); + SQL_DOCUMENT_SELECTOR, + isSqlDocument, +} = require('./sql/sqlDocument'); const { QueryRunner } = require('./services/queryRunner'); const { SqlNavigationProvider } = require('./views/sqlNavigationProvider'); const { ExportService } = require('./services/exportService'); @@ -66,10 +68,7 @@ async function profileForCommand(connectionStore, editorSessionManager, node) { if (nodeProfile) return nodeProfile; const editor = vscode.window.activeTextEditor; - if ( - editor?.document?.languageId === 'sql' && - editor.document.uri.scheme !== DEFINITION_SCHEME - ) { + if (editor && isSqlDocument(editor.document)) { const session = await editorSessionManager.ensureSession(editor.document); if (!session) return null; return connectionStore.get(session.profileId) || null; @@ -200,11 +199,11 @@ async function activate(context) { sqlNavigationProvider, ); const definitionRegistration = vscode.languages.registerDefinitionProvider( - { language: 'sql' }, + SQL_DOCUMENT_SELECTOR, sqlNavigationProvider, ); const declarationRegistration = vscode.languages.registerDeclarationProvider( - { language: 'sql' }, + SQL_DOCUMENT_SELECTOR, sqlNavigationProvider, ); context.subscriptions.push( diff --git a/src/managers/editorSessionManager.js b/src/managers/editorSessionManager.js index d72adac..5aa4a54 100644 --- a/src/managers/editorSessionManager.js +++ b/src/managers/editorSessionManager.js @@ -3,8 +3,7 @@ const { randomUUID } = require('node:crypto'); const vscode = require('vscode'); const { getDatabaseEngine } = require('../databaseEngines'); - -const DEFINITION_SCHEME = 'simple-db-definition'; +const { DEFINITION_SCHEME, isSqlDocument } = require('../sql/sqlDocument'); function databaseForProfile(profile, preferred = '') { return ( @@ -52,7 +51,7 @@ class EditorSessionManager { } _isSqlDocument(document) { - return document?.languageId === 'sql' && document.uri.scheme !== DEFINITION_SCHEME; + return isSqlDocument(document); } _canPersist(document) { diff --git a/src/sql/sqlDocument.js b/src/sql/sqlDocument.js new file mode 100644 index 0000000..78d445b --- /dev/null +++ b/src/sql/sqlDocument.js @@ -0,0 +1,28 @@ +'use strict'; + +const DEFINITION_SCHEME = 'simple-db-definition'; + +function hasSqlExtension(document) { + const resourcePath = String(document?.uri?.path || document?.uri?.fsPath || ''); + return resourcePath.toLowerCase().endsWith('.sql'); +} + +function isSqlDocument(document, options = {}) { + if (!document?.uri) return false; + if (!options.allowDefinition && document.uri.scheme === DEFINITION_SCHEME) { + return false; + } + return document.languageId === 'sql' || hasSqlExtension(document); +} + +const SQL_DOCUMENT_SELECTOR = [ + { language: 'sql' }, + { pattern: '**/*.sql' }, +]; + +module.exports = { + DEFINITION_SCHEME, + SQL_DOCUMENT_SELECTOR, + hasSqlExtension, + isSqlDocument, +}; diff --git a/src/test/manifest.test.js b/src/test/manifest.test.js index 931bdfd..65d1c23 100644 --- a/src/test/manifest.test.js +++ b/src/test/manifest.test.js @@ -3,11 +3,23 @@ const manifest = require('../../package.json'); describe('extension manifest', () => { - it('ships 0.1.6 without the removed History feature', () => { - expect(manifest.version).toBe('0.1.6'); + it('ships 0.1.7 without the removed History feature', () => { + expect(manifest.version).toBe('0.1.7'); expect(JSON.stringify(manifest).toLowerCase()).not.toContain('history'); }); + it('recognizes .sql files without depending on another SQL language extension', () => { + expect(manifest.contributes.languages).toContainEqual( + expect.objectContaining({ id: 'sql', extensions: ['.sql'] }), + ); + expect(manifest.contributes.menus['editor/context']).toContainEqual( + expect.objectContaining({ + submenu: 'simpleDb.editorContextMenu', + when: 'editorLangId == sql || resourceExtname == .sql', + }), + ); + }); + it('exposes only Open Configuration in the Command Palette', () => { const commandIds = manifest.contributes.commands.map((entry) => entry.command); const hiddenIds = new Set( @@ -73,6 +85,7 @@ describe('extension manifest', () => { 'simpleDb.goToPackageSpecification', 'simpleDb.goToPackageBody', ]); + expect(menu.every((entry) => !entry.when || !entry.when.includes('editorLangId'))).toBe(true); }); it('uses Ctrl+Enter, F5, and F12 for the main SQL workflow', () => { diff --git a/src/test/resultViewHtml.test.js b/src/test/resultViewHtml.test.js index 63502b5..b008b33 100644 --- a/src/test/resultViewHtml.test.js +++ b/src/test/resultViewHtml.test.js @@ -21,7 +21,7 @@ describe('lower Results view', () => { const panel = packageJson.contributes.viewsContainers.panel; const views = packageJson.contributes.views.simpleDbResults; - expect(packageJson.version).toBe('0.1.6'); + expect(packageJson.version).toBe('0.1.7'); expect(panel).toContainEqual( expect.objectContaining({ id: 'simpleDbResults', title: 'Simple DB' }), ); diff --git a/src/test/sqlDocument.test.js b/src/test/sqlDocument.test.js new file mode 100644 index 0000000..0278eeb --- /dev/null +++ b/src/test/sqlDocument.test.js @@ -0,0 +1,42 @@ +'use strict'; + +const { + DEFINITION_SCHEME, + hasSqlExtension, + isSqlDocument, +} = require('../sql/sqlDocument'); + +function document({ languageId = 'plaintext', scheme = 'file', path = '/work/query.sql' } = {}) { + return { + languageId, + uri: { scheme, path }, + }; +} + +describe('SQL document recognition', () => { + it('recognizes .sql files even when VS Code reports plaintext', () => { + const sqlFile = document({ languageId: 'plaintext' }); + expect(hasSqlExtension(sqlFile)).toBe(true); + expect(isSqlDocument(sqlFile)).toBe(true); + }); + + it('keeps SQL language documents supported even when they are untitled', () => { + expect( + isSqlDocument(document({ languageId: 'sql', scheme: 'untitled', path: 'Untitled-1' })), + ).toBe(true); + }); + + it('does not treat unrelated plaintext files as SQL', () => { + expect(isSqlDocument(document({ path: '/work/notes.txt' }))).toBe(false); + }); + + it('keeps generated definition documents out of executable editor sessions', () => { + const definition = document({ + languageId: 'sql', + scheme: DEFINITION_SCHEME, + path: '/PACKAGE.sql', + }); + expect(isSqlDocument(definition)).toBe(false); + expect(isSqlDocument(definition, { allowDefinition: true })).toBe(true); + }); +}); diff --git a/src/views/sqlNavigationProvider.js b/src/views/sqlNavigationProvider.js index b1f9109..46f9e02 100644 --- a/src/views/sqlNavigationProvider.js +++ b/src/views/sqlNavigationProvider.js @@ -1,7 +1,7 @@ 'use strict'; const vscode = require('vscode'); -const { DEFINITION_SCHEME } = require('../managers/editorSessionManager'); +const { DEFINITION_SCHEME, isSqlDocument } = require('../sql/sqlDocument'); const { extractSqlReference, resolveSqlTargets } = require('../services/sqlNavigation'); function positionAt(text, offset) { @@ -190,7 +190,7 @@ class SqlNavigationProvider { async openFromActiveEditor(mode, options = {}) { const editor = vscode.window.activeTextEditor; - if (!editor || editor.document.languageId !== 'sql') { + if (!editor || !isSqlDocument(editor.document, { allowDefinition: true })) { throw new Error('Open a SQL editor before navigating to a database object.'); } let locations;