//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="filename">The filename to load</param> /// <param name="spellCheckConfiguration">The spell checker configuration for the file</param> public XmlClassifier(string filename, SpellCheckerConfiguration spellCheckConfiguration) : base(filename, spellCheckConfiguration) { try { // If an encoding is specified, re-read it using the correct encoding Match m = reXmlEncoding.Match(this.Text); if(m.Success) { var encoding = Encoding.GetEncoding(m.Groups["Encoding"].Value); if(encoding != Encoding.Default) { using(StreamReader sr = new StreamReader(filename, encoding, true)) { this.SetText(sr.ReadToEnd()); } } } } catch(Exception ex) { // Ignore errors for invalid encodings. We'll just use the default System.Diagnostics.Debug.WriteLine(ex); } }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="filename">The filename to load</param> /// <param name="spellCheckConfiguration">The spell checker configuration for the file</param> /// <param name="classifierConfiguration">The configuration element containing the classification /// expressions and their range types.</param> public CodeClassifier(string filename, SpellCheckerConfiguration spellCheckConfiguration, XElement classifierConfiguration) : base(filename, spellCheckConfiguration, classifierConfiguration) { xmlDocCommentDelimiter = (string)classifierConfiguration.Attribute("XmlDocCommentDelimiter"); quadSlashDelimiter = (string)classifierConfiguration.Attribute("QuadSlashDelimiter"); oldStyleDocCommentDelimiter = (string)classifierConfiguration.Attribute("OldStyleDocCommentDelimiter"); isCSharp = filename.EndsWith(".cs", StringComparison.OrdinalIgnoreCase); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="filename">The filename to use</param> /// <param name="defaultConfig">A default configuration to use for missing properties</param> public SpellingConfigurationFile(string filename, SpellCheckerConfiguration defaultConfig) { if (String.IsNullOrWhiteSpace(filename)) { throw new ArgumentNullException("filename", "Filename cannot be null or empty"); } try { // Get the property cache for finding current and default values propertyCache = new Dictionary <string, PropertyInfo>(); configCache = TypeDescriptor.GetProperties(typeof(SpellCheckerConfiguration)); csoCache = TypeDescriptor.GetProperties(typeof(CSharpOptions)); foreach (PropertyInfo property in typeof(SpellCheckerConfiguration).GetProperties( BindingFlags.Public | BindingFlags.Instance)) { propertyCache.Add(property.Name, property); } foreach (PropertyInfo property in typeof(CSharpOptions).GetProperties( BindingFlags.Public | BindingFlags.Instance)) { propertyCache.Add(property.Name, property); } } catch { // Ignore exceptions } this.Filename = filename; this.defaultConfig = defaultConfig; if (File.Exists(filename)) { document = XDocument.Load(filename); root = document.Root; // If it's an older configuration file, upgrade it to the new format if (root.Attribute("Format") == null || root.Attribute("Format").Value != AssemblyInfo.ConfigSchemaVersion) { this.UpgradeConfiguration(); } } else { root = new XElement("SpellCheckerConfiguration", new XAttribute("Format", AssemblyInfo.ConfigSchemaVersion)); document = new XDocument(new XComment(" Visual Studio Spell Checker configuration file - " + "[https://github.com/EWSoftware/VSSpellChecker]\r\n Do not edit the XML. Use the " + "configuration file editor in Visual Studio to modify the settings. "), root); } }
//===================================================================== /// <summary> /// This is used to get the classifier for the given file /// </summary> /// <param name="filename">The file for which to get a classifier</param> /// <param name="spellCheckConfiguration">The spell checker configuration that the classifier can use to /// determine what elements to return for spell checking if needed.</param> /// <returns>The classifier to use or null if the file should not be processed</returns> public static TextClassifier GetClassifier(string filename, SpellCheckerConfiguration spellCheckConfiguration) { ClassifierDefinition definition; TextClassifier classifier = null; string id, extension = Path.GetExtension(filename); if(extensionMap == null) LoadClassifierConfiguration(); if(!ignoredFilePatterns.Any(p => p.IsMatch(filename))) { if(!String.IsNullOrWhiteSpace(extension)) extension = extension.Substring(1); if(!extensionMap.TryGetValue(extension, out id)) id = FileIsXml(filename) ? "XML" : "PlainText"; if(id != "None" && definitions.TryGetValue(id, out definition)) switch(definition.ClassifierType) { case "PlainTextClassifier": classifier = new PlainTextClassifier(filename, spellCheckConfiguration); break; case "XmlClassifier": classifier = new XmlClassifier(filename, spellCheckConfiguration); break; case "ReportingServicesClassifier": classifier = new ReportingServicesClassifier(filename, spellCheckConfiguration); break; case "ResourceFileClassifier": classifier = new ResourceFileClassifier(filename, spellCheckConfiguration); break; case "HtmlClassifier": classifier = new HtmlClassifier(filename, spellCheckConfiguration); break; case "CodeClassifier": classifier = new CodeClassifier(filename, spellCheckConfiguration, definition.Configuration); break; case "RegexClassifier": classifier = new RegexClassifier(filename, spellCheckConfiguration, definition.Configuration); break; default: break; } } return classifier; }
//===================================================================== /// <summary> /// Constructor /// </summary> /// /// <param name="filename">The filename to load</param> /// <param name="spellCheckConfiguration">The spell checker configuration for the file</param> /// <param name="classifierConfiguration">The configuration element containing the classification /// expressions and their range types.</param> public RegexClassifier(string filename, SpellCheckerConfiguration spellCheckConfiguration, XElement classifierConfiguration) : base(filename, spellCheckConfiguration) { string expression, options; RangeClassification classification; RegexOptions regexOptions; expressions = new List<RegexClassification>(); if(classifierConfiguration != null) foreach(XElement match in classifierConfiguration.Elements("Match")) { expression = (string)match.Attribute("Expression"); if(!String.IsNullOrWhiteSpace(expression)) { options = (string)match.Attribute("Options"); if(String.IsNullOrWhiteSpace(options) || !Enum.TryParse<RegexOptions>(options, true, out regexOptions)) regexOptions = RegexOptions.None; if(!Enum.TryParse<RangeClassification>((string)match.Attribute("Classification"), out classification)) classification = RangeClassification.PlainText; try { // Enforce a 1 second timeout on all expressions. If we can't get a match within // that amount of time, ignore it. This can happen on some files with odd formatting. expressions.Add(new RegexClassification(new Regex(expression, regexOptions, TimeSpan.FromSeconds(1)), classification)); } catch(ArgumentException ex) { // Ignore invalid regular expression entries System.Diagnostics.Debug.WriteLine(ex); } } } }
/// <summary> /// Constructor /// </summary> /// <param name="filename">The filename to load</param> /// <param name="spellCheckConfiguration">The spell checker configuration for the file</param> public PlainTextClassifier(string filename, SpellCheckerConfiguration spellCheckConfiguration) : base(filename, spellCheckConfiguration) { }
//===================================================================== /// <summary> /// Generate the configuration to use when spell checking the given text buffer /// </summary> /// <param name="buffer">The text buffer for which to generate a configuration</param> /// <returns>The generated configuration to use</returns> /// <remarks>The configuration is a merger of the global settings plus any solution, project, folder, and /// file settings related to the text buffer.</remarks> private SpellCheckerConfiguration GenerateConfiguration(ITextBuffer buffer) { ProjectItem projectItem, fileItem; string bufferFilename, filename, projectPath, projectFilename = null; // Start with the global configuration var config = new SpellCheckerConfiguration(); try { config.Load(SpellingConfigurationFile.GlobalConfigurationFilename); var dte2 = (globalServiceProvider == null) ? null : globalServiceProvider.GetService(typeof(SDTE)) as DTE2; if(dte2 != null && dte2.Solution != null && !String.IsNullOrWhiteSpace(dte2.Solution.FullName)) { var solution = dte2.Solution; // Clear the global dictionary cache when a change in solution is detected. This handles // cases where only the MEF components are loaded and not the package (i.e. a configuration // has not been edited). See VSSpellCheckerPackage.solutionEvents_AfterClosing(). if(lastSolutionName == null || !lastSolutionName.Equals(solution.FullName, StringComparison.OrdinalIgnoreCase)) { GlobalDictionary.ClearDictionaryCache(); lastSolutionName = solution.FullName; } // See if there is a solution configuration filename = solution.FullName + ".vsspell"; projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); // Find the project item for the file we are opening bufferFilename = buffer.GetFilename(); projectItem = (bufferFilename != null) ? solution.FindProjectItem(bufferFilename) : null; if(projectItem != null) { fileItem = projectItem; // If we have a project (we should), see if it has settings if(projectItem.ContainingProject != null && !String.IsNullOrWhiteSpace(projectItem.ContainingProject.FullName)) { projectFilename = projectItem.ContainingProject.FullName; filename = projectFilename + ".vsspell"; projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); // Get the full path based on the project. The buffer filename will refer to the actual // path which may be to a linked file outside the project's folder structure. projectPath = Path.GetDirectoryName(filename); filename = Path.GetDirectoryName((string)fileItem.Properties.Item("FullPath").Value); // Search for folder-specific configuration files if(filename.StartsWith(projectPath, StringComparison.OrdinalIgnoreCase)) { // Then check subfolders. No need to check the root folder as the project // settings cover it. if(filename.Length > projectPath.Length) foreach(string folder in filename.Substring(projectPath.Length + 1).Split('\\')) { projectPath = Path.Combine(projectPath, folder); filename = Path.Combine(projectPath, folder + ".vsspell"); projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); } } // If the item looks like a dependent file item, look for a settings file related to // the parent file item. if(fileItem.Collection != null && fileItem.Collection.Parent != null) { projectItem = fileItem.Collection.Parent as ProjectItem; if(projectItem != null && projectItem.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFile) { filename = (string)projectItem.Properties.Item("FullPath").Value + ".vsspell"; projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); } } // And finally, look for file-specific settings for the item itself filename = (string)fileItem.Properties.Item("FullPath").Value + ".vsspell"; projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); } else if(projectItem.Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems) { // Looks like a solution item, see if a related setting file exists filename = bufferFilename + ".vsspell"; projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); } } // Load code analysis dictionaries if wanted if(projectFilename != null && config.CadOptions.ImportCodeAnalysisDictionaries) { // I'm not sure if there's a better way to do this but it does seem to work. We need to // find one or more arbitrary files with an item type of "CodeAnalysisDictionary". We // do so by getting the MSBuild project from the global project collection and using its // GetItems() method to find them. var loadedProject = Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.GetLoadedProjects( projectFilename).FirstOrDefault(); if(loadedProject != null) { // Typically there is only one but multiple files are supported foreach(var cad in loadedProject.GetItems("CodeAnalysisDictionary")) { filename = Path.Combine(Path.GetDirectoryName(projectFilename), cad.EvaluatedInclude); if(File.Exists(filename)) config.ImportCodeAnalysisDictionary(filename); } } } if(bufferFilename != null && config.DetermineResourceFileLanguageFromName && Path.GetExtension(bufferFilename).Equals(".resx", StringComparison.OrdinalIgnoreCase)) { // Localized resource files are expected to have filenames in the format // BaseName.Language.resx (i.e. LocalizedForm.de-DE.resx). bufferFilename = Path.GetExtension(Path.GetFileNameWithoutExtension(bufferFilename)); if(bufferFilename.Length > 1) { bufferFilename = bufferFilename.Substring(1); SpellCheckerDictionary match; if(SpellCheckerDictionary.AvailableDictionaries( config.AdditionalDictionaryFolders).TryGetValue(bufferFilename, out match)) { // Clear any existing dictionary languages and use just the one that matches the // file's language. config.DictionaryLanguages.Clear(); config.DictionaryLanguages.Add(match.Culture); } } } } else if(lastSolutionName != null) { // A solution was closed and a file has been opened outside of a solution so clear the // cache and use the global dictionaries. GlobalDictionary.ClearDictionaryCache(); lastSolutionName = null; } } catch(Exception ex) { // Ignore errors, we just won't load the configurations after the point of failure System.Diagnostics.Debug.WriteLine(ex); } return config; }
/// <summary> /// This is used to generate the configuration for the instance /// </summary> /// <returns>The configuration to use or null if the file should not be spell checked (disabled or not a /// type of file that can be spell checked such as a binary file).</returns> public SpellCheckerConfiguration GenerateConfiguration(IEnumerable<string> codeAnalysisFiles) { string configFile; var config = new SpellCheckerConfiguration(); try { // Start with the global configuration and work on down config.Load(SpellingConfigurationFile.GlobalConfigurationFilename); if(this.HasSolutionConfigurationFile) { configFile = this.SolutionFile + ".vsspell"; if(File.Exists(configFile)) config.Load(configFile); } if(this.HasProjectConfigurationFile) { configFile = this.ProjectFile + ".vsspell"; if(File.Exists(configFile)) config.Load(configFile); } if(this.FolderConfigurationFiles.Any()) foreach(string cf in this.FolderConfigurationFiles) if(File.Exists(cf)) config.Load(cf); if(this.DependencyConfigurationFile != null && File.Exists(this.DependencyConfigurationFile)) config.Load(this.DependencyConfigurationFile); if(this.HasRelatedConfigurationFile) { configFile = this.CanonicalName + ".vsspell"; if(File.Exists(configFile)) config.Load(configFile); } // Merge any code analysis dictionary settings if(codeAnalysisFiles != null) foreach(string cad in codeAnalysisFiles) if(File.Exists(cad)) config.ImportCodeAnalysisDictionary(cad); // If wanted, set the language based on the resource filename if(config.DetermineResourceFileLanguageFromName && Path.GetExtension(this.Filename).Equals(".resx", StringComparison.OrdinalIgnoreCase)) { // Localized resource files are expected to have filenames in the format // BaseName.Language.resx (i.e. LocalizedForm.de-DE.resx). string ext = Path.GetExtension(Path.GetFileNameWithoutExtension(this.Filename)); if(ext.Length > 1) { ext = ext.Substring(1); SpellCheckerDictionary match; if(SpellCheckerDictionary.AvailableDictionaries( config.AdditionalDictionaryFolders).TryGetValue(ext, out match)) { // Clear any existing dictionary languages and use just the one that matches the // file's language. config.DictionaryLanguages.Clear(); config.DictionaryLanguages.Add(match.Culture); } } } } catch(Exception ex) { // Ignore errors, we just won't load the configurations after the point of failure System.Diagnostics.Debug.WriteLine(ex); } return (!config.IncludeInProjectSpellCheck || config.IsExcludedByExtension(Path.GetExtension(this.Filename)) || IsBinaryFile(this.CanonicalName)) ? null : config; }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="filename">The filename to load</param> /// <param name="spellCheckConfiguration">The spell checker configuration for the file</param> public ResourceFileClassifier(string filename, SpellCheckerConfiguration spellCheckConfiguration) : base(filename, spellCheckConfiguration) { }
/// <summary> /// Generate the configuration for all parent items /// </summary> /// <returns>The generated configuration to use</returns> /// <remarks>The configuration is a merger of the global settings plus any solution, project, and folder /// settings related to but excluding the current configuration file. This allows us to determine the /// inherited additional dictionary folders to use.</remarks> private SpellCheckerConfiguration GenerateParentConfiguration() { ProjectItem projectItem, fileItem; string filename, projectPath; // Start with the global configuration var config = new SpellCheckerConfiguration(); if(isGlobal) return config; try { config.Load(SpellingConfigurationFile.GlobalConfigurationFilename); if(configType == ConfigurationType.Solution) return config; var dte2 = Utility.GetServiceFromPackage<DTE2, SDTE>(true); if(dte2 != null && dte2.Solution != null && !String.IsNullOrWhiteSpace(dte2.Solution.FullName)) { var solution = dte2.Solution; // See if there is a solution configuration filename = solution.FullName + ".vsspell"; projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); if(configType == ConfigurationType.Project) return config; // Find the project item for the file we are opening if(configType != ConfigurationType.Folder) projectItem = solution.FindProjectItem(relatedFilename); else projectItem = solution.FindProjectItem(Path.Combine(relatedFilename, relatedFilename + ".vsspell")); if(projectItem != null) { fileItem = projectItem; // If we have a project (we should), see if it has settings if(projectItem.ContainingProject != null && !String.IsNullOrWhiteSpace(projectItem.ContainingProject.FullName)) { filename = projectItem.ContainingProject.FullName + ".vsspell"; projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); // Get the full path based on the project. The configuration filename will refer to // the actual path which may be to a linked file outside the project's folder // structure. projectPath = Path.GetDirectoryName(filename); filename = Path.GetDirectoryName((string)fileItem.Properties.Item("FullPath").Value); // Search for folder-specific configuration files if(filename.StartsWith(projectPath, StringComparison.OrdinalIgnoreCase)) { // Then check subfolders. No need to check the root folder as the project // settings cover it. if(filename.Length > projectPath.Length) foreach(string folder in filename.Substring(projectPath.Length + 1).Split('\\')) { projectPath = Path.Combine(projectPath, folder); filename = Path.Combine(projectPath, folder + ".vsspell"); if(configType == ConfigurationType.Folder && Path.GetFileNameWithoutExtension(filename) == relatedFilename) return config; projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); } } // If the item looks like a dependent file item, look for a settings file related to // the parent file item. if(fileItem.Collection != null && fileItem.Collection.Parent != null) { projectItem = fileItem.Collection.Parent as ProjectItem; if(projectItem != null && projectItem.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFile) { filename = (string)projectItem.Properties.Item("FullPath").Value + ".vsspell"; projectItem = solution.FindProjectItem(filename); if(projectItem != null) config.Load(filename); } } } } } } catch(Exception ex) { // Ignore errors, we just won't load the configurations after the point of failure System.Diagnostics.Debug.WriteLine(ex); } return config; }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="filename">The filename to load</param> /// <param name="spellCheckConfiguration">The spell checker configuration for the file</param> public ReportingServicesClassifier(string filename, SpellCheckerConfiguration spellCheckConfiguration) : base(filename, spellCheckConfiguration) { }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="filename">The filename to load</param> /// <param name="spellCheckConfiguration">The spell checker configuration for the file</param> protected TextClassifier(string filename, SpellCheckerConfiguration spellCheckConfiguration) { this.Filename = filename; this.SpellCheckConfiguration = spellCheckConfiguration; using(StreamReader sr = new StreamReader(filename, Encoding.Default, true)) { this.SetText(sr.ReadToEnd()); } }