/// <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); }
/// <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> /// Parses the SQL text and binds it to the SMO metadata provider if connected /// </summary> /// <param name="filePath"></param> /// <param name="sqlText"></param> /// <returns>The ParseResult instance returned from SQL Parser</returns> public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo) { // get or create the current parse info object ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientFilePath, createIfNotExists: true); if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout)) { try { if (connInfo == null || !parseInfo.IsConnected) { // parse current SQL file contents to retrieve a list of errors ParseResult parseResult = Parser.IncrementalParse( scriptFile.Contents, parseInfo.ParseResult, this.DefaultParseOptions); parseInfo.ParseResult = parseResult; } else { QueueItem queueItem = this.BindingQueue.QueueBindingOperation( key: parseInfo.ConnectionKey, bindingTimeout: LanguageService.BindingTimeout, bindOperation: (bindingContext, cancelToken) => { try { ParseResult parseResult = Parser.IncrementalParse( scriptFile.Contents, parseInfo.ParseResult, bindingContext.ParseOptions); parseInfo.ParseResult = parseResult; List <ParseResult> parseResults = new List <ParseResult>(); parseResults.Add(parseResult); bindingContext.Binder.Bind( parseResults, connInfo.ConnectionDetails.DatabaseName, BindMode.Batch); } catch (ConnectionException) { Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object..."); } catch (SqlParserInternalBinderError) { Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object..."); } catch (Exception ex) { Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString()); } return(null); }); queueItem.ItemProcessed.WaitOne(); } } catch (Exception ex) { // reset the parse result to do a full parse next time parseInfo.ParseResult = null; Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString()); } finally { Monitor.Exit(parseInfo.BuildingMetadataLock); } } else { Logger.Write(LogLevel.Warning, "Binding metadata lock timeout in ParseAndBind"); } return(parseInfo.ParseResult); }