private static void GetDocumentContext(ProjectAnalysisResult result, int textCursorOffset, ReadOnlyDocument doc) { #region Debugging Aid // Resolve content around text cursor #if false int numberOfCharactersAroundCursorToResolve = 20; int firstCharOffset = textCursorOffset - numberOfCharactersAroundCursorToResolve / 2; int lastCharOffset = textCursorOffset + numberOfCharactersAroundCursorToResolve / 2; #else int firstCharOffset = textCursorOffset - 40; int lastCharOffset = textCursorOffset + 20; #endif // shift window to the "right" if (firstCharOffset < 0) { lastCharOffset -= firstCharOffset; firstCharOffset = 0; } // shift window to the "left" if (lastCharOffset > doc.TextLength) { firstCharOffset -= (lastCharOffset - doc.TextLength); lastCharOffset = doc.TextLength; // compensate, in case "left" side of window is in the void if (firstCharOffset < 0) { firstCharOffset = 0; } } //if (doc.TextLength < firstCharOffset + numberOfCharactersAroundCursorToResolve) // numberOfCharactersAroundCursorToResolve = doc.TextLength - firstCharOffset; //string surroundingText = doc.GetText(firstCharOffset, numberOfCharactersAroundCursorToResolve); //Debug.WriteLine("Text around cursor: [{0}]", surroundingText); result.CompletionContextBefore = doc.GetText(firstCharOffset, textCursorOffset - firstCharOffset); result.CompletionContextAfter = doc.GetText(textCursorOffset, lastCharOffset - textCursorOffset); Debug.WriteLine("Completion Context: \"{0}\" <cursor> \"{1}\"", result.CompletionContextBefore, result.CompletionContextAfter); #endregion }
void RenameAllReferences(List<SearchResultMatch> references) { SearchResultMatch firstResult = references.First(); var document = new ReadOnlyDocument(SD.FileService.GetFileContent(firstResult.FileName)); string name = document.GetText(firstResult.StartOffset, firstResult.Length); string newName = GetNewName(name); if (ShouldRenameReference(newName, name)) { ApplyRenames(references, newName); } }
void RenameAllReferences(List <SearchResultMatch> references) { SearchResultMatch firstResult = references.First(); var document = new ReadOnlyDocument(SD.FileService.GetFileContent(firstResult.FileName)); string name = document.GetText(firstResult.StartOffset, firstResult.Length); string newName = GetNewName(name); if (ShouldRenameReference(newName, name)) { ApplyRenames(references, newName); } }
void CheckWhitespace(AstNode startNode, TextLocation whitespaceStart, AstNode endNode, TextLocation whitespaceEnd) { if (whitespaceStart == whitespaceEnd || startNode == endNode) { return; } int start = currentDocument.GetOffset(whitespaceStart.Line, whitespaceStart.Column); int end = currentDocument.GetOffset(whitespaceEnd.Line, whitespaceEnd.Column); string text = currentDocument.GetText(start, end - start); bool assertion = string.IsNullOrWhiteSpace(text); if (!assertion) { if (startNode.Parent != endNode.Parent) { PrintNode(startNode.Parent); } PrintNode(endNode.Parent); } Assert.IsTrue(assertion, "Expected whitespace between " + startNode.GetType() + ":" + whitespaceStart + " and " + endNode.GetType() + ":" + whitespaceEnd + ", but got '" + text + "' (in " + currentFileName + " parent:" + startNode.Parent.GetType() + ")"); }
//// todo: eliminate this method later //private static void AugmentProjectModelWithAlgorithmBase(ProjectModel model) //{ // model.Children.Add(QcAlgorithmFileModel.Value); //} public static ProjectAnalysisResult RunFullProjectAnalysis(ProjectAnalysisRequest projectAnalysisRequest) { Stopwatch sw = Stopwatch.StartNew(); ProjectAnalysisResult projectAnalysisResult = new ProjectAnalysisResult(); ProjectModel projectModel = projectAnalysisRequest.ProjectModel; // todo: Ask Jared why QCAlgorithm is a partial class and why it's not included in "common" #if false // ************************************************************************************ // NOTE: In order to get this project building cleanly, with minimal dependencies // before checking into Github, I've removed all hard QuantConnect dependencies, // including the QCAlgorithm.cs embedded resource and the assembly references to // QuantConnect.Algorithm.Interface // Augment the project model with the QCAgorithm base class // projectModel.Children.Add(QcAlgorithmFileModel.Value); // ************************************************************************************ #endif // Set up the project (if not already done) if (projectModel.ProjectContent == null) { projectModel.ProjectContent = new CSharpProjectContent(); projectModel.ProjectContent = projectModel.ProjectContent.AddAssemblyReferences(QCReferences.Value); } // For each new file, we need to integrate it into the project content var fileModelsInProject = projectModel.GetFileDescendants().ToArray(); foreach (var fileModelInProject in fileModelsInProject) { IntegrateFileModel(projectModel, fileModelInProject); } // We can return now if no code completion was requested if (projectAnalysisRequest.CodeCompletionParameters == null) { projectAnalysisResult.TimeElapsed = sw.Elapsed; return(projectAnalysisResult); } // Now it's time to give attention specifically to the matter of resolving the code completion // options. This, of course, requires a deeper analysis of the specified file... var codeCompletionParams = projectAnalysisRequest.CodeCompletionParameters; // Locate the file in the project ProjectFileModel fileModel = projectModel.FindFile(codeCompletionParams.FileId); if (fileModel == null) { throw new Exception("Specified file does not exist in this project"); } // Create a TypeSystem.ICompilation that allows resolving within the project. var compilation = projectModel.ProjectContent.CreateCompilation(); #region Resolve text cursor/caret location // The text cursor position is crucial to creating a properly-scoped type resolution context // so as to get relevant code completion suggestions. int textCursorOffset = 0; TextLocation textCursorLocation = new TextLocation(1, 1); ReadOnlyDocument doc = new ReadOnlyDocument(fileModel.Content); try { // if line and column aren't set, we'll assume that the cursor offset/index is set if (codeCompletionParams.Line == 0 && codeCompletionParams.Column == 0) { textCursorOffset = codeCompletionParams.Offset; if (textCursorOffset < 0) { textCursorOffset = 0; } textCursorLocation = doc.GetLocation(textCursorOffset); } // if either line or column are invalid (i.e. <= 0), then we'll use offset 0 instead else if (codeCompletionParams.Line <= 0 || codeCompletionParams.Column <= 0) { textCursorOffset = 0; } else { textCursorLocation = new TextLocation(codeCompletionParams.Line, codeCompletionParams.Column); textCursorOffset = doc.GetOffset(textCursorLocation); codeCompletionParams.Offset = textCursorOffset; } } catch (Exception) { textCursorOffset = 0; textCursorLocation = new TextLocation(1, 1); } finally { projectAnalysisResult.Line = textCursorLocation.Line; projectAnalysisResult.Column = textCursorLocation.Column; projectAnalysisResult.Offset = textCursorOffset; } #endregion #region Create and Refine the type resolution context as much as possible based upon the cursor position var typeResolveContext = new CSharpTypeResolveContext(compilation.MainAssembly); // Constrain the resolve context by using scope typeResolveContext = typeResolveContext .WithUsingScope(fileModel.UnresolvedFile.GetUsingScope(textCursorLocation) .Resolve(compilation)); var curDef = fileModel.UnresolvedFile.GetInnermostTypeDefinition(textCursorLocation); if (curDef != null) { var resolvedDef = curDef.Resolve(typeResolveContext).GetDefinition(); typeResolveContext = typeResolveContext.WithCurrentTypeDefinition(resolvedDef); var curMember = resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= textCursorLocation && textCursorLocation < m.BodyRegion.End); if (curMember != null) { typeResolveContext = typeResolveContext.WithCurrentMember(curMember); } } #endregion // The purpose of the rest of these steps is a little fuzzy in my mind... // I'm still trying to understand them fully and if/why they're all needed. // It seems there is some redundancy here... var completionContext = new DefaultCompletionContextProvider(doc, fileModel.UnresolvedFile); #region Add Preprocessor Symbols?? completionContext.AddSymbol("TEST"); foreach (var sym in fileModel.SyntaxTree.ConditionalSymbols) { completionContext.AddSymbol(sym); } #endregion var completionDataFactory = new CodeCompletionDataFactory(new CSharpResolver(typeResolveContext)); var completionEngine = new CSharpCompletionEngine(doc, completionContext, completionDataFactory, projectModel.ProjectContent, typeResolveContext); completionEngine.EolMarker = Environment.NewLine; completionEngine.FormattingPolicy = FormattingOptionsFactory.CreateMono(); projectModel.CompletionEngine = completionEngine; // Attach contextual info to analysis result GetDocumentContext(projectAnalysisResult, textCursorOffset, doc); // Finally, generate completion data! var completionOptions = completionEngine.GetCompletionData(textCursorOffset, projectAnalysisRequest.CodeCompletionParameters.CtrlSpace).ToArray(); projectAnalysisResult.CompletionOptions = completionOptions.OrderBy(x => x.CompletionText).ToArray(); projectAnalysisResult.AutoCompleteEmptyMatch = completionEngine.AutoCompleteEmptyMatch; projectAnalysisResult.AutoSelect = completionEngine.AutoSelect; projectAnalysisResult.DefaultCompletionString = completionEngine.DefaultCompletionString; int startPos, wordLength; if (completionEngine.TryGetCompletionWord(textCursorOffset, out startPos, out wordLength)) { //Debug.WriteLine("TryGetCompletionWord :: startpos:{0} wordlength:{1}", startPos, wordLength); string completionWord = projectAnalysisResult.CompletionWord = doc.GetText(startPos, wordLength); if (!string.IsNullOrWhiteSpace(completionWord)) { var bestMatch = projectAnalysisResult.CompletionOptions .FirstOrDefault(x => x.CompletionText.CompareTo(completionWord) >= 0); projectAnalysisResult.BestMatchToCompletionWord = bestMatch; //if (bestMatch != null) //projectAnalysisResult.BestMatchToCompletionWord = bestMatch.CompletionText; } } projectAnalysisResult.TimeElapsed = sw.Elapsed; return(projectAnalysisResult); }
public static ProjectAnalysisResult RunFullProjectAnalysis(ProjectAnalysisRequest projectAnalysisRequest) // ProjectModel projectModel, int fileId, int line, int column) { Stopwatch sw = Stopwatch.StartNew(); ProjectAnalysisResult projectAnalysisResult = new ProjectAnalysisResult(); ProjectModel projectModel = projectAnalysisRequest.ProjectModel; // Set up the project (if not already done) if (projectModel.ProjectContent == null) { projectModel.ProjectContent = new CSharpProjectContent(); projectModel.ProjectContent = projectModel.ProjectContent.AddAssemblyReferences(QCReferences.Value); } // For each new file, we need to integrate it into the project content var fileModelsInProject = projectModel.GetFileDescendants().ToArray(); foreach (var fileModelInProject in fileModelsInProject) { IntegrateFileModel(projectModel, fileModelInProject); } // We can return now if no code completion was requested if (projectAnalysisRequest.CodeCompletionParameters == null) { projectAnalysisResult.TimeElapsed = sw.Elapsed; return(projectAnalysisResult); } // Now it's time to give attention specifically to the matter of resolving the code completion // options. This, of course, requires a deeper analysis of the specified file... var codeCompletionParams = projectAnalysisRequest.CodeCompletionParameters; // Locate the file in the project ProjectFileModel fileModel = projectModel.FindFile(codeCompletionParams.FileId); if (fileModel == null) { throw new Exception("Specified file does not exist in this project"); } // Create a TypeSystem.ICompilation that allows resolving within the project. var compilation = projectModel.ProjectContent.CreateCompilation(); #region Resolve text cursor/caret location // The text cursor position is crucial to creating a properly-scoped type resolution context // so as to get relevant code completion suggestions. int textCursorOffset; TextLocation textCursorLocation; ReadOnlyDocument doc = new ReadOnlyDocument(fileModel.Content); if (codeCompletionParams.Line == 0 && codeCompletionParams.Column == 0) { textCursorOffset = codeCompletionParams.Offset; textCursorLocation = doc.GetLocation(textCursorOffset); codeCompletionParams.Line = textCursorLocation.Line; codeCompletionParams.Column = textCursorLocation.Column; } else { textCursorLocation = new TextLocation(codeCompletionParams.Line, codeCompletionParams.Column); textCursorOffset = doc.GetOffset(textCursorLocation); codeCompletionParams.Offset = textCursorOffset; } #endregion #region Create and Refine the type resolution context as much as possible based upon the cursor position var typeResolveContext = new CSharpTypeResolveContext(compilation.MainAssembly); // Constrain the resolve context by using scope typeResolveContext = typeResolveContext .WithUsingScope(fileModel.UnresolvedFile.GetUsingScope(textCursorLocation) .Resolve(compilation)); var curDef = fileModel.UnresolvedFile.GetInnermostTypeDefinition(textCursorLocation); if (curDef != null) { var resolvedDef = curDef.Resolve(typeResolveContext).GetDefinition(); typeResolveContext = typeResolveContext.WithCurrentTypeDefinition(resolvedDef); var curMember = resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= textCursorLocation && textCursorLocation < m.BodyRegion.End); if (curMember != null) { typeResolveContext = typeResolveContext.WithCurrentMember(curMember); } } #endregion // The purpose of the rest of these steps is a little fuzzy in my mind... // I'm still trying to understand them fully and if/why they're all needed. // It seems there is some redundancy here... var completionContext = new DefaultCompletionContextProvider(doc, fileModel.UnresolvedFile); #region Add Preprocessor Symbols?? completionContext.AddSymbol("TEST"); foreach (var sym in fileModel.SyntaxTree.ConditionalSymbols) { completionContext.AddSymbol(sym); } #endregion var completionDataFactory = new TestCompletionDataFactory(new CSharpResolver(typeResolveContext)); var completionEngine = new CSharpCompletionEngine(doc, completionContext, completionDataFactory, projectModel.ProjectContent, typeResolveContext); completionEngine.EolMarker = Environment.NewLine; completionEngine.FormattingPolicy = FormattingOptionsFactory.CreateMono(); projectModel.CompletionEngine = completionEngine; #region Debugging Aid // Resolve content around text cursor int numberOfCharactersAroundCursorToResolve = 20; int firstCharOffset = textCursorOffset - numberOfCharactersAroundCursorToResolve / 2; if (firstCharOffset < 0) { firstCharOffset = 0; } if (doc.TextLength < firstCharOffset + numberOfCharactersAroundCursorToResolve) { numberOfCharactersAroundCursorToResolve = doc.TextLength - firstCharOffset; } string surroundingText = doc.GetText(firstCharOffset, numberOfCharactersAroundCursorToResolve); Debug.WriteLine("Text around cursor: [{0}]", surroundingText); #endregion // Finally, generate completion data! var completionOptions = completionEngine.GetCompletionData(textCursorOffset, projectAnalysisRequest.CodeCompletionParameters.CtrlSpace).ToArray(); projectAnalysisResult.CompletionOptions = completionEngine.GetCompletionData(textCursorOffset, projectAnalysisRequest.CodeCompletionParameters.CtrlSpace).ToArray(); projectAnalysisResult.AutoCompleteEmptyMatch = completionEngine.AutoCompleteEmptyMatch; projectAnalysisResult.AutoSelect = completionEngine.AutoSelect; projectAnalysisResult.DefaultCompletionString = completionEngine.DefaultCompletionString; int startPos, wordLength; if (completionEngine.TryGetCompletionWord(textCursorOffset, out startPos, out wordLength)) { Debug.WriteLine("TryGetCompletionWord :: startpos:{0} wordlength:{1}", startPos, wordLength); projectAnalysisResult.CompletionWord = doc.GetText(startPos, wordLength); } projectAnalysisResult.TimeElapsed = sw.Elapsed; return(projectAnalysisResult); }
/// <summary> /// Gets the project resource from the specified expression. /// </summary> public ProjectResourceInfo GetProjectResource(CodePropertyReferenceExpression propRef) { CodeTypeReferenceExpression typeRef = propRef.TargetObject as CodeTypeReferenceExpression; if (typeRef == null) { LoggingService.Info("Target of possible project resources property reference is not a type reference, but " + propRef.TargetObject.ToString() + "."); return(null); } ICompilation compilation = SD.ParserService.GetCompilation(project); // Get the (generated) class where the resource is defined. var resourceClass = compilation.FindType(new FullTypeName(typeRef.Type.BaseType)).GetDefinition(); if (resourceClass == null) { throw new InvalidOperationException("Could not find class for project resources: '" + typeRef.Type.BaseType + "'."); } if (resourceClass.Region.IsEmpty) { return(null); } // Make sure the class we have found is a generated resource class. if (!IsGeneratedResourceClass(resourceClass)) { return(null); } // Get the name of the resource file based on the file that contains the generated class. string resourceFileName = Path.GetFileNameWithoutExtension(resourceClass.Region.FileName); if (resourceFileName.EndsWith("Designer", StringComparison.OrdinalIgnoreCase)) { resourceFileName = Path.GetFileNameWithoutExtension(resourceFileName); } resourceFileName = Path.Combine(Path.GetDirectoryName(resourceClass.Region.FileName), resourceFileName); if (File.Exists(resourceFileName + ".resources")) { resourceFileName = resourceFileName + ".resources"; } else if (File.Exists(resourceFileName + ".resx")) { resourceFileName = resourceFileName + ".resx"; } else { throw new FileNotFoundException("Could not find the resource file for type '" + resourceClass.FullName + "'. Tried these file names: '" + resourceFileName + ".(resources|resx)'."); } // Get the property for the resource. IProperty prop = resourceClass.Properties.SingleOrDefault(p => p.Name == propRef.PropertyName); if (prop == null) { throw new InvalidOperationException("Property '" + propRef.PropertyName + "' not found in type '" + resourceClass.FullName + "'."); } if (!prop.CanGet) { throw new InvalidOperationException("Property '" + propRef.PropertyName + "' in type '" + resourceClass.FullName + "' does not have a getter."); } // Get the code of the resource class and // extract the resource key from the property. // This is necessary because the key may differ from the property name // if special characters are used. // It would be better if we could use a real code parser for this, but // that is not possible without getting dependent on the programming language. IDocument doc = new ReadOnlyDocument(SD.FileService.GetFileContent(resourceClass.Region.FileName)); int startOffset = doc.PositionToOffset(prop.Getter.BodyRegion.BeginLine, prop.Getter.BodyRegion.BeginColumn); int endOffset = doc.PositionToOffset(prop.Getter.BodyRegion.EndLine, prop.Getter.BodyRegion.EndColumn); string code = doc.GetText(startOffset, endOffset - startOffset + 1); int index = code.IndexOf("ResourceManager", StringComparison.Ordinal); if (index == -1) { throw new InvalidOperationException("No reference to ResourceManager found in property getter of '" + prop.FullName + "'. Code: '" + code + "'"); } index = code.IndexOf("Get", index, StringComparison.Ordinal); if (index == -1) { throw new InvalidOperationException("No call to Get... found in property getter of '" + prop.FullName + "'. Code: '" + code + "'"); } index = code.IndexOf(this.StringLiteralDelimiter, index + 1, StringComparison.Ordinal); if (index == -1) { throw new InvalidOperationException("No string delimiter ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullName + "'. Code: '" + code + "'"); } index += this.StringLiteralDelimiter.Length; int endIndex = code.LastIndexOf(this.StringLiteralDelimiter, StringComparison.Ordinal); if (endIndex == -1) { throw new InvalidOperationException("No string terminator ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullName + "'. Code: '" + code + "'"); } string resourceKey = code.Substring(index, endIndex - index); LoggingService.Debug("-> Decoded resource: In: " + resourceFileName + ". Key: " + resourceKey); return(new ProjectResourceInfo(new FileName(resourceFileName), resourceKey)); }
/// <summary> /// Gets the project resource from the specified expression. /// </summary> public ProjectResourceInfo GetProjectResource(CodePropertyReferenceExpression propRef) { CodeTypeReferenceExpression typeRef = propRef.TargetObject as CodeTypeReferenceExpression; if (typeRef == null) { LoggingService.Info("Target of possible project resources property reference is not a type reference, but " + propRef.TargetObject.ToString() + "."); return null; } ICompilation compilation = SD.ParserService.GetCompilation(project); // Get the (generated) class where the resource is defined. var resourceClass = compilation.FindType(new FullTypeName(typeRef.Type.BaseType)).GetDefinition(); if (resourceClass == null) { throw new InvalidOperationException("Could not find class for project resources: '" + typeRef.Type.BaseType + "'."); } if (resourceClass.Region.IsEmpty) return null; // Make sure the class we have found is a generated resource class. if (!IsGeneratedResourceClass(resourceClass)) { return null; } // Get the name of the resource file based on the file that contains the generated class. string resourceFileName = Path.GetFileNameWithoutExtension(resourceClass.Region.FileName); if (resourceFileName.EndsWith("Designer", StringComparison.OrdinalIgnoreCase)) { resourceFileName = Path.GetFileNameWithoutExtension(resourceFileName); } resourceFileName = Path.Combine(Path.GetDirectoryName(resourceClass.Region.FileName), resourceFileName); if (File.Exists(resourceFileName + ".resources")) { resourceFileName = resourceFileName + ".resources"; } else if (File.Exists(resourceFileName + ".resx")) { resourceFileName = resourceFileName + ".resx"; } else { throw new FileNotFoundException("Could not find the resource file for type '" + resourceClass.FullName + "'. Tried these file names: '" + resourceFileName + ".(resources|resx)'."); } // Get the property for the resource. IProperty prop = resourceClass.Properties.SingleOrDefault(p => p.Name == propRef.PropertyName); if (prop == null) { throw new InvalidOperationException("Property '" + propRef.PropertyName + "' not found in type '" + resourceClass.FullName + "'."); } if (!prop.CanGet) { throw new InvalidOperationException("Property '" + propRef.PropertyName + "' in type '" + resourceClass.FullName + "' does not have a getter."); } // Get the code of the resource class and // extract the resource key from the property. // This is necessary because the key may differ from the property name // if special characters are used. // It would be better if we could use a real code parser for this, but // that is not possible without getting dependent on the programming language. IDocument doc = new ReadOnlyDocument(SD.FileService.GetFileContent(resourceClass.Region.FileName)); int startOffset = doc.PositionToOffset(prop.Getter.BodyRegion.BeginLine, prop.Getter.BodyRegion.BeginColumn); int endOffset = doc.PositionToOffset(prop.Getter.BodyRegion.EndLine, prop.Getter.BodyRegion.EndColumn); string code = doc.GetText(startOffset, endOffset - startOffset + 1); int index = code.IndexOf("ResourceManager", StringComparison.Ordinal); if (index == -1) { throw new InvalidOperationException("No reference to ResourceManager found in property getter of '" + prop.FullName + "'. Code: '" + code + "'"); } index = code.IndexOf("Get", index, StringComparison.Ordinal); if (index == -1) { throw new InvalidOperationException("No call to Get... found in property getter of '" + prop.FullName + "'. Code: '" + code + "'"); } index = code.IndexOf(this.StringLiteralDelimiter, index + 1, StringComparison.Ordinal); if (index == -1) { throw new InvalidOperationException("No string delimiter ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullName + "'. Code: '" + code + "'"); } index += this.StringLiteralDelimiter.Length; int endIndex = code.LastIndexOf(this.StringLiteralDelimiter, StringComparison.Ordinal); if (endIndex == -1) { throw new InvalidOperationException("No string terminator ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullName + "'. Code: '" + code + "'"); } string resourceKey = code.Substring(index, endIndex - index); LoggingService.Debug("-> Decoded resource: In: " + resourceFileName + ". Key: " + resourceKey); return new ProjectResourceInfo(new FileName(resourceFileName), resourceKey); }