Posted 12 years ago by Leif Zars
Version: 4.0.0260
Avatar
I have implemented a method that uses the AstNodes provided by your .Net Addon. It is almost finished the only problem is that it is very slow. I think it is slow because i am updating every lines TabStopLevel. Should i be calling a PauseUpdate on the document and then ResumeUpdate or should make all my changes in a buffer and then replace the formatted area with the buffer?


Thank you
Leif

I am not done with the code, i know of a few indenting bugs. for VB only
Imports ActiproSoftware.SyntaxEditor
Imports ActiproSoftware.SyntaxEditor.Addons.VB
Imports ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast

Public Class FormattingASTVisitor
    Inherits AstVisitor

    Private Document As Document

    Private IndentLevel As Integer
    Private IndentationOffsetSum As Integer
    Private LastFormattedLine As Integer

#Region " Public  "
    Public Overridable Sub VisitCompilationUnit(ByVal CompilationUnit As ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.CompilationUnit, ByVal Document As Document)
        IndentationOffsetSum = 0
        IndentLevel = 0
        LastFormattedLine = -1
        _Regions = Nothing
        Me.Document = Document


        CompilationUnit.Accept(Me)

    End Sub
#End Region

#Region " Step Left or Right in Indentation "
    Private Sub Left()
        IndentLevel = IndentLevel - 1
        If IndentLevel < 0 Then
            IndentLevel = 0
            'BCAM.GeneralCore.Services.EventLogger.Warn("Auto Indenting Error")
        End If
    End Sub
    Private Sub Right()
        IndentLevel = IndentLevel + 1
    End Sub
#End Region

#Region " Adjust Line Indentation "
    Private Sub AdjustLine(ByVal Line As Integer, ByVal IndentLevel As Integer)
        If Document.Lines(Line).TabStopLevel <> IndentLevel Then
            IndentationOffsetSum = IndentationOffsetSum + (IndentLevel - Document.Lines(Line).TabStopLevel)
            'If Document.Lines(Line).Text = "" And IndentLevel > 0 Then IndentationOffsetSum = IndentationOffsetSum
            Document.Lines(Line).TabStopLevel = IndentLevel
        End If
    End Sub
#End Region

#Region " Adjust Line with Relation to AstNode "
    Private Sub AdjustOnPreVisiting(ByVal Node As AstNode)
        AdjustAstNode(Node, True)
    End Sub
    Private Sub AdjustOnPostVisiting(ByVal Node As AstNode)
        AdjustAstNode(Node, False)
    End Sub
    Private Sub AdjustAstNode(ByVal Node As AstNode, ByVal UseStartOffset As Boolean)
        Dim LineOffset As Integer = 0
        If UseStartOffset Then
            AdjustMissedLines(Document.OffsetToPosition(Node.TextRange.StartOffset + IndentationOffsetSum).Line)
            LineOffset = Node.TextRange.StartOffset + IndentationOffsetSum
        Else
            LineOffset = Node.TextRange.EndOffset + IndentationOffsetSum
        End If

        Dim DP As ActiproSoftware.SyntaxEditor.DocumentPosition = Document.OffsetToPosition(LineOffset)
        AdjustLine(DP.Line, IndentLevel)
        LastFormattedLine = DP.Line
    End Sub
#End Region

#Region " Adjust Line with out Relation to AstNode "
    Private Sub AdjustMissedLines(ByVal NextLine As Integer)
        For Line As Integer = LastFormattedLine + 1 To NextLine - 1
            If IsLineRegionDirective(Line) Then
                AdjustLine(Line, 0)
            Else
                AdjustLine(Line, IndentLevel)
            End If
        Next
    End Sub
    Private Sub AdjustMissedLines(ByVal NextNode As AstNode)
        AdjustMissedLines(Document.OffsetToPosition(NextNode.TextRange.EndOffset + IndentationOffsetSum).Line)
    End Sub
#End Region

#Region " Else Help "
    ''' <summary>
    ''' This whole region isnt very good
    ''' Help???
    ''' </summary>
    ''' <param name="Node"></param>
    ''' <remarks></remarks>
    Private Sub AdjustIfElseLine(ByVal Node As IfStatement)
        Dim Line As Integer = FindElseLine(Node)
        If Line = -1 Then Exit Sub
        Me.AdjustLine(Line, Me.IndentLevel)
    End Sub
    Private Function FindElseLine(ByVal Node As IfStatement) As Integer
        If Node.FalseStatement Is Nothing Then Return -1

        Dim TrueStatmentLastLine As Integer = Document.OffsetToPosition(Node.TrueStatement.TextRange.EndOffset + Me.IndentationOffsetSum).Line
        Dim FalseStatmentFirstLine As Integer = Document.OffsetToPosition(Node.FalseStatement.TextRange.StartOffset + Me.IndentationOffsetSum).Line
        If FalseStatmentFirstLine - TrueStatmentLastLine = 1 Then
            Return FalseStatmentFirstLine - 1
        ElseIf FalseStatmentFirstLine - TrueStatmentLastLine > 1 Then
            For I As Integer = TrueStatmentLastLine To FalseStatmentFirstLine - 1
                If Document.Lines(I).Text.Trim(" ").Trim(vbTab).ToString = "Else" Then
                    Return I
                End If
            Next
            Return ((FalseStatmentFirstLine + TrueStatmentLastLine) / 2) - 1
        End If
        Return -1
    End Function
