diff --git a/packages/core/dialect/sqlite/index.ts b/packages/core/dialect/sqlite/index.ts index 6b37c5414..3ef62fddc 100644 --- a/packages/core/dialect/sqlite/index.ts +++ b/packages/core/dialect/sqlite/index.ts @@ -6,6 +6,8 @@ import SQLiteLib from 'sqlite3'; import GenericDialect from '../generic'; import queries from './queries'; import { DatabaseInterface } from '@sqltools/core/plugin-api'; +import makeDir from 'make-dir'; +import { dirname } from 'path'; const SQLite3Version = '4.0.8'; @@ -24,13 +26,20 @@ export default class SQLite extends GenericDialect implement return __non_webpack_require__('sqlite3') as SQLiteLib.sqlite3; } + createFileIfNotExists = () => { + if (this.credentials.database.toLowerCase() === ':memory:') return; + + const baseDir = dirname(this.credentials.database); + makeDir.sync(baseDir); + } + public async open() { if (this.connection) { return this.connection; } this.needToInstallDependencies(); - + this.createFileIfNotExists(); const db = await new Promise((resolve, reject) => { const instance = new (this.lib).Database(this.credentials.database, (err) => { if (err) return reject(err); diff --git a/packages/core/package.json b/packages/core/package.json index 5a81d9921..ced18c604 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -12,6 +12,7 @@ "cassandra-driver": "^4.1.0", "command-exists": "^1.2.8", "compare-versions": "^3.5.1", + "make-dir": "^3.0.0", "mssql": "^5.0.5", "mysql": "^2.16.0", "pg": "^7.12.1", diff --git a/packages/core/plugin-api.d.ts b/packages/core/plugin-api.d.ts index 354ede0be..d44c3d377 100644 --- a/packages/core/plugin-api.d.ts +++ b/packages/core/plugin-api.d.ts @@ -93,6 +93,7 @@ export declare namespace SQLTools { addOnInitializedHook(hook: Arg0): this; notifyError(message: string, error?: any): any; client: IConnection['client']; + server: IConnection; docManager: TextDocuments; telemetry: TelemetryInterface; store: S; diff --git a/packages/core/utils/index.ts b/packages/core/utils/index.ts index d2b24f8ae..aaf3e5152 100644 --- a/packages/core/utils/index.ts +++ b/packages/core/utils/index.ts @@ -21,7 +21,7 @@ export function getConnectionDescription(c: ConnectionInterface): string | null if (!c) return null; if (c.dialect === DatabaseDialect.SQLite) { - return `file://${c.database}`; + return c.database.replace(/\$\{workspaceFolder:(.+)}/g, '$1').replace(/\$\{workspaceFolder}/g, '.'); } if (c.connectString) { diff --git a/packages/core/utils/vscode/parse-workspace-path.ts b/packages/core/utils/vscode/parse-workspace-path.ts new file mode 100644 index 000000000..9929f4ab2 --- /dev/null +++ b/packages/core/utils/vscode/parse-workspace-path.ts @@ -0,0 +1,17 @@ +import { workspace } from 'vscode'; + +const WORKSPACE_PLACEHOLDER = /\$\{workspaceFolder(?:\:(.+))?}/; + +const parseWorkspacePath = (file: string) => { + if (!WORKSPACE_PLACEHOLDER.test(file)) return file; + const [ _, workspaceName ] = file.match(WORKSPACE_PLACEHOLDER) || []; + if (workspaceName) { + const workspacePath = (workspace.workspaceFolders.find(w => w.name === workspaceName) || { uri: { fsPath: '.' } }).uri.fsPath; + file = file.replace(WORKSPACE_PLACEHOLDER, `${workspacePath}`); + } else { + file = file.replace(WORKSPACE_PLACEHOLDER, `.`) + } + return file; +} + +export default parseWorkspacePath; \ No newline at end of file diff --git a/packages/core/utils/vscode/relative-to-workspace.ts b/packages/core/utils/vscode/relative-to-workspace.ts new file mode 100644 index 000000000..3c98d8ce1 --- /dev/null +++ b/packages/core/utils/vscode/relative-to-workspace.ts @@ -0,0 +1,20 @@ +import { workspace, Uri } from 'vscode'; +import path from 'path'; + +const relativeToWorkspace = (file: string) => { + const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(file)); + const isSavedWorkSpace = !!(workspace.workspaceFile && workspace.workspaceFile.scheme !== 'untitled'); + if (isSavedWorkSpace && workspaceFolder) { + // when using workspace files + file = workspace.asRelativePath(file, false); + file = `\${workspaceFolder:${workspaceFolder.name}}/${file}`; + } else if (workspaceFolder && workspace.workspaceFolders.length === 1) { + // when vscode opens a folder + file = workspace.asRelativePath(file, false); + file = `\${workspaceFolder}${path.sep}${file}`; + } + // if no folder is open or not saved workspace, just return + return file; +} + +export default relativeToWorkspace; \ No newline at end of file diff --git a/packages/language-server/server.ts b/packages/language-server/server.ts index 44e6e1c7f..6678ba9cd 100644 --- a/packages/language-server/server.ts +++ b/packages/language-server/server.ts @@ -139,6 +139,9 @@ ExecPath: ${process.execPath} return this; } + public get server() { + return this._server; + } public get client() { return this._server.client; } diff --git a/packages/plugins/connection-manager/extension.ts b/packages/plugins/connection-manager/extension.ts index d479f4ab4..aec7e62f1 100644 --- a/packages/plugins/connection-manager/extension.ts +++ b/packages/plugins/connection-manager/extension.ts @@ -9,19 +9,20 @@ import { getSelectedText, quickPick, readInput } from '@sqltools/core/utils/vsco import { SidebarConnection, SidebarTableOrView, ConnectionExplorer } from '@sqltools/plugins/connection-manager/explorer'; import ResultsWebviewManager from '@sqltools/plugins/connection-manager/screens/results'; import SettingsWebview from '@sqltools/plugins/connection-manager/screens/settings'; -import { commands, QuickPickItem, ExtensionContext, StatusBarAlignment, StatusBarItem, window, workspace, ConfigurationTarget, Uri, TextEditor, TextDocument, ProgressLocation, Progress } from 'vscode'; +import { commands, QuickPickItem, ExtensionContext, window, workspace, ConfigurationTarget, Uri, TextEditor, TextDocument, ProgressLocation, Progress } from 'vscode'; import { ConnectionDataUpdatedRequest, ConnectRequest, DisconnectRequest, GetConnectionDataRequest, GetConnectionPasswordRequest, GetConnectionsRequest, RefreshTreeRequest, RunCommandRequest, ProgressNotificationStart, ProgressNotificationComplete, ProgressNotificationStartParams, ProgressNotificationCompleteParams, TestConnectionRequest } from './contracts'; import path from 'path'; import CodeLensPlugin from '../codelens/extension'; import { getHome } from '@sqltools/core/utils'; import { extractConnName, getQueryParameters } from '@sqltools/core/utils/query'; import { CancellationTokenSource } from 'vscode-jsonrpc'; +import statusBar from './status-bar'; +import parseWorkspacePath from '@sqltools/core/utils/vscode/parse-workspace-path'; export default class ConnectionManagerPlugin implements SQLTools.ExtensionPlugin { public client: SQLTools.LanguageClientInterface; public resultsWebview: ResultsWebviewManager; public settingsWebview: SettingsWebview; - public statusBar: StatusBarItem;; private context: ExtensionContext; private errorHandler: SQLTools.ExtensionInterface['errorHandler']; private explorer: ConnectionExplorer; @@ -103,8 +104,6 @@ export default class ConnectionManagerPlugin implements SQLTools.ExtensionPlugin await this.client.sendRequest(DisconnectRequest, { conn }) this.client.telemetry.registerInfoMessage('Connection closed!'); await this.explorer.updateTreeRoot(); - this._updateStatusBar(); - } catch (e) { return this.errorHandler('Error closing connection', e); } @@ -464,38 +463,17 @@ export default class ConnectionManagerPlugin implements SQLTools.ExtensionPlugin let password = null; if (c && getConnectionId(c) !== this.explorer.getActiveId()) { + if (c.dialect === DatabaseDialect.SQLite) { + c.database = parseWorkspacePath(c.database); + } if (c.askForPassword) password = await this._askForPassword(c); if (c.askForPassword && password === null) return; - c = await this.client.sendRequest( - ConnectRequest, - { conn: c, password }, - ); + c = await this.client.sendRequest(ConnectRequest, { conn: c, password }); } await this.explorer.focusActiveConnection(c); - this._updateStatusBar(); return this.explorer.getActive(); } - private _updateStatusBar() { - if (!this.statusBar) { - this.statusBar = window.createStatusBarItem(StatusBarAlignment.Left, 10); - this.statusBar.tooltip = 'Select a connection'; - this.statusBar.command = `${EXT_NAME}.selectConnection`; - } - if (this.explorer.getActive()) { - this.statusBar.text = `$(database) ${this.explorer.getActive().name}`; - } else { - this.statusBar.text = '$(database) Connect'; - } - if (ConfigManager.showStatusbar) { - this.statusBar.show(); - } else { - this.statusBar.hide(); - } - - return this.statusBar; - } - private async saveConnectionList(connList: ConnectionInterface[], writeTo?: ConfigurationTarget) { if (!writeTo && (!workspace.workspaceFolders || workspace.workspaceFolders.length === 0)) { writeTo = ConfigurationTarget.Global; @@ -630,7 +608,10 @@ export default class ConnectionManagerPlugin implements SQLTools.ExtensionPlugin this.context = extension.context; this.errorHandler = extension.errorHandler; this.explorer = new ConnectionExplorer(extension); - + this.explorer.onConnectionDidChange(() => { + const active = this.explorer.getActive(); + statusBar.setText(active ? active.name : null); + }); this.client.onRequest(ConnectionDataUpdatedRequest, this.handler_connectionDataUpdated); this.client.onNotification(ProgressNotificationStart, this.handler_progressStart); this.client.onNotification(ProgressNotificationComplete, this.handler_progressComplete); @@ -639,7 +620,7 @@ export default class ConnectionManagerPlugin implements SQLTools.ExtensionPlugin this.context.subscriptions.push( (this.resultsWebview = new ResultsWebviewManager(this.context, this.client)), (this.settingsWebview = new SettingsWebview(this.context)), - this._updateStatusBar(), + statusBar, workspace.onDidCloseTextDocument(this.onDidOpenOrCloseTextDocument), workspace.onDidOpenTextDocument(this.onDidOpenOrCloseTextDocument), window.onDidChangeActiveTextEditor(this.changeTextEditorHandler), @@ -670,11 +651,6 @@ export default class ConnectionManagerPlugin implements SQLTools.ExtensionPlugin .registerCommand(`detachConnectionFromFile`, this.ext_detachConnectionFromFile) .registerCommand(`copyTextFromTreeItem`, this.ext_copyTextFromTreeItem); - // hooks - ConfigManager.addOnUpdateHook(() => { - this._updateStatusBar(); - }); - if (window.activeTextEditor) { setTimeout(() => { this.changeTextEditorHandler(window.activeTextEditor); diff --git a/packages/plugins/connection-manager/language-server.ts b/packages/plugins/connection-manager/language-server.ts index d49ccfffa..884c0d8f3 100644 --- a/packages/plugins/connection-manager/language-server.ts +++ b/packages/plugins/connection-manager/language-server.ts @@ -156,7 +156,7 @@ export default class ConnectionManagerPlugin implements SQLTools.LanguageServerP this.server.store.dispatch(actions.Connect(c)); this.server.sendNotification(ProgressNotificationStart, { ...progressBase, message: 'Connecting....' }); - await c.connect() + await c.connect(); await this._loadConnectionData(c); this.server.sendNotification(ProgressNotificationComplete, { ...progressBase, message: 'Connected!' }); return this.serializarConnectionState(req.conn); diff --git a/packages/plugins/connection-manager/screens/settings.ts b/packages/plugins/connection-manager/screens/settings.ts index 08ad67f83..2e910bd02 100644 --- a/packages/plugins/connection-manager/screens/settings.ts +++ b/packages/plugins/connection-manager/screens/settings.ts @@ -1,16 +1,10 @@ import { EXT_NAME } from '@sqltools/core/constants'; import { getConnectionId } from '@sqltools/core/utils'; import WebviewProvider from '@sqltools/plugins/connection-manager/screens/provider'; -import { commands, ExtensionContext, Uri, workspace } from 'vscode'; +import { commands, ExtensionContext, Uri } from 'vscode'; import path from 'path'; import { DatabaseDialect } from '@sqltools/core/interface'; - -const relativeToWorkspace = (file: string) => { - const fileUri = workspace.asRelativePath(Uri.file(file), true); - if (file === fileUri) return file; - if (fileUri.startsWith('/') || fileUri.startsWith('.//')) return file; - return `.${path.sep}${fileUri}`; -} +import relativeToWorkspace from '@sqltools/core/utils/vscode/relative-to-workspace'; export default class SettingsWebview extends WebviewProvider { protected id: string = 'Settings'; diff --git a/packages/plugins/connection-manager/status-bar.ts b/packages/plugins/connection-manager/status-bar.ts new file mode 100644 index 000000000..70ed5e9ad --- /dev/null +++ b/packages/plugins/connection-manager/status-bar.ts @@ -0,0 +1,21 @@ +import ConfigManager from '@sqltools/core/config-manager'; +import { window, StatusBarItem, StatusBarAlignment } from 'vscode'; +import { EXT_NAME } from '@sqltools/core/constants'; + +let statusBar: StatusBarItem & { setText: (text?: string) => void }; + +statusBar = window.createStatusBarItem(StatusBarAlignment.Left, 10); +statusBar.tooltip = 'Select a connection'; +statusBar.command = `${EXT_NAME}.selectConnection`; + +statusBar.setText = text => (statusBar.text = `$(database) ${text || 'Connect'}`); + +statusBar.setText(); + +const updateVisibility = () => ConfigManager.showStatusbar ? statusBar.show() : statusBar.hide(); + +updateVisibility(); + +ConfigManager.addOnUpdateHook(updateVisibility); + +export default statusBar; \ No newline at end of file diff --git a/packages/plugins/dependency-manager/extension.ts b/packages/plugins/dependency-manager/extension.ts index afa82ed6d..5f66e676d 100644 --- a/packages/plugins/dependency-manager/extension.ts +++ b/packages/plugins/dependency-manager/extension.ts @@ -1,9 +1,9 @@ import { window as Win, workspace, ConfigurationTarget, window, ProgressLocation, commands } from 'vscode'; import { InstallDepRequest, MissingModuleNotification, ElectronNotSupportedNotification, DependeciesAreBeingInstalledNotification } from '@sqltools/plugins/dependency-manager/contracts'; import SQLTools from '@sqltools/core/plugin-api'; -import { ConnectRequest } from '@sqltools/plugins/connection-manager/contracts'; import { openExternal } from '@sqltools/core/utils/vscode'; import { EXT_NAME, DOCS_ROOT_URL } from '@sqltools/core/constants'; +import { getConnectionId } from '@sqltools/core/utils'; export default class DependencyManager implements SQLTools.ExtensionPlugin { public client: SQLTools.LanguageClientInterface; @@ -69,7 +69,7 @@ Go ahead and connect!`, ...opt ); if (rr === opt[0]) { - await this.client.sendRequest(ConnectRequest, { conn }); + await commands.executeCommand(`${EXT_NAME}.showOutputChannel`, getConnectionId(conn)); } break; case readMore: diff --git a/yarn.lock b/yarn.lock index ba76f4d59..1ae827a8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5167,6 +5167,13 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" +make-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801" + integrity sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw== + dependencies: + semver "^6.0.0" + make-error@1.x: version "1.3.5" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" @@ -8351,7 +8358,7 @@ vscode-uri@^1.0.6: resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.8.tgz#9769aaececae4026fb6e22359cb38946580ded59" integrity sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ== -vscode@^1.1.30, vscode@^1.1.36: +vscode@^1.1.36: version "1.1.36" resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6" integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ==