/// <summary> /// Called before character type is passed down to the core editor /// along the controll chain. Gives language-specific controller /// a chance to initiate different action and potentially 'eat' /// the character. For example, in R typing 'abc[TAB] should bring /// up intellisense list rather than actually insert the tab character. /// </summary> /// <returns> /// True if character was handled and should not be /// passed down to core editor or false otherwise. /// </returns> public override bool OnPreTypeChar(char typedCharacter) { // Allow tab to bring intellisense if // a) REditorSettings.ShowCompletionOnTab true // b) Position is at the end of a string so we bring completion for files // c) There is no selection if (typedCharacter == '\t' && !HasActiveCompletionSession && TextView.Selection.StreamSelectionSpan.Length == 0) { // if previous character is identifier character, bring completion list SnapshotPoint?position = REditorDocument.MapCaretPositionFromView(TextView); if (position.HasValue) { int pos = position.Value; if (pos > 0 && pos <= position.Value.Snapshot.Length) { bool endOfIdentifier = RTokenizer.IsIdentifierCharacter(position.Value.Snapshot[pos - 1]); bool showCompletion = endOfIdentifier && REditorSettings.ShowCompletionOnTab; if (!showCompletion) { var document = REditorDocument.FromTextBuffer(position.Value.Snapshot.TextBuffer); string directory; showCompletion = RCompletionEngine.CanShowFileCompletion(document.EditorTree.AstRoot, pos, out directory); } if (showCompletion) { ShowCompletion(autoShownCompletion: false); return(true); // eat the character } } } } return(base.OnPreTypeChar(typedCharacter)); }
private static CompletionList OrderList(IReadOnlyCollection <Completion> completions) { // Place 'name =' at the top prioritizing argument names // Place items starting with non-alpha characters like .Call and && // at the end of the list. var argumentNames = completions.Where(x => RTokenizer.IsIdentifierCharacter(x.DisplayText[0]) && x.DisplayText.EndsWith("=", StringComparison.Ordinal)); var rtvsNames = completions.Where(x => x.DisplayText.IndexOf(".rtvs") >= 0); var specialNames = completions.Where(x => !char.IsLetter(x.DisplayText[0])); specialNames = specialNames.Except(rtvsNames); var generalEntries = completions.Except(argumentNames); generalEntries = generalEntries.Except(rtvsNames); generalEntries = generalEntries.Except(specialNames); List <Completion> orderedCompletions = new List <Completion>(); orderedCompletions.AddRange(argumentNames); orderedCompletions.AddRange(generalEntries); orderedCompletions.AddRange(specialNames); return(new CompletionList(orderedCompletions)); }
/// <summary> /// Calculates span in the text buffer that contains data /// applicable to the current completion session. A tracking /// span will be created over it and editor will grow and shrink /// tracking span as user types and filter completion session /// based on the data inside the tracking span. /// </summary> private Span?GetApplicableSpan(IRIntellisenseContext context, ICompletionSession session) { var selectedSpans = session.TextView.Selection.SelectedSpans; if (selectedSpans.Count == 1 && selectedSpans[0].Span.Length > 0) { var spans = session.TextView.MapDownToR(selectedSpans[0]); if (spans.Count > 0) { return(spans[0].Span); } return(null); } var snapshot = _textBuffer.CurrentSnapshot; var line = snapshot.GetLineFromPosition(context.Position); var lineText = snapshot.GetText(line.Start, line.Length); var linePosition = context.Position - line.Start; // If completion is in comment, such as in Roxygen, // allow any text since it is no longer language-specific var document = context.EditorBuffer.GetEditorDocument <IREditorDocument>(); if (document.IsPositionInComment(context.Position)) { var typedText = lineText.TextBeforePosition(linePosition); return(new Span(context.Position - typedText.Length, typedText.Length)); } var start = 0; var end = line.Length; for (var i = linePosition - 1; i >= 0; i--) { var ch = lineText[i]; if (!RTokenizer.IsIdentifierCharacter(ch)) { start = i + 1; break; } } for (var i = linePosition; i < lineText.Length; i++) { var ch = lineText[i]; if (!RTokenizer.IsIdentifierCharacter(ch)) { end = i; break; } } if (start < end) { return(new Span(start + line.Start, end - start)); } return(new Span(context.Position, 0)); }
/// <summary> /// Called after character is typed. Gives language-specific completion /// controller has a chance to dismiss or initiate completion and paramenter /// help sessions depending on the current context. /// </summary> public override void OnPostTypeChar(char typedCharacter) { if (typedCharacter == '(' || typedCharacter == ',') { // Check if caret moved into a different functions such as when // user types a sequence of nested function calls. If so, // dismiss current signature session and start a new one. if (!SignatureHelper.IsSameSignatureContext(TextView, _textBuffer, SignatureBroker)) { DismissAllSessions(); TriggerSignatureHelp(); } } else if (HasActiveSignatureSession(TextView) && typedCharacter == ')') { // Typing closing ) closes signature and completion sessions. DismissAllSessions(); // However, when user types closing brace is an expression inside // function argument like in x = y * (z + 1) we need to re-trigger // signature session AstRoot ast = REditorDocument.FromTextBuffer(TextView.TextBuffer).EditorTree.AstRoot; FunctionCall f = ast.GetNodeOfTypeFromPosition <FunctionCall>(TextView.Caret.Position.BufferPosition); if (f != null) { TriggerSignatureHelp(); } } else if (HasActiveSignatureSession(TextView) && typedCharacter == '\n') { // Upon ENTER we need to dismiss all sessions and re-trigger // signature help. Triggering signature help outside of // a function definition or call is a no-op so it is safe. DismissAllSessions(); TriggerSignatureHelp(); } else if (this.HasActiveCompletionSession) { if (typedCharacter == '\'' || typedCharacter == '\"') { // First handle completion of a string. base.OnPostTypeChar(typedCharacter); // Then re-trigger completion. DismissAllSessions(); ShowCompletion(autoShownCompletion: true); return; } else { // Backspace does not dismiss completion. Characters that may be an identifier // name do not dismiss completion either allowing correction of typing. if (typedCharacter != '\b' && !RTokenizer.IsIdentifierCharacter(typedCharacter)) { DismissCompletionSession(); } } } base.OnPostTypeChar(typedCharacter); }
/// <summary> /// Calculates span in the text buffer that contains data /// applicable to the current completion session. A tracking /// span will be created over it and editor will grow and shrink /// tracking span as user types and filter completion session /// based on the data inside the tracking span. /// </summary> private Span?GetApplicableSpan(int position, ICompletionSession session) { var selectedSpans = session.TextView.Selection.SelectedSpans; if (selectedSpans.Count == 1 && selectedSpans[0].Span.Length > 0) { var spans = session.TextView.MapDownToR(selectedSpans[0]); if (spans.Count > 0) { return(spans[0].Span); } return(null); } ITextSnapshot snapshot = _textBuffer.CurrentSnapshot; ITextSnapshotLine line = snapshot.GetLineFromPosition(position); string lineText = snapshot.GetText(line.Start, line.Length); int linePosition = position - line.Start; int start = 0; int end = line.Length; for (int i = linePosition - 1; i >= 0; i--) { char ch = lineText[i]; if (!RTokenizer.IsIdentifierCharacter(ch)) { start = i + 1; break; } } for (int i = linePosition; i < lineText.Length; i++) { char ch = lineText[i]; if (!RTokenizer.IsIdentifierCharacter(ch)) { end = i; break; } } if (start < end) { return(new Span(start + line.Start, end - start)); } return(new Span(position, 0)); }
/// <summary> /// Retrieves name of the package in 'package::' statement /// so intellisense can show list of functions available /// in the specific package. /// </summary> private IEnumerable <IPackageInfo> GetSpecificPackage(RCompletionContext context) { List <IPackageInfo> packages = new List <IPackageInfo>(); ITextSnapshot snapshot = context.TextBuffer.CurrentSnapshot; int colons = 0; for (int i = context.Position - 1; i >= 0; i--, colons++) { char ch = snapshot[i]; if (ch != ':') { break; } } if (colons > 1 && colons < 4) { string packageName = string.Empty; int start = 0; int end = context.Position - colons; for (int i = end - 1; i >= 0; i--) { char ch = snapshot[i]; if (!RTokenizer.IsIdentifierCharacter(ch)) { start = i + 1; break; } } if (start < end) { packageName = snapshot.GetText(Span.FromBounds(start, end)); if (packageName.Length > 0) { context.InternalFunctions = colons == 3; var package = GetPackageByName(packageName); if (package != null) { packages.Add(package); } } } } return(packages); }
/// <summary> /// Retrieves name of the package in 'package::' statement /// so intellisense can show list of functions available /// in the specific package. /// </summary> private IEnumerable <IPackageInfo> GetSpecificPackage(IRIntellisenseContext context) { var packages = new List <IPackageInfo>(); var snapshot = context.EditorBuffer.CurrentSnapshot; var colons = 0; for (var i = context.Position - 1; i >= 0; i--, colons++) { var ch = snapshot[i]; if (ch != ':') { break; } } if (colons > 1 && colons < 4) { var packageName = string.Empty; var start = 0; var end = context.Position - colons; for (var i = end - 1; i >= 0; i--) { var ch = snapshot[i]; if (!RTokenizer.IsIdentifierCharacter(ch)) { start = i + 1; break; } } if (start < end) { packageName = snapshot.GetText(TextRange.FromBounds(start, end)); if (packageName.Length > 0) { context.InternalFunctions = colons == 3; var package = GetPackageByName(packageName); if (package != null) { packages.Add(package); } } } } return(packages); }
private static string GetFilterPrefix(IRIntellisenseContext context) { var snapshot = context.EditorBuffer.CurrentSnapshot; var line = snapshot.GetLineFromPosition(context.Position); var text = line.GetText(); int i; var offset = context.Position - line.Start; for (i = offset - 1; i >= 0 && RTokenizer.IsIdentifierCharacter(text[i]); i--) { } i = Math.Min(offset, i + 1); return(text.Substring(i, offset - i)); }
/// <summary> /// Called before character type is passed down to the core editor /// along the controll chain. Gives language-specific controller /// a chance to initiate different action and potentially 'eat' /// the character. For example, in R typing 'abc[TAB] should bring /// up intellisense list rather than actually insert the tab character. /// </summary> /// <returns> /// True if character was handled and should not be /// passed down to core editor or false otherwise. /// </returns> public override bool OnPreTypeChar(char typedCharacter) { // Allow tab to bring intellisense if // a) REditorSettings.ShowCompletionOnTab true // b) Position is at the end of a string so we bring completion for files // c) There is no selection if (typedCharacter == '\t' && !HasActiveCompletionSession && TextView.Selection.StreamSelectionSpan.Length == 0) { // if previous character is identifier character, bring completion list var position = TextView.GetCaretPosition(_textBuffer); if (position.HasValue) { int pos = position.Value; var document = position.Value.Snapshot.TextBuffer.GetEditorDocument <IREditorDocument>(); if (!document.IsPositionInComment(pos)) { if (pos > 0 && pos <= position.Value.Snapshot.Length) { var endOfIdentifier = RTokenizer.IsIdentifierCharacter(position.Value.Snapshot[pos - 1]); var showCompletion = endOfIdentifier && _settings.ShowCompletionOnTab; if (!showCompletion) { showCompletion = RCompletionEngine.CanShowFileCompletion(document.EditorTree.AstRoot, pos, out string directory); } if (showCompletion) { ShowCompletion(autoShownCompletion: false); return(true); // eat the character } } } } } else if (typedCharacter == '#') { if (TryInsertRoxygenBlock()) { return(true); } } return(base.OnPreTypeChar(typedCharacter)); }
public static string GetVariableName(ITextView textView, ITextSnapshot snapshot) { SnapshotPoint?pt = REditorDocument.MapCaretPositionFromView(textView); if (pt.HasValue && pt.Value > 0) { int i = pt.Value - 1; for (; i >= 0; i--) { char ch = snapshot[i]; if (!RTokenizer.IsIdentifierCharacter(ch) && ch != '$' && ch != '@') { break; } } return(snapshot.GetText(Span.FromBounds(i + 1, pt.Value))); } return(string.Empty); }
/// <summary> /// Determines if position is in object member. Typically used /// to suppress general intellisense when typing data member /// name such as 'mtcars$|' /// </summary> internal static bool IsInObjectMemberName(ITextProvider textProvider, int position) { if (position > 0) { for (int i = position - 1; i >= 0; i--) { char ch = textProvider[i]; if (ch == '$' || ch == '@') { return(true); } if (!RTokenizer.IsIdentifierCharacter(ch)) { break; } } } return(false); }
/// <summary> /// Should this key press trigger a completion session? /// </summary> public override bool IsTriggerChar(char typedCharacter) { if (!HasActiveCompletionSession) { switch (typedCharacter) { case '$': case '@': return(true); case ':': return(RCompletionContext.IsCaretInNamespace(TextView)); case '(': return(RCompletionContext.IsCaretInLibraryStatement(TextView)); default: if (_settings.ShowCompletionOnFirstChar) { SnapshotPoint?position = REditorDocument.MapCaretPositionFromView(TextView); if (position.HasValue) { int pos = position.Value; var snapshot = position.Value.Snapshot; // Trigger on first character if (RTokenizer.IsIdentifierCharacter(typedCharacter) && !char.IsDigit(typedCharacter)) { // Ignore if this is not the first character return(pos <= 1 || (pos > 1 && !RTokenizer.IsIdentifierCharacter(snapshot[pos - 2]))); } } } break; } } return(false); }
private static CompletionList OrderList(List <ICompletionEntry> completions) { // Place 'name =' at the top prioritizing argument names // Place items starting with non-alpha characters like .Call and && // at the end of the list. var orderedCompletions = new List <Completion>(); var specialNames = new List <Completion>(); var generalEntries = new List <Completion>(); foreach (var c in completions) { if (RTokenizer.IsIdentifierCharacter(c.DisplayText[0]) && c.DisplayText.EndsWith("=", StringComparison.Ordinal)) { // Place argument completions first orderedCompletions.Add(new RCompletion(c)); } else if (c.DisplayText.IndexOfIgnoreCase(".rtvs") < 0) { // Exclude .rtvs if (!char.IsLetter(c.DisplayText[0])) { // Special names will come last specialNames.Add(new RCompletion(c)); } else { generalEntries.Add(new RCompletion(c)); } } } orderedCompletions.AddRange(generalEntries); orderedCompletions.AddRange(specialNames); return(new CompletionList(orderedCompletions)); }
/// <summary> /// Provides list of completion entries for a given location in the AST. /// </summary> /// <param name="tree">Document tree</param> /// <param name="position">Caret position in the document</param> /// <param name="autoShownCompletion">True if completion is forced (like when typing Ctrl+Space)</param> /// <returns>List of completion entries for a given location in the AST</returns> public static IReadOnlyCollection <IRCompletionListProvider> GetCompletionForLocation(RCompletionContext context, bool autoShownCompletion) { List <IRCompletionListProvider> providers = new List <IRCompletionListProvider>(); if (context.AstRoot.Comments.Contains(context.Position)) { // No completion in comments return(providers); } // First check file completion - it happens inside strings string directory; if (CanShowFileCompletion(context.AstRoot, context.Position, out directory)) { if (!string.IsNullOrEmpty(directory)) { providers.Add(new FilesCompletionProvider(directory)); } return(providers); } // Now check if position is inside a string and if so, suppress completion list var tokenNode = context.AstRoot.GetNodeOfTypeFromPosition <TokenNode>(context.Position); if (tokenNode != null && tokenNode.Token.TokenType == RTokenType.String) { // No completion in string return(providers); } // Identifier character is a trigger but only as a first character so it doesn't suddenly // bring completion back on a second character if user dismissed it after the first one, // or in a middle of 'install.packages' when user types dot or in floating point numbers. if (context.Position > 1 && autoShownCompletion) { char triggerChar = context.TextBuffer.CurrentSnapshot.GetText(context.Position - 1, 1)[0]; char charBeforeTigger = context.TextBuffer.CurrentSnapshot.GetText(context.Position - 2, 1)[0]; if (RTokenizer.IsIdentifierCharacter(triggerChar) && RTokenizer.IsIdentifierCharacter(charBeforeTigger)) { return(providers); } } if (IsInFunctionArgumentName <FunctionDefinition>(context.AstRoot, context.Position)) { // No completion in function definition argument names return(providers); } if (IsInObjectMemberName(context.AstRoot.TextProvider, context.Position)) { providers.Add(new WorkspaceVariableCompletionProvider()); return(providers); } if (IsPackageListCompletion(context.TextBuffer, context.Position)) { providers.Add(new PackagesCompletionProvider()); } else { if (IsInFunctionArgumentName <FunctionCall>(context.AstRoot, context.Position)) { providers.Add(new ParameterNameCompletionProvider()); } foreach (var p in CompletionProviders) { providers.Add(p.Value); } if (!context.IsInNameSpace()) { providers.Add(new PackagesCompletionProvider()); } } providers.Add(new WorkspaceVariableCompletionProvider()); return(providers); }