/// <summary> /// Update the autocomplete metadata provider when the user connects to a database /// </summary> /// <param name="info"></param> public async Task UpdateLanguageServiceOnConnection(ConnectionInfo info) { await Task.Run(() => { ScriptParseInfo scriptInfo = GetScriptParseInfo(info.OwnerUri, createIfNotExists: true); if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout)) { try { scriptInfo.ConnectionKey = this.BindingQueue.AddConnectionContext(info); scriptInfo.IsConnected = true; } catch (Exception ex) { Logger.Write(LogLevel.Error, "Unknown error in OnConnection " + ex.ToString()); scriptInfo.IsConnected = false; } finally { // Set Metadata Build event to Signal state. // (Tell Language Service that I am ready with Metadata Provider Object) Monitor.Exit(scriptInfo.BuildingMetadataLock); } } AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue); }); }
/// <summary> /// Get quick info hover tooltips for the current position /// </summary> /// <param name="textDocumentPosition"></param> /// <param name="scriptFile"></param> internal Hover GetHoverItem(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile) { int startLine = textDocumentPosition.Position.Line; int startColumn = TextUtilities.PositionOfPrevDelimeter( scriptFile.Contents, textDocumentPosition.Position.Line, textDocumentPosition.Position.Character); int endColumn = textDocumentPosition.Position.Character; ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); if (scriptParseInfo != null && scriptParseInfo.ParseResult != null) { if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) { try { QueueItem queueItem = this.BindingQueue.QueueBindingOperation( key: scriptParseInfo.ConnectionKey, bindingTimeout: LanguageService.HoverTimeout, bindOperation: (bindingContext, cancelToken) => { // get the current quick info text Babel.CodeObjectQuickInfo quickInfo = Resolver.GetQuickInfo( scriptParseInfo.ParseResult, startLine + 1, endColumn + 1, bindingContext.MetadataDisplayInfoProvider); // convert from the parser format to the VS Code wire format return(AutoCompleteHelper.ConvertQuickInfoToHover( quickInfo, startLine, startColumn, endColumn)); }); queueItem.ItemProcessed.WaitOne(); return(queueItem.GetResultAsT <Hover>()); } finally { Monitor.Exit(scriptParseInfo.BuildingMetadataLock); } } } // return null if there isn't a tooltip for the current location return(null); }
/// <summary> /// Preinitialize the parser and binder with common metadata. /// This should front load the long binding wait to the time the /// connection is established. Once this is completed other binding /// requests should be faster. /// </summary> /// <param name="info"></param> /// <param name="scriptInfo"></param> internal static void PrepopulateCommonMetadata( ConnectionInfo info, ScriptParseInfo scriptInfo, ConnectedBindingQueue bindingQueue) { if (scriptInfo.IsConnected) { var scriptFile = AutoCompleteHelper.WorkspaceServiceInstance.Workspace.GetFile(info.OwnerUri); LanguageService.Instance.ParseAndBind(scriptFile, info); if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout)) { try { QueueItem queueItem = bindingQueue.QueueBindingOperation( key: scriptInfo.ConnectionKey, bindingTimeout: AutoCompleteHelper.PrepopulateBindTimeout, waitForLockTimeout: AutoCompleteHelper.PrepopulateBindTimeout, bindOperation: (bindingContext, cancelToken) => { // parse a simple statement that returns common metadata ParseResult parseResult = Parser.Parse( "select ", bindingContext.ParseOptions); List <ParseResult> parseResults = new List <ParseResult>(); parseResults.Add(parseResult); bindingContext.Binder.Bind( parseResults, info.ConnectionDetails.DatabaseName, BindMode.Batch); // get the completion list from SQL Parser var suggestions = Resolver.FindCompletions( parseResult, 1, 8, bindingContext.MetadataDisplayInfoProvider); // this forces lazy evaluation of the suggestion metadata AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8); parseResult = Parser.Parse( "exec ", bindingContext.ParseOptions); parseResults = new List <ParseResult>(); parseResults.Add(parseResult); bindingContext.Binder.Bind( parseResults, info.ConnectionDetails.DatabaseName, BindMode.Batch); // get the completion list from SQL Parser suggestions = Resolver.FindCompletions( parseResult, 1, 6, bindingContext.MetadataDisplayInfoProvider); // this forces lazy evaluation of the suggestion metadata AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6); return(null); }); queueItem.ItemProcessed.WaitOne(); } catch { } finally { Monitor.Exit(scriptInfo.BuildingMetadataLock); } } } }
/// <summary> /// Return the completion item list for the current text position. /// This method does not await cache builds since it expects to return quickly /// </summary> /// <param name="textDocumentPosition"></param> public CompletionItem[] GetCompletionItems( TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ConnectionInfo connInfo) { // initialize some state to parse and bind the current script file this.currentCompletionParseInfo = null; CompletionItem[] resultCompletionItems = null; string filePath = textDocumentPosition.TextDocument.Uri; int startLine = textDocumentPosition.Position.Line; int parserLine = textDocumentPosition.Position.Line + 1; int startColumn = TextUtilities.PositionOfPrevDelimeter( scriptFile.Contents, textDocumentPosition.Position.Line, textDocumentPosition.Position.Character); int endColumn = TextUtilities.PositionOfNextDelimeter( scriptFile.Contents, textDocumentPosition.Position.Line, textDocumentPosition.Position.Character); int parserColumn = textDocumentPosition.Position.Character + 1; bool useLowerCaseSuggestions = this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value; // get the current script parse info object ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); if (scriptParseInfo == null) { return(AutoCompleteHelper.GetDefaultCompletionItems( startLine, startColumn, endColumn, useLowerCaseSuggestions)); } // reparse and bind the SQL statement if needed if (RequiresReparse(scriptParseInfo, scriptFile)) { ParseAndBind(scriptFile, connInfo); } // if the parse failed then return the default list if (scriptParseInfo.ParseResult == null) { return(AutoCompleteHelper.GetDefaultCompletionItems( startLine, startColumn, endColumn, useLowerCaseSuggestions)); } // need to adjust line & column for base-1 parser indices Token token = GetToken(scriptParseInfo, parserLine, parserColumn); string tokenText = token != null ? token.Text : null; // check if the file is connected and the file lock is available if (scriptParseInfo.IsConnected && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) { try { // queue the completion task with the binding queue QueueItem queueItem = this.BindingQueue.QueueBindingOperation( key: scriptParseInfo.ConnectionKey, bindingTimeout: LanguageService.BindingTimeout, bindOperation: (bindingContext, cancelToken) => { // get the completion list from SQL Parser scriptParseInfo.CurrentSuggestions = Resolver.FindCompletions( scriptParseInfo.ParseResult, parserLine, parserColumn, bindingContext.MetadataDisplayInfoProvider); // cache the current script parse info object to resolve completions later this.currentCompletionParseInfo = scriptParseInfo; // convert the suggestion list to the VS Code format return(AutoCompleteHelper.ConvertDeclarationsToCompletionItems( scriptParseInfo.CurrentSuggestions, startLine, startColumn, endColumn)); }, timeoutOperation: (bindingContext) => { // return the default list if the connected bind fails return(AutoCompleteHelper.GetDefaultCompletionItems( startLine, startColumn, endColumn, useLowerCaseSuggestions, tokenText)); }); // wait for the queue item queueItem.ItemProcessed.WaitOne(); var completionItems = queueItem.GetResultAsT <CompletionItem[]>(); if (completionItems != null && completionItems.Length > 0) { resultCompletionItems = completionItems; } else if (!ShouldShowCompletionList(token)) { resultCompletionItems = AutoCompleteHelper.EmptyCompletionList; } } finally { Monitor.Exit(scriptParseInfo.BuildingMetadataLock); } } // if there are no completions then provide the default list if (resultCompletionItems == null) { resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems( startLine, startColumn, endColumn, useLowerCaseSuggestions, tokenText); } return(resultCompletionItems); }