public IEnumerable<TextChangeEventArgs> GetChangesTo(ITextBufferVersion other) { SnapshotVersion otherVersion = other as SnapshotVersion; if (otherVersion == null) throw new ArgumentException("Does not belong to same document"); return checkpoint.GetChangesTo(otherVersion.checkpoint).Select(c => new TextChangeEventArgs(c.Offset, c.RemovedText, c.InsertedText)); }
/// <summary> /// Parse the given text and enter read lock. /// No parsing is done if the text is older than seen before. /// </summary> public IDisposable ParseAndLock(ITextBuffer fileContent) { // Copy to ensure thread-safety var lastVer = this.lastParsedVersion; if (lastVer == null || // First parse fileContent.Version == null || // Versioning not supported !fileContent.Version.BelongsToSameDocumentAs(lastVer) || // Different document instance? Can happen after closing and reopening of file. fileContent.Version.CompareAge(lastVer) > 0) // Is fileContent newer? { parser.Lock.EnterWriteLock(); // Double check, now that we are thread-safe lastVer = this.lastParsedVersion; if (lastVer == null || fileContent.Version == null || !fileContent.Version.BelongsToSameDocumentAs(lastVer)) { // First parse or versioning not supported using (DebugTimer.Time("normal parse")) parser.Parse(fileContent.Text, null); this.lastParsedVersion = fileContent.Version; } else if (fileContent.Version.CompareAge(lastParsedVersion) > 0) { // Incremental parse var changes = lastParsedVersion.GetChangesTo(fileContent.Version). Select(c => new DocumentChangeEventArgs(c.Offset, c.RemovedText, c.InsertedText)); using (DebugTimer.Time("incremental parse")) parser.Parse(fileContent.Text, changes); this.lastParsedVersion = fileContent.Version; } else { // fileContent is older - no need to parse } parser.Lock.EnterReadLock(); parser.Lock.ExitWriteLock(); } else { // fileContent is older - no need to parse parser.Lock.EnterReadLock(); } return new CallbackOnDispose(() => parser.Lock.ExitReadLock()); }
public int CompareAge(ITextBufferVersion other) { SnapshotVersion otherVersion = other as SnapshotVersion; if (otherVersion == null) throw new ArgumentException("Does not belong to same document"); return checkpoint.CompareAge(otherVersion.checkpoint); }
public int MoveOffsetTo(ITextBufferVersion other, int oldOffset, AnchorMovementType movement) { SnapshotVersion otherVersion = other as SnapshotVersion; if (otherVersion == null) throw new ArgumentException("Does not belong to same document"); switch (movement) { case AnchorMovementType.AfterInsertion: return checkpoint.MoveOffsetTo(otherVersion.checkpoint, oldOffset, ICSharpCode.AvalonEdit.Document.AnchorMovementType.AfterInsertion); case AnchorMovementType.BeforeInsertion: return checkpoint.MoveOffsetTo(otherVersion.checkpoint, oldOffset, ICSharpCode.AvalonEdit.Document.AnchorMovementType.BeforeInsertion); default: throw new NotSupportedException(); } }
/// <summary> /// Parse the given text and enter read lock. /// No parsing is done if the text is older than seen before. /// </summary> public IDisposable ParseAndLock(ITextBuffer fileContent) { // Copy to ensure thread-safety var lastVer = this.lastParsedVersion; if (lastVer == null || // First parse fileContent.Version == null || // Versioning not supported !fileContent.Version.BelongsToSameDocumentAs(lastVer) || // Different document instance? Can happen after closing and reopening of file. fileContent.Version.CompareAge(lastVer) > 0) // Is fileContent newer? { parser.Lock.EnterWriteLock(); // Double check, now that we are thread-safe lastVer = this.lastParsedVersion; if (lastVer == null || fileContent.Version == null || !fileContent.Version.BelongsToSameDocumentAs(lastVer)) { // First parse or versioning not supported using (DebugTimer.Time("normal parse")) parser.Parse(fileContent.Text, null); this.lastParsedVersion = fileContent.Version; } else if (fileContent.Version.CompareAge(lastParsedVersion) > 0) { // Incremental parse var changes = lastParsedVersion.GetChangesTo(fileContent.Version). Select(c => new DocumentChangeEventArgs(c.Offset, c.RemovedText, c.InsertedText)); using (DebugTimer.Time("incremental parse")) parser.Parse(fileContent.Text, changes); this.lastParsedVersion = fileContent.Version; } else { // fileContent is older - no need to parse } parser.Lock.EnterReadLock(); parser.Lock.ExitWriteLock(); } else { // fileContent is older - no need to parse parser.Lock.EnterReadLock(); } return(new CallbackOnDispose(() => parser.Lock.ExitReadLock())); }
public void Clear() { ParseInformation parseInfo; ICompilationUnit[] oldUnits; lock (this) { // by setting the disposed flag, we'll cause all running ParseFile() calls to return null and not // call into the parser anymore, so we can do the remainder of the clean-up work outside the lock this.disposed = true; parseInfo = this.parseInfo; oldUnits = this.oldUnits; this.oldUnits = null; this.bufferVersion = null; this.parseInfo = null; } foreach (ICompilationUnit oldUnit in oldUnits) { oldUnit.ProjectContent.RemoveCompilationUnit(oldUnit); bool isPrimary = parseInfo != null && parseInfo.CompilationUnit == oldUnit; RaiseParseInformationUpdated(new ParseInformationEventArgs(fileName, oldUnit.ProjectContent, oldUnit, null, isPrimary)); } }
public bool BelongsToSameDocumentAs(ITextBufferVersion other) { SnapshotVersion otherVersion = other as SnapshotVersion; return(otherVersion != null && checkpoint.BelongsToSameDocumentAs(otherVersion.checkpoint)); }
public Snapshot(ITextSource textSource, ChangeTrackingCheckpoint checkpoint) : base(textSource) { this.version = new SnapshotVersion(checkpoint); }
public bool BelongsToSameDocumentAs(ITextBufferVersion other) { SnapshotVersion otherVersion = other as SnapshotVersion; return otherVersion != null && checkpoint.BelongsToSameDocumentAs(otherVersion.checkpoint); }
public void Clear() { ICompilationUnit[] oldUnits; lock (this) { // by setting the disposed flag, we'll cause all running ParseFile() calls to return null and not // call into the parser anymore, so we can do the remainder of the clean-up work outside the lock this.disposed = true; oldUnits = this.oldUnits; this.oldUnits = null; this.bufferVersion = null; } foreach (ICompilationUnit oldUnit in oldUnits) { oldUnit.ProjectContent.RemoveCompilationUnit(oldUnit); RaiseParseInformationUpdated(new ParseInformationEventArgs(fileName, oldUnit.ProjectContent, oldUnit, null)); } }
public ParseInformation ParseFile(IProjectContent parentProjectContent, ITextBuffer fileContent) { if (parser == null) return null; if (fileContent == null) { // GetParseableFileContent must not be called inside any lock // (otherwise we'd risk deadlocks because GetParseableFileContent must invoke on the main thread) fileContent = GetParseableFileContent(fileName); } ITextBufferVersion fileContentVersion = fileContent.Version; List<IProjectContent> projectContents; lock (this) { if (this.disposed) return null; if (fileContentVersion != null && this.bufferVersion != null && this.bufferVersion.BelongsToSameDocumentAs(fileContentVersion)) { if (this.bufferVersion.CompareAge(fileContentVersion) >= 0) { // Special case: (necessary due to parentProjectContent optimization) // Detect when a file belongs to multiple projects but the ParserService hasn't realized // that, yet. In this case, do another parse run to detect all parent projects. if (!(parentProjectContent != null && this.oldUnits.Length == 1 && this.oldUnits[0].ProjectContent != parentProjectContent)) { return this.parseInfo; } } } if (parentProjectContent != null && (oldUnits.Length == 0 || (oldUnits.Length == 1 && oldUnits[0].ProjectContent == parentProjectContent))) { // Optimization: if parentProjectContent is specified and doesn't conflict with what we already know, // we will use it instead of doing an expensive GetProjectContents call. projectContents = new List<IProjectContent>(); projectContents.Add(parentProjectContent); } else { projectContents = GetProjectContents(fileName); } } // We now leave the lock to do the actual parsing. // This is done to allow IParser implementations to invoke methods on the main thread without // risking deadlocks. // parse once for each project content that contains the file ICompilationUnit[] newUnits = new ICompilationUnit[projectContents.Count]; ICompilationUnit resultUnit = null; for (int i = 0; i < newUnits.Length; i++) { IProjectContent pc = projectContents[i]; try { newUnits[i] = parser.Parse(pc, fileName, fileContent); } catch (Exception ex) { throw new ApplicationException("Error parsing " + fileName, ex); } if (i == 0 || pc == parentProjectContent) resultUnit = newUnits[i]; } lock (this) { if (this.disposed) return null; // ensure we never go backwards in time (we need to repeat this check after we've reacquired the lock) if (fileContentVersion != null && this.bufferVersion != null && this.bufferVersion.BelongsToSameDocumentAs(fileContentVersion)) { if (this.bufferVersion.CompareAge(fileContentVersion) >= 0) { if (parentProjectContent != null && parentProjectContent != parseInfo.CompilationUnit.ProjectContent) { ICompilationUnit oldUnit = oldUnits.FirstOrDefault(o => o.ProjectContent == parentProjectContent); if (oldUnit != null) return new ParseInformation(oldUnit); } return this.parseInfo; } } ParseInformation newParseInfo = new ParseInformation(resultUnit); for (int i = 0; i < newUnits.Length; i++) { IProjectContent pc = projectContents[i]; // update the compilation unit ICompilationUnit oldUnit = oldUnits.FirstOrDefault(o => o.ProjectContent == pc); pc.UpdateCompilationUnit(oldUnit, newUnits[i], fileName); ParseInformation newUnitParseInfo = (newUnits[i] == resultUnit) ? newParseInfo : new ParseInformation(newUnits[i]); RaiseParseInformationUpdated(new ParseInformationEventArgs(fileName, pc, oldUnit, newUnitParseInfo)); } // remove all old units that don't exist anymore foreach (ICompilationUnit oldUnit in oldUnits) { if (!newUnits.Any(n => n.ProjectContent == oldUnit.ProjectContent)) { oldUnit.ProjectContent.RemoveCompilationUnit(oldUnit); RaiseParseInformationUpdated(new ParseInformationEventArgs(fileName, oldUnit.ProjectContent, oldUnit, null)); } } this.bufferVersion = fileContentVersion; this.oldUnits = newUnits; this.parseInfo = newParseInfo; return newParseInfo; } }
public ParseInformation ParseFile(IProjectContent parentProjectContent, ITextBuffer fileContent) { if (parser == null) { return(null); } if (fileContent == null) { // GetParseableFileContent must not be called inside any lock // (otherwise we'd risk deadlocks because GetParseableFileContent must invoke on the main thread) try { fileContent = GetParseableFileContent(fileName); } catch (System.Reflection.TargetInvocationException ex) { // It is possible that the file gets deleted/becomes inaccessible while a background parse // operation is enqueued, so we have to handle IO exceptions. if (ex.InnerException is IOException || ex.InnerException is UnauthorizedAccessException) { return(null); } else { throw; } } catch (IOException) { return(null); } catch (UnauthorizedAccessException) { return(null); } } ITextBufferVersion fileContentVersion = fileContent.Version; List <IProjectContent> projectContents; lock (this) { if (this.disposed) { return(null); } if (fileContentVersion != null && this.bufferVersion != null && this.bufferVersion.BelongsToSameDocumentAs(fileContentVersion)) { if (this.bufferVersion.CompareAge(fileContentVersion) >= 0) { // Special case: (necessary due to parentProjectContent optimization) // Detect when a file belongs to multiple projects but the ParserService hasn't realized // that, yet. In this case, do another parse run to detect all parent projects. if (!(parentProjectContent != null && this.oldUnits.Length == 1 && this.oldUnits[0].ProjectContent != parentProjectContent)) { return(this.parseInfo); } } } if (parentProjectContent != null && (oldUnits.Length == 0 || (oldUnits.Length == 1 && oldUnits[0].ProjectContent == parentProjectContent))) { // Optimization: if parentProjectContent is specified and doesn't conflict with what we already know, // we will use it instead of doing an expensive GetProjectContents call. projectContents = new List <IProjectContent>(); projectContents.Add(parentProjectContent); } else { projectContents = GetProjectContents(fileName); } } // We now leave the lock to do the actual parsing. // This is done to allow IParser implementations to invoke methods on the main thread without // risking deadlocks. // parse once for each project content that contains the file ICompilationUnit[] newUnits = new ICompilationUnit[projectContents.Count]; ICompilationUnit resultUnit = null; for (int i = 0; i < newUnits.Length; i++) { IProjectContent pc = projectContents[i]; try { newUnits[i] = parser.Parse(pc, fileName, fileContent); } catch (Exception ex) { throw new ApplicationException("Error parsing " + fileName, ex); } if (i == 0 || pc == parentProjectContent) { resultUnit = newUnits[i]; } } lock (this) { if (this.disposed) { return(null); } // ensure we never go backwards in time (we need to repeat this check after we've reacquired the lock) if (fileContentVersion != null && this.bufferVersion != null && this.bufferVersion.BelongsToSameDocumentAs(fileContentVersion)) { if (this.bufferVersion.CompareAge(fileContentVersion) >= 0) { if (parentProjectContent != null && parentProjectContent != parseInfo.CompilationUnit.ProjectContent) { ICompilationUnit oldUnit = oldUnits.FirstOrDefault(o => o.ProjectContent == parentProjectContent); if (oldUnit != null) { return(new ParseInformation(oldUnit)); } } return(this.parseInfo); } } ParseInformation newParseInfo = new ParseInformation(resultUnit); for (int i = 0; i < newUnits.Length; i++) { IProjectContent pc = projectContents[i]; // update the compilation unit ICompilationUnit oldUnit = oldUnits.FirstOrDefault(o => o.ProjectContent == pc); pc.UpdateCompilationUnit(oldUnit, newUnits[i], fileName); ParseInformation newUnitParseInfo = (newUnits[i] == resultUnit) ? newParseInfo : new ParseInformation(newUnits[i]); RaiseParseInformationUpdated(new ParseInformationEventArgs(fileName, pc, oldUnit, newUnitParseInfo, newUnits[i] == resultUnit)); } // remove all old units that don't exist anymore foreach (ICompilationUnit oldUnit in oldUnits) { if (!newUnits.Any(n => n.ProjectContent == oldUnit.ProjectContent)) { oldUnit.ProjectContent.RemoveCompilationUnit(oldUnit); RaiseParseInformationUpdated(new ParseInformationEventArgs(fileName, oldUnit.ProjectContent, oldUnit, null, false)); } } this.bufferVersion = fileContentVersion; this.oldUnits = newUnits; this.parseInfo = newParseInfo; return(newParseInfo); } }