#End Region

#Region " Regions Directive Help "
    Private _Regions As Collections.IList
    Private ReadOnly Property Regions() As Collections.IList
        Get
            Return _Regions
        End Get
    End Property
    Private Function IsLineRegionDirective(ByVal Line As Integer) As Boolean
        If Regions Is Nothing Then Return False
        For Each Range As ActiproSoftware.SyntaxEditor.TextRange In Regions
            If Document.OffsetToPosition(Range.StartOffset + IndentationOffsetSum).Line = Line Then
                Return True
            ElseIf Document.OffsetToPosition(Range.EndOffset + IndentationOffsetSum).Line = Line Then
                Return True
            End If
        Next
        Return False
    End Function
#End Region

#Region " Ast Visiters "
    Public Overrides Function OnPreVisiting(ByVal node As ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.AstNode) As Boolean
        If TypeOf node Is CompilationUnit Then
            _Regions = CType(node, CompilationUnit).RegionTextRanges

        ElseIf TypeOf node Is TypeDeclaration Then
            AdjustOnPreVisiting(node)
            Right()
        ElseIf TypeOf node Is IfStatement Then
            AdjustOnPreVisiting(node)
            Right()
        ElseIf TypeOf node Is ElseIfSection Then
            Left()
            AdjustOnPreVisiting(node)
            Right()
        ElseIf TypeOf node Is TypeMemberDeclaration Then
            AdjustOnPreVisiting(node)
            Right()
        ElseIf TypeOf node Is NamespaceDeclaration Then
            AdjustOnPreVisiting(node)
            Right()
        ElseIf TypeOf node Is ChildStatementStatement Then
            AdjustOnPreVisiting(node)
            Right()
        ElseIf TypeOf node Is BlockStatement Then
        ElseIf TypeOf node Is LocalVariableDeclaration Then
        ElseIf TypeOf node Is Statement Then
            AdjustOnPreVisiting(node)
        Else
            Return False
        End If
        Return True
        'Return MyBase.OnPreVisiting(node)
    End Function
    Public Overrides Sub OnPostVisited(ByVal node As ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.AstNode)
        If TypeOf node Is CompilationUnit Then
            _Regions = Nothing
        ElseIf TypeOf node Is TypeDeclaration Then
            AdjustMissedLines(node)
            Left()
            AdjustOnPostVisiting(node)
        ElseIf TypeOf node Is IfStatement Then
            AdjustMissedLines(node)
            Left()
            AdjustIfElseLine(node)
            AdjustOnPostVisiting(node)
        ElseIf TypeOf node Is ElseIfSection Then
            AdjustMissedLines(node)
            'Left()
            AdjustOnPostVisiting(node)
        ElseIf TypeOf node Is TypeMemberDeclaration Then
            AdjustMissedLines(node)
            Left()
            AdjustOnPostVisiting(node)
        ElseIf TypeOf node Is NamespaceDeclaration Then
            AdjustMissedLines(node)
            Left()
            AdjustOnPostVisiting(node)
        ElseIf TypeOf node Is ChildStatementStatement Then
            AdjustMissedLines(node)
            Left()
            AdjustOnPostVisiting(node)
    
        ElseIf TypeOf node Is LocalVariableDeclaration Then
        ElseIf TypeOf node Is BlockStatement Then
        ElseIf TypeOf node Is Statement Then

        End If
        MyBase.OnPostVisited(node)
    End Sub


#End Region

End Class

Comments (15)

Posted 12 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
You could try turning off Document.LexicalParsingEnabled while you make the update and then re-enable it after.


Actipro Software Support

Posted 12 years ago by Leif Zars
Avatar
i think it has to do with some handlers i have handling the SyntaxEditor.TextChanged event. can i disable this event while i change every lines TabStopLevel?


Leif
Posted 12 years ago by Leif Zars
Avatar
I need the ability to complete a batch processes of text update and have the Document record the many update as just one. This is needed to i can adjust the TabStopLevel on many lines to fix the formatting and have only one entry in the UndoRedo Stack and have TextChanged called only once.


Basically something like,
BegainUpdate(DocumentModificationType)
.....
many changes, inserts and replaces
.....
EndUpdate()

Thank you

Leif
Posted 12 years ago by Leif Zars
Avatar
Also i couldn't imagine it taking you more then a few hours for you guys to use the astnodes to keep a document in a proper formatted state. Why haven't yall? With the astnodes it seems very simple.

Leif
Posted 12 years ago by Kelly Leahy - Software Architect, Milliman
Avatar
UndoRedo.StartGroup / EndGroup on the document.

Kelly Leahy Software Architect Milliman, USA

Posted 12 years ago by Kelly Leahy - Software Architect, Milliman
Avatar
However, TextChanged will still get called multiple times I think... That, you'll have to handle on your own. I do all my modifications outside the editor and then only put the text back when I'm done, for my code rewriting support.

Otherwise, you could set a flag in your application to prohibit your TextChanged code from running while you're making all but the last modification.

Kelly Leahy Software Architect Milliman, USA

Posted 12 years ago by Leif Zars
Avatar
Most complex data containers have a way of doing mass updates while suppressing events that should only be raised at the end of the mass update.
Posted 12 years ago by Kelly Leahy - Software Architect, Milliman
Avatar
Quote:
Most complex data containers have a way of doing mass updates while suppressing events that should only be raised at the end of the mass update.


True... I'm not sure I'd call this a 'complex data container', though. Also, I'm not sure those containers you're referring to actually do raise the individual events after the end of the update - do you have an example in mind?

Kelly Leahy Software Architect Milliman, USA

Posted 12 years ago by Leif Zars
Avatar
I am not sure what kind of example you are looking for. i have one above. Or maybe you are talking about an example of a data container, the DataTable object has a Begin and EndLoadData that turns off Constraint enforcement and sorting along with a call to SuspendIndexEvents. I am not sure if the modified event is raised at the call of EndLoadData, but i am sure it could easily be made optional.
Posted 12 years ago by Kelly Leahy - Software Architect, Milliman
Avatar
Quote:
the DataTable object has a Begin and EndLoadData that turns off Constraint enforcement and sorting along with a call to SuspendIndexEvents. I am not sure if the modified event is raised at the call of EndLoadData, but i am sure it could easily be made optional.


That's what I was referring to. Also, I don't believe the 'DataColumnChanged', etc. events are raised when the 'EndLoadData' is called, so you have to 'force update' your UI to react to the changes en masse.

What I was getting at is that it's easy to suspect TextChanged events (and there should probably be a way to do it in SyntaxEditor, though I think there isn't - unless the Suspend / Resume stuff that's already there does it), there won't be an easy way to receive a TextChanged event when the operation is complete, since 'TextChanged' has some information in the EventArgs that is very targeted for 'single change' events, not en masse changes. Of course, it's not hard to add a new event for 'AfterEndUpdate' or something like that for notification that your UI needs to refresh itself after a mass change, but it should probably be a different event (unless the EventArgs for TextChanged already supports en-masse changes - I haven't looked at it yet myself).

Kelly

Kelly Leahy Software Architect Milliman, USA

Posted 12 years ago by Leif Zars
Avatar
i might just do it in a buffer outside of actipro, or something. maybe inherit from the document class and add an updating flag or something.

Thanks
Posted 12 years ago by Kelly Leahy - Software Architect, Milliman
Avatar
Quote:
i might just do it in a buffer outside of actipro, or something. maybe inherit from the document class and add an updating flag or something.


So you just want to avoid your 'TextChanged' event handler from executing while the text is being changed? Is your handler attached directly to Document, or is it on the Editor? You could detach the document from the editor, turn off parsing and lexing, make your changes, turn parsing / lexing back on, and put the document back on the editor, if you wanted to.

Personally, I prefer a model where I use 'SemanticParseData'-directed modification of the text outside of the editor. Then I put the text back. Just make sure that if you use this approach, you use GetText(LineTerminator.Newline) on the document, rather than the Text property of the document or the editor. This is because your offsets will be all screwy in your AST if you don't.

Good luck,

Kelly Leahy Software Architect Milliman, USA

Posted 12 years ago by Leif Zars
Avatar
Ya okay thanks, i will work on it some more later today.

Another event would be Editor.UserInterfaceUpdate
THanks
Leif Zars
Posted 12 years ago by Kelly Leahy - Software Architect, Milliman
Avatar
Quote:
Another event would be Editor.UserInterfaceUpdate


Yeah, my users aren't patient enough for that one ;)

I'm sure there's someplace you can set the interval used for UserInterfaceUpdate but I haven't looked for it...

Kelly Leahy Software Architect Milliman, USA

Posted 12 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Leif,

You can start a group of the undo as Kelly said. Then also set a flag in your code when you start this indent and in your TextChanged handler, ignore the event if that flag is set, then reset it to false when your indent is complete. We'll see what we can do about making a suspend/resume down the road though that would help in this scenario.


Actipro Software Support

The latest build of this product (v2018.1 build 0341) was released 7 months ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.