Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Xtext's new Formatter API

7.400 Aufrufe

Veröffentlicht am

About the Xtext's Formatter API that has been introduces with Xtext 2.8

Presented at http://xtextcon.org/ 2015 in Kiel, Germany.

Xtext's new Formatter API

  1. 1. Moritz Eysholdt, itemis AG XTEXT’S NEW FORMATTER API
  2. 2. AGENDA • motivation • use cases • ITextRegionAccess • IFormattableDocument • example formatter • testing
  3. 3. DEFINITION
  4. 4. DEFINITION “improve readability and emphasize structure of a document without changing its meaning”
  5. 5. DEFINITION “improve readability and emphasize structure of a document without changing its meaning” “prettify spaces, line wraps, tabs and indentation without changing the AST”
  6. 6. …if it does change the AST, it’s probably a clean-up action or refactoring
  7. 7. MOTIVATION
  8. 8. I’m lazy readability diff & merge bug prevention MOTIVATION
  9. 9. I’m lazy readability diff & merge bug prevention With Formatter: CTRL+SHIFT+F MOTIVATION
  10. 10. I’m lazy readability diff & merge bug prevention With Formatter: CTRL+SHIFT+F Without Formatter: →→ __↓ →→ __↓ ¶ ↑↑ ↓↓ ¶ MOTIVATION
  11. 11. I’m lazy readability diff & merge bug prevention MOTIVATION
  12. 12. bad formatting… - obscures the code’s structure - unnecessarily consumes brain cycles to filter out anomalies I’m lazy readability diff & merge bug prevention MOTIVATION
  13. 13. I’m lazy readability diff & merge bug prevention MOTIVATION
  14. 14. • whitespace-only changes in diffs are annoying I’m lazy readability diff & merge bug prevention MOTIVATION
  15. 15. • whitespace-only changes in diffs are annoying • merge conflicts because of whitespace-only super annoying I’m lazy readability diff & merge bug prevention MOTIVATION
  16. 16. • whitespace-only changes in diffs are annoying • merge conflicts because of whitespace-only super annoying • only commit formatted code! I’m lazy readability diff & merge bug prevention MOTIVATION
  17. 17. I’m lazy readability diff & merge bug prevention MOTIVATION
  18. 18. I’m lazy readability diff & merge bug prevention MOTIVATION
  19. 19. I’m lazy readability diff & merge bug prevention MOTIVATION
  20. 20. I’m lazy readability diff & merge bug prevention MOTIVATION
  21. 21. wrong indentation! proper formatting would have increased the chances of spotting this bug! I’m lazy readability diff & merge bug prevention MOTIVATION
  22. 22. USE CASES
  23. 23. on demand serialization semantic quickfix USE CASES
  24. 24. on demand serialization semantic quickfix CTRL+SHIFT+F ….and via context menu USE CASES
  25. 25. on demand serialization semantic quickfix USE CASES
  26. 26. on demand serialization semantic quickfix Use an Xtext grammar to convert an EMF model to text. Formatter contributes indentation, newlines, space, etc. USE CASES
  27. 27. on demand serialization semantic quickfix Use an Xtext grammar to convert an EMF model to text. Formatter contributes indentation, newlines, space, etc. Serializer Use Cases: - non-textual (e.g. graphical) editors - model migration USE CASES
  28. 28. on demand serialization semantic quickfix USE CASES @Fix(IssueCodes.INVALID_FEATURE_NAME) public void fixName(final Issue issue, IssueResolutionAcceptor acceptor) { acceptor.accept(issue, "Uncapitalize name", "", "", new ISemanticModification() { @Override public void apply(EObject element, IModificationContext context) { ((Feature) element).setName(toFirstLower(issue.getData()[0])); } }); }
  29. 29. ARCHITECTURE
  30. 30. messy formatter pretty
  31. 31. messy formatter pretty developer creates developer creates formatter, NoEngine!
  32. 32. messy formatter pretty developer creates text model (AST) text- regions (CST) in in in Input: Text, AST and CST
  33. 33. messy formatter pretty developer creates text model (AST) text- regions (CST) in in in text replacements apply Output: TextReplacements
  34. 34. messy formatter pretty grammar developer creates text model (AST) text- regions (CST) in in in text replacements apply references access to grammar
  35. 35. fragment = formatting2.Formatter2Fragment auto-inject {} fragment = formatting.FormatterFragment auto-inject {} GeneratorFragment for MWE2
  36. 36. import org.eclipse.xtext.formatting2.AbstractFormatter2 ! class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } } Model: "machine" name=QualifiedName “;";
  37. 37. import org.eclipse.xtext.formatting2.AbstractFormatter2 ! class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } } Model: "machine" name=QualifiedName “;"; extends AbstractFormatter2
  38. 38. import org.eclipse.xtext.formatting2.AbstractFormatter2 ! class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } } Model: "machine" name=QualifiedName “;"; dispatch over AST elements
  39. 39. import org.eclipse.xtext.formatting2.AbstractFormatter2 ! class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } } 1. start with the EObject Model: "machine" name=QualifiedName “;";
  40. 40. import org.eclipse.xtext.formatting2.AbstractFormatter2 ! class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } } 1. start with the EObject 2. find desired semantic text region using ITextRegionAccess Model: "machine" name=QualifiedName “;";
  41. 41. import org.eclipse.xtext.formatting2.AbstractFormatter2 ! class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } } 1. start with the EObject 2. find desired semantic text region using ITextRegionAccess 3. register formatting information on IFormattableDocument Model: "machine" name=QualifiedName “;";
  42. 42. TEXT REGIONS
  43. 43. text:String Document getText():String offset:int length:int TextRegion1 * TextRegions… …represent a substring …can overlap …can be empty
  44. 44. regionForOffset(int offset, int length) regionForDocument() resource:XtextResource ITextRegionAccess regionForEObject(EObject object) regionForRootEObject() the document. created from node model or via serializer
  45. 45. getText():String offset : int length: int ITextRegion regionForOffset(int offset, int length) regionForDocument() resource:XtextResource ITextRegionAccess regionForEObject(EObject object) regionForRootEObject() returns regions for anything
  46. 46. non-hidden token 0..n hidden tokens ISequentialRegion getText():String offset : int length: int ITextRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext regionForOffset(int offset, int length) regionForDocument() resource:XtextResource ITextRegionAccess regionForEObject(EObject object) regionForRootEObject() returns strictly alternating linked list of hidden/semantic regions
  47. 47. whitespace token comment token ISequentialRegion getText():String offset : int length: int ITextRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPartparts IWhitespace IComment regionForOffset(int offset, int length) regionForDocument() resource:XtextResource ITextRegionAccess regionForEObject(EObject object) regionForRootEObject() returns
  48. 48. eObject:EObject grammarElement:EObject IEObjectRegion ISequentialRegion getText():String offset : int length: int ITextRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment regionForOffset(int offset, int length) regionForDocument() resource:XtextResource ITextRegionAccess regionForEObject(EObject object) regionForRootEObject() returns returns leading trailing for AST element
  49. 49. eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing
  50. 50. eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing /* Copyright */ machine my.X; ! // First State state 1. Take Document 1.
  51. 51. eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; 1. Take Document 2. Parse it using Grammar 1. 2.
  52. 52. class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { println(textRegionAccess.toString()) } } eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; 1. Take Document 2. Parse it using Grammar 3. Dump it via textRegionAccess.toString() 1. 2. 3.
  53. 53. class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { println(textRegionAccess) } } eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; 1. Take Document 2. Parse it using Grammar 3. Dump it via textRegionAccess.toString() 4. See what we get (next slide) 1. 2. 3.
  54. 54. /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion ! 0 H "/* Copyright */" Comment:TerminalRule'ML_COMMENT' 16 "n" Whitespace:TerminalRule'WS' B Model'my.X' Model 16 7 S "machine" Model:'machine' 23 1 H " " Whitespace:TerminalRule'WS' 24 4 S "my.X" Model:name=QualifiedName 28 0 H 28 1 S ";" Model:';' 29 H "nn" Whitespace:TerminalRule'WS' 17 "// First Staten" Comment:TerminalRule'SL_COMMENT' B State Model:elements+=State path:Model'my.X'/elements[0] 46 5 S "state" State:'state' E State Model:elements+=State path:Model'my.X'/elements[0] E Model'my.X' Model 51 0 H eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing
  55. 55. /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion ! 0 H "/* Copyright */" Comment:TerminalRule'ML_COMMENT' 16 "n" Whitespace:TerminalRule'WS' B Model'my.X' Model 16 7 S "machine" Model:'machine' 23 1 H " " Whitespace:TerminalRule'WS' 24 4 S "my.X" Model:name=QualifiedName 28 0 H 28 1 S ";" Model:';' 29 H "nn" Whitespace:TerminalRule'WS' 17 "// First Staten" Comment:TerminalRule'SL_COMMENT' B State Model:elements+=State path:Model'my.X'/elements[0] 46 5 S "state" State:'state' E State Model:elements+=State path:Model'my.X'/elements[0] E Model'my.X' Model 51 0 H eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing
  56. 56. /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion ! 0 H "/* Copyright */" Comment:TerminalRule'ML_COMMENT' 16 "n" Whitespace:TerminalRule'WS' B Model'my.X' Model 16 7 S "machine" Model:'machine' 23 1 H " " Whitespace:TerminalRule'WS' 24 4 S "my.X" Model:name=QualifiedName 28 0 H 28 1 S ";" Model:';' 29 H "nn" Whitespace:TerminalRule'WS' 17 "// First Staten" Comment:TerminalRule'SL_COMMENT' B State Model:elements+=State path:Model'my.X'/elements[0] 46 5 S "state" State:'state' E State Model:elements+=State path:Model'my.X'/elements[0] E Model'my.X' Model 51 0 H eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing
  57. 57. Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion ! 0 H "/* Copyright */" Comment:TerminalRule'ML_COMMENT' 16 "n" Whitespace:TerminalRule'WS' B Model'my.X' Model 16 7 S "machine" Model:'machine' 23 1 H " " Whitespace:TerminalRule'WS' 24 4 S "my.X" Model:name=QualifiedName 28 0 H 28 1 S ";" Model:';' 29 H "nn" Whitespace:TerminalRule'WS' 17 "// First Staten" Comment:TerminalRule'SL_COMMENT' B State Model:elements+=State path:Model'my.X'/elements[0] 46 5 S "state" State:'state' E State Model:elements+=State path:Model'my.X'/elements[0] E Model'my.X' Model 51 0 H /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing keyword datatype
  58. 58. Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion ! 0 H "/* Copyright */" Comment:TerminalRule'ML_COMMENT' 16 "n" Whitespace:TerminalRule'WS' B Model'my.X' Model 16 7 S "machine" Model:'machine' 23 1 H " " Whitespace:TerminalRule'WS' 24 4 S "my.X" Model:name=QualifiedName 28 0 H 28 1 S ";" Model:';' 29 H "nn" Whitespace:TerminalRule'WS' 17 "// First Staten" Comment:TerminalRule'SL_COMMENT' B State Model:elements+=State path:Model'my.X'/elements[0] 46 5 S "state" State:'state' E State Model:elements+=State path:Model'my.X'/elements[0] E Model'my.X' Model 51 0 H /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing empty first region in document last region in document 1 part 2 parts
  59. 59. Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion ! 0 H "/* Copyright */" Comment:TerminalRule'ML_COMMENT' 16 "n" Whitespace:TerminalRule'WS' B Model'my.X' Model 16 7 S "machine" Model:'machine' 23 1 H " " Whitespace:TerminalRule'WS' 24 4 S "my.X" Model:name=QualifiedName 28 0 H 28 1 S ";" Model:';' 29 H "nn" Whitespace:TerminalRule'WS' 17 "// First Staten" Comment:TerminalRule'SL_COMMENT' B State Model:elements+=State path:Model'my.X'/elements[0] 46 5 S "state" State:'state' E State Model:elements+=State path:Model'my.X'/elements[0] E Model'my.X' Model 51 0 H /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing
  60. 60. Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion ! 0 H "/* Copyright */" Comment:TerminalRule'ML_COMMENT' 16 "n" Whitespace:TerminalRule'WS' B Model'my.X' Model 16 7 S "machine" Model:'machine' 23 1 H " " Whitespace:TerminalRule'WS' 24 4 S "my.X" Model:name=QualifiedName 28 0 H 28 1 S ";" Model:';' 29 H "nn" Whitespace:TerminalRule'WS' 17 "// First Staten" Comment:TerminalRule'SL_COMMENT' B State Model:elements+=State path:Model'my.X'/elements[0] 46 5 S "state" State:'state' E State Model:elements+=State path:Model'my.X'/elements[0] E Model'my.X' Model 51 0 H /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing
  61. 61. Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion ! 0 H "/* Copyright */" Comment:TerminalRule'ML_COMMENT' 16 "n" Whitespace:TerminalRule'WS' B Model'my.X' Model 16 7 S "machine" Model:'machine' 23 1 H " " Whitespace:TerminalRule'WS' 24 4 S "my.X" Model:name=QualifiedName 28 0 H 28 1 S ";" Model:';' 29 H "nn" Whitespace:TerminalRule'WS' 17 "// First Staten" Comment:TerminalRule'SL_COMMENT' B State Model:elements+=State path:Model'my.X'/elements[0] 46 5 S "state" State:'state' E State Model:elements+=State path:Model'my.X'/elements[0] E Model'my.X' Model 51 0 H /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing begin end grammarElement path
  62. 62. Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion ! 0 H "/* Copyright */" Comment:TerminalRule'ML_COMMENT' 16 "n" Whitespace:TerminalRule'WS' B Model'my.X' Model 16 7 S "machine" Model:'machine' 23 1 H " " Whitespace:TerminalRule'WS' 24 4 S "my.X" Model:name=QualifiedName 28 0 H 28 1 S ";" Model:';' 29 H "nn" Whitespace:TerminalRule'WS' 17 "// First Staten" Comment:TerminalRule'SL_COMMENT' B State Model:elements+=State path:Model'my.X'/elements[0] 46 5 S "state" State:'state' E State Model:elements+=State path:Model'my.X'/elements[0] E Model'my.X' Model 51 0 H /* Copyright */ machine my.X; ! // First State state Model: "machine" name=QualifiedName ";" elements+=State*; ! State: {State} ‘state'; ! QualifiedName: ID ('.' ID)*; eObject:EObject grammarElement:EObject IEObjectRegion undefined:boolean IHiddenRegion grammarElement:EObject ISemanticRegion nextprevious previousnext grammarElement:EObject IHiddenRegionPart children parts IWhitespace IComment leading trailing
  63. 63. 1. start with the EObject 2. find desired semantic text region using ITextRegionAccess 3. register formatting information on IFormattableDocument (Recap) import org.eclipse.xtext.formatting2.AbstractFormatter2 ! class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } } Model: "machine" name=QualifiedName “;";
  64. 64. FIND SEMANTIC REGIONS // in local eObject only eObject.regionFor textRegionAccess.regionForEObject(eObject).regionFor ! // in eObject or any of its children eObject.allRegionsFor textRegionAccess.regionForEObject(eObject).allRegionsFor ! // in whole document textRegionAccess.regionForRootEObject.allRegionsFor } // first matched semantic region .keyword(",") .feature(StatesPackage.Literals.STATE__NAME) .keyword(actionsKeyword_2_1_0) .assignment(actionsAssignment_2_1_1) .ruleCall(actionsINTTerminalRuleCall_2_1_1_0) ! // all matched semantic region .keywords(",") .features(StatesPackage.Literals.STATE__NAME) .keywords(actionsKeyword_2_1_0) .assignments(actionsAssignment_2_1_1) .ruleCalls(actionsINTTerminalRuleCall_2_1_1_0) ! .keywordPairs("(", ")") {
  65. 65. FIND SEMANTIC REGIONS // in local eObject only eObject.regionFor textRegionAccess.regionForEObject(eObject).regionFor ! // in eObject or any of its children eObject.allRegionsFor textRegionAccess.regionForEObject(eObject).allRegionsFor ! // in whole document textRegionAccess.regionForRootEObject.allRegionsFor } // first matched semantic region .keyword(",") .feature(StatesPackage.Literals.STATE__NAME) .keyword(actionsKeyword_2_1_0) .assignment(actionsAssignment_2_1_1) .ruleCall(actionsINTTerminalRuleCall_2_1_1_0) ! // all matched semantic region .keywords(",") .features(StatesPackage.Literals.STATE__NAME) .keywords(actionsKeyword_2_1_0) .assignments(actionsAssignment_2_1_1) .ruleCalls(actionsINTTerminalRuleCall_2_1_1_0) ! .keywordPairs("(", ")") {ITextRegionAccess
  66. 66. FIND SEMANTIC REGIONS // in local eObject only eObject.regionFor textRegionAccess.regionForEObject(eObject).regionFor ! // in eObject or any of its children eObject.allRegionsFor textRegionAccess.regionForEObject(eObject).allRegionsFor ! // in whole document textRegionAccess.regionForRootEObject.allRegionsFor } // first matched semantic region .keyword(",") .feature(StatesPackage.Literals.STATE__NAME) .keyword(actionsKeyword_2_1_0) .assignment(actionsAssignment_2_1_1) .ruleCall(actionsINTTerminalRuleCall_2_1_1_0) ! // all matched semantic region .keywords(",") .features(StatesPackage.Literals.STATE__NAME) .keywords(actionsKeyword_2_1_0) .assignments(actionsAssignment_2_1_1) .ruleCalls(actionsINTTerminalRuleCall_2_1_1_0) ! .keywordPairs("(", ")") {ITextRegionExtensions
  67. 67. FIND SEMANTIC REGIONS // in local eObject only eObject.regionFor textRegionAccess.regionForEObject(eObject).regionFor ! // in eObject or any of its children eObject.allRegionsFor textRegionAccess.regionForEObject(eObject).allRegionsFor ! // in whole document textRegionAccess.regionForRootEObject.allRegionsFor } // first matched semantic region .keyword(",") .feature(StatesPackage.Literals.STATE__NAME) .keyword(actionsKeyword_2_1_0) .assignment(actionsAssignment_2_1_1) .ruleCall(actionsINTTerminalRuleCall_2_1_1_0) ! // all matched semantic region .keywords(",") .features(StatesPackage.Literals.STATE__NAME) .keywords(actionsKeyword_2_1_0) .assignments(actionsAssignment_2_1_1) .ruleCalls(actionsINTTerminalRuleCall_2_1_1_0) ! .keywordPairs("(", ")") {Keywords (String)
  68. 68. FIND SEMANTIC REGIONS // in local eObject only eObject.regionFor textRegionAccess.regionForEObject(eObject).regionFor ! // in eObject or any of its children eObject.allRegionsFor textRegionAccess.regionForEObject(eObject).allRegionsFor ! // in whole document textRegionAccess.regionForRootEObject.allRegionsFor } // first matched semantic region .keyword(",") .feature(StatesPackage.Literals.STATE__NAME) .keyword(actionsKeyword_2_1_0) .assignment(actionsAssignment_2_1_1) .ruleCall(actionsINTTerminalRuleCall_2_1_1_0) ! // all matched semantic region .keywords(",") .features(StatesPackage.Literals.STATE__NAME) .keywords(actionsKeyword_2_1_0) .assignments(actionsAssignment_2_1_1) .ruleCalls(actionsINTTerminalRuleCall_2_1_1_0) ! .keywordPairs("(", ")") {EMF EStructuralFeature (EAttribute, EReference)
  69. 69. FIND SEMANTIC REGIONS // in local eObject only eObject.regionFor textRegionAccess.regionForEObject(eObject).regionFor ! // in eObject or any of its children eObject.allRegionsFor textRegionAccess.regionForEObject(eObject).allRegionsFor ! // in whole document textRegionAccess.regionForRootEObject.allRegionsFor } // first matched semantic region .keyword(",") .feature(StatesPackage.Literals.STATE__NAME) .keyword(actionsKeyword_2_1_0) .assignment(actionsAssignment_2_1_1) .ruleCall(actionsINTTerminalRuleCall_2_1_1_0) ! // all matched semantic region .keywords(",") .features(StatesPackage.Literals.STATE__NAME) .keywords(actionsKeyword_2_1_0) .assignments(actionsAssignment_2_1_1) .ruleCalls(actionsINTTerminalRuleCall_2_1_1_0) ! .keywordPairs("(", ")") {Grammar Element (from MyLanguageGrammarAccess)
  70. 70. TEXT REGION RECAP
  71. 71. TEXT REGION RECAP • ITextRegionAccess (for Xtend, ITextRegionExtensions)
  72. 72. TEXT REGION RECAP • ITextRegionAccess (for Xtend, ITextRegionExtensions) • linked list of strictly alternating ISemanticRegion and IHiddenRegion
  73. 73. TEXT REGION RECAP • ITextRegionAccess (for Xtend, ITextRegionExtensions) • linked list of strictly alternating ISemanticRegion and IHiddenRegion • IHiddenRegion can be empty
  74. 74. TEXT REGION RECAP • ITextRegionAccess (for Xtend, ITextRegionExtensions) • linked list of strictly alternating ISemanticRegion and IHiddenRegion • IHiddenRegion can be empty • IHiddenRegion contains parts, e.g. IComment and IWhitespace
  75. 75. TEXT REGION RECAP • ITextRegionAccess (for Xtend, ITextRegionExtensions) • linked list of strictly alternating ISemanticRegion and IHiddenRegion • IHiddenRegion can be empty • IHiddenRegion contains parts, e.g. IComment and IWhitespace • IEObjectRegion for elements from AST
  76. 76. TEXT REGION RECAP • ITextRegionAccess (for Xtend, ITextRegionExtensions) • linked list of strictly alternating ISemanticRegion and IHiddenRegion • IHiddenRegion can be empty • IHiddenRegion contains parts, e.g. IComment and IWhitespace • IEObjectRegion for elements from AST • use toString() during debugging!
  77. 77. import org.eclipse.xtext.formatting2.AbstractFormatter2 ! class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } } 1. start with the EObject 2. find desired semantic text region using ITextRegionAccess 3. register formatting information on IFormattableDocument Model: "machine" name=QualifiedName “;"; (Recap)
  78. 78. import org.eclipse.xtext.formatting2.AbstractFormatter2 ! class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } } 1. start with the EObject 2. find desired semantic text region using ITextRegionAccess 3. register formatting information on IFormattableDocument Model: "machine" name=QualifiedName “;"; (Recap)
  79. 79. FORMATTABLE DOCUMENT
  80. 80. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } API: specify formatting information for hidden regions Implementation: store text replacers for text regions
  81. 81. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } API: specify formatting information for hidden regions Implementation: store text replacers for text regions
  82. 82. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } specify for one or two offset : int length: int IHiddenRegion autowrap() setPriority(int priority) indent() newLine() setNewLines(int newLines) setNewLines(int min, int default, int max) noSpace() oneSpace() setSpace(String space) IHiddenRegionFormatter
  83. 83. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } specify for one or two offset : int length: int IHiddenRegion autowrap() setPriority(int priority) indent() newLine() setNewLines(int newLines) setNewLines(int min, int default, int max) noSpace() oneSpace() setSpace(String space) IHiddenRegionFormatter class StatesFormatter extends AbstractFormatter2 { ! def dispatch void format(Model model, extension IFormattableDocument document) { model.regionFor.keyword(";").prepend[noSpace] } }
  84. 84. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } specify for one or two offset : int length: int IHiddenRegion autowrap() setPriority(int priority) indent() newLine() setNewLines(int newLines) setNewLines(int min, int default, int max) noSpace() oneSpace() setSpace(String space) IHiddenRegionFormatter space, newLines, indent, autowrap
  85. 85. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } specify for one or two offset : int length: int IHiddenRegion autowrap() setPriority(int priority) indent() newLine() setNewLines(int newLines) setNewLines(int min, int default, int max) noSpace() oneSpace() setSpace(String space) IHiddenRegionFormatter IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion IHiddenRegionPart IWhitespace IComment 1 * remember the strictly alternating list of hidden/semantic regions
  86. 86. interface IFormattableDocument { ! ! ! ! ! ! ! // set for provided hidden region def set(IHiddenRegion hiddenRegion, (IHiddenRegionFormatter)=>void init) ! // append after the provided semantic region def append(ISemanticRegion appendAfter, (IHiddenRegionFormatter)=>void init) def append(EObject appendAfter, (IHiddenRegionFormatter)=>void init) ! // prepend before the provided semantic region def prepend(ISemanticRegion prependBefore, (IHiddenRegionFormatter)=>void init) def prepend(EObject prependBefore, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for oneIHiddenRegionFormatter IHiddenRegion
  87. 87. interface IFormattableDocument { ! ! ! ! ! ! ! // set for provided hidden region def set(IHiddenRegion hiddenRegion, (IHiddenRegionFormatter)=>void init) ! // append after the provided semantic region def append(ISemanticRegion appendAfter, (IHiddenRegionFormatter)=>void init) def append(EObject appendAfter, (IHiddenRegionFormatter)=>void init) ! // prepend before the provided semantic region def prepend(ISemanticRegion prependBefore, (IHiddenRegionFormatter)=>void init) def prepend(EObject prependBefore, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for one IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion IHiddenRegionFormatter IHiddenRegion
  88. 88. interface IFormattableDocument { ! ! ! ! ! ! ! // set for provided hidden region def set(IHiddenRegion hiddenRegion, (IHiddenRegionFormatter)=>void init) ! // append after the provided semantic region def append(ISemanticRegion appendAfter, (IHiddenRegionFormatter)=>void init) def append(EObject appendAfter, (IHiddenRegionFormatter)=>void init) ! // prepend before the provided semantic region def prepend(ISemanticRegion prependBefore, (IHiddenRegionFormatter)=>void init) def prepend(EObject prependBefore, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for one IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion IHiddenRegionFormatter IHiddenRegion
  89. 89. interface IFormattableDocument { ! ! ! ! ! ! ! // set for provided hidden region def set(IHiddenRegion hiddenRegion, (IHiddenRegionFormatter)=>void init) ! // append after the provided semantic region def append(ISemanticRegion appendAfter, (IHiddenRegionFormatter)=>void init) def append(EObject appendAfter, (IHiddenRegionFormatter)=>void init) ! // prepend before the provided semantic region def prepend(ISemanticRegion prependBefore, (IHiddenRegionFormatter)=>void init) def prepend(EObject prependBefore, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for one IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion IHiddenRegionFormatter IHiddenRegion
  90. 90. interface IFormattableDocument { ! ! ! ! ! ! ! // set for provided hidden region def set(IHiddenRegion hiddenRegion, (IHiddenRegionFormatter)=>void init) ! // append after the provided semantic region def append(ISemanticRegion appendAfter, (IHiddenRegionFormatter)=>void init) def append(EObject appendAfter, (IHiddenRegionFormatter)=>void init) ! // prepend before the provided semantic region def prepend(ISemanticRegion prependBefore, (IHiddenRegionFormatter)=>void init) def prepend(EObject prependBefore, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for one IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion IHiddenRegionFormatter IHiddenRegion
  91. 91. interface IFormattableDocument { ! ! ! ! ! ! ! // set for provided hidden region def set(IHiddenRegion hiddenRegion, (IHiddenRegionFormatter)=>void init) ! // append after the provided semantic region def append(ISemanticRegion appendAfter, (IHiddenRegionFormatter)=>void init) def append(EObject appendAfter, (IHiddenRegionFormatter)=>void init) ! // prepend before the provided semantic region def prepend(ISemanticRegion prependBefore, (IHiddenRegionFormatter)=>void init) def prepend(EObject prependBefore, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for one IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion IHiddenRegionFormatter IHiddenRegion
  92. 92. interface IFormattableDocument { ! ! ! ! ! ! ! // set for both provided hidden regions def set(IHiddenRegion r1, IHiddenRegion r2, (IHiddenRegionFormatter)=>void init) ! // before and after provided semantic region def surround(ISemanticRegion semanticRegion, (IHiddenRegionFormatter)=>void init) def surround(EObject owner, (IHiddenRegionFormatter)=>void init) ! // set for two inwards-bound hidden regions def interior(ISemanticRegion r1, ISemanticRegion r2, (IHiddenRegionFormatter)=>void init) def interior(EObject object, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for twoIHiddenRegionFormatter IHiddenRegion
  93. 93. interface IFormattableDocument { ! ! ! ! ! ! ! // set for both provided hidden regions def set(IHiddenRegion r1, IHiddenRegion r2, (IHiddenRegionFormatter)=>void init) ! // before and after provided semantic region def surround(ISemanticRegion semanticRegion, (IHiddenRegionFormatter)=>void init) def surround(EObject owner, (IHiddenRegionFormatter)=>void init) ! // set for two inwards-bound hidden regions def interior(ISemanticRegion r1, ISemanticRegion r2, (IHiddenRegionFormatter)=>void init) def interior(EObject object, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for twoIHiddenRegionFormatter IHiddenRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion
  94. 94. interface IFormattableDocument { ! ! ! ! ! ! ! // set for both provided hidden regions def set(IHiddenRegion r1, IHiddenRegion r2, (IHiddenRegionFormatter)=>void init) ! // before and after provided semantic region def surround(ISemanticRegion semanticRegion, (IHiddenRegionFormatter)=>void init) def surround(EObject owner, (IHiddenRegionFormatter)=>void init) ! // set for two inwards-bound hidden regions def interior(ISemanticRegion r1, ISemanticRegion r2, (IHiddenRegionFormatter)=>void init) def interior(EObject object, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for twoIHiddenRegionFormatter IHiddenRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion
  95. 95. interface IFormattableDocument { ! ! ! ! ! ! ! // set for both provided hidden regions def set(IHiddenRegion r1, IHiddenRegion r2, (IHiddenRegionFormatter)=>void init) ! // before and after provided semantic region def surround(ISemanticRegion semanticRegion, (IHiddenRegionFormatter)=>void init) def surround(EObject owner, (IHiddenRegionFormatter)=>void init) ! // set for two inwards-bound hidden regions def interior(ISemanticRegion r1, ISemanticRegion r2, (IHiddenRegionFormatter)=>void init) def interior(EObject object, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for twoIHiddenRegionFormatter IHiddenRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion
  96. 96. interface IFormattableDocument { ! ! ! ! ! ! ! // set for both provided hidden regions def set(IHiddenRegion r1, IHiddenRegion r2, (IHiddenRegionFormatter)=>void init) ! // before and after provided semantic region def surround(ISemanticRegion semanticRegion, (IHiddenRegionFormatter)=>void init) def surround(EObject owner, (IHiddenRegionFormatter)=>void init) ! // set for two inwards-bound hidden regions def interior(ISemanticRegion r1, ISemanticRegion r2, (IHiddenRegionFormatter)=>void init) def interior(EObject object, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for twoIHiddenRegionFormatter IHiddenRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion
  97. 97. interface IFormattableDocument { ! ! ! ! ! ! ! // set for both provided hidden regions def set(IHiddenRegion r1, IHiddenRegion r2, (IHiddenRegionFormatter)=>void init) ! // before and after provided semantic region def surround(ISemanticRegion semanticRegion, (IHiddenRegionFormatter)=>void init) def surround(EObject owner, (IHiddenRegionFormatter)=>void init) ! // set for two inwards-bound hidden regions def interior(ISemanticRegion r1, ISemanticRegion r2, (IHiddenRegionFormatter)=>void init) def interior(EObject object, (IHiddenRegionFormatter)=>void init) ! ! ! ! ! ! ! ! ! ! ! // ... } specify for twoIHiddenRegionFormatter IHiddenRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion ISemanitcRegion IHiddenRegion
  98. 98. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... }
  99. 99. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } Why specify formatting for two hidden regions at once?
  100. 100. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } Why specify formatting for two hidden regions at once? - indent() spans from first to second hidden region!
  101. 101. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } Why specify formatting for two hidden regions at once? - indent() spans from first to second hidden region! - convenience
  102. 102. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } Why specify formatting for two hidden regions at once? - indent() spans from first to second hidden region! - convenience
  103. 103. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } Why specify formatting for two hidden regions at once? - indent() spans from first to second hidden region! - convenience Why not increaseIndent() and decreaseIndent() individually?
  104. 104. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } Why specify formatting for two hidden regions at once? - indent() spans from first to second hidden region! - convenience Why not increaseIndent() and decreaseIndent() individually? - likely to not be applied symmetrically due to bugs, exceptions
  105. 105. ----------- RootDocument with ITextReplacers (syntax: <offset|text>) ----------- machine my.X; state<19| >foo<23| >actions 1, 2, 3;<43| >end ! -------------------------------------------------------------------------------- 19 1 " ": HiddenRegionReplacer: space=' ';indentInc=1 23 2 "n ": HiddenRegionReplacer: newLine=1 43 1 "n": HiddenRegionReplacer: newLine=1;indentDec=1 def dispatch void format(State state, extension IFormattableDocument document) { state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] state.regionFor.keyword("end").prepend[newLine] println(document.toString) } State: {State} 'state' name=ID ('actions' actions+=INT ("," actions+=INT)* ";")? 'end';
  106. 106. ----------- RootDocument with ITextReplacers (syntax: <offset|text>) ----------- machine my.X; state<19| >foo<23| >actions 1, 2, 3;<43| >end ! -------------------------------------------------------------------------------- 19 1 " ": HiddenRegionReplacer: space=' ';indentInc=1 23 2 "n ": HiddenRegionReplacer: newLine=1 43 1 "n": HiddenRegionReplacer: newLine=1;indentDec=1 def dispatch void format(State state, extension IFormattableDocument document) { state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] state.regionFor.keyword("end").prepend[newLine] println(document.toString) } State: {State} 'state' name=ID ('actions' actions+=INT ("," actions+=INT)* ";")? 'end';
  107. 107. ----------- RootDocument with ITextReplacers (syntax: <offset|text>) ----------- machine my.X; state<19| >foo<23| >actions 1, 2, 3;<43| >end ! -------------------------------------------------------------------------------- 19 1 " ": HiddenRegionReplacer: space=' ';indentInc=1 23 2 "n ": HiddenRegionReplacer: newLine=1 43 1 "n": HiddenRegionReplacer: newLine=1;indentDec=1 def dispatch void format(State state, extension IFormattableDocument document) { state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] state.regionFor.keyword("end").prepend[newLine] println(document.toString) } State: {State} 'state' name=ID ('actions' actions+=INT ("," actions+=INT)* ";")? 'end';
  108. 108. ----------- RootDocument with ITextReplacers (syntax: <offset|text>) ----------- machine my.X; state<19| >foo<23| >actions 1, 2, 3;<43| >end ! -------------------------------------------------------------------------------- 19 1 " ": HiddenRegionReplacer: space=' ';indentInc=1 23 2 "n ": HiddenRegionReplacer: newLine=1 43 1 "n": HiddenRegionReplacer: newLine=1;indentDec=1 def dispatch void format(State state, extension IFormattableDocument document) { state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] state.regionFor.keyword("end").prepend[newLine] println(document.toString) } State: {State} 'state' name=ID ('actions' actions+=INT ("," actions+=INT)* ";")? 'end';
  109. 109. ----------- RootDocument with ITextReplacers (syntax: <offset|text>) ----------- machine my.X; state<19| >foo<23| >actions 1, 2, 3;<43| >end ! -------------------------------------------------------------------------------- 19 1 " ": HiddenRegionReplacer: space=' ';indentInc=1 23 2 "n ": HiddenRegionReplacer: newLine=1 43 1 "n": HiddenRegionReplacer: newLine=1;indentDec=1 def dispatch void format(State state, extension IFormattableDocument document) { state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] state.regionFor.keyword("end").prepend[newLine] println(document.toString) } State: {State} 'state' name=ID ('actions' actions+=INT ("," actions+=INT)* ";")? 'end';
  110. 110. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } API: specify formatting information for hidden regions Implementation: store text replacers for text regions
  111. 111. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! // ... } offset : int length: int newText: String ITextReplacement autowrap() setPriority(int priority) indent() newLine() setNewLines(int newLines) setNewLines(int min, int default, int max) noSpace() oneSpace() setSpace(String space) IHiddenRegionFormatter ( )*? Recap: The formatter outputs text replacements!
  112. 112. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer HiddenRegionReplacer autowrap() setPriority(int priority) indent() newLine() setNewLines(int newLines) setNewLines(int min, int default, int max) noSpace() oneSpace() setSpace(String space) IHiddenRegionFormatter configures configures a HiddenRegionReplacer
  113. 113. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! } creates TextReplacements createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer autowrap() setPriority(int priority) indent() newLine() setNewLines(int newLines) setNewLines(int min, int default, int max) noSpace() oneSpace() setSpace(String space) IHiddenRegionFormatter configures creates
  114. 114. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! } specific for TextRegion createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer autowrap() setPriority(int priority) indent() newLine() setNewLines(int newLines) setNewLines(int min, int default, int max) noSpace() oneSpace() setSpace(String space) IHiddenRegionFormatter configures offset : int length: int ITextRegion 1 1 creates
  115. 115. interface IFormattableDocument { ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer autowrap() setPriority(int priority) indent() newLine() setNewLines(int newLines) setNewLines(int min, int default, int max) noSpace() oneSpace() setSpace(String space) IHiddenRegionFormatter configures getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects context collects replacements
  116. 116. class FormattableDocument { ! Map<ITextRegion, ITextReplacer> replacers; ! def List<ITextReplacement> renderToTextReplacements() { val List<ITextReplacer> sorted = sortByOffset(replacers) var ITextReplacerContext context = createTextReplacerContext() for (ITextReplacer replacer : sorted) { context = replacer.createReplacements(context) } return context.getAllReplacements() } } ! ! ! ! ! ! class MyReplacer implements ITextReplacer { ! override ITextReplacerContext createReplacements(ITextReplacerContext context) { val ITextReplacement replacement = region.replaceWith(" ") context.addReplacement(replacement) val int oldIndentation = context.getIndentation() return context.withIndentation(oldIndentation + 1) } } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects
  117. 117. class FormattableDocument { ! Map<ITextRegion, ITextReplacer> replacers; ! def List<ITextReplacement> renderToTextReplacements() { val List<ITextReplacer> sorted = sortByOffset(replacers) var ITextReplacerContext context = createTextReplacerContext() for (ITextReplacer replacer : sorted) { context = replacer.createReplacements(context) } return context.getAllReplacements() } } ! ! ! ! ! ! class MyReplacer implements ITextReplacer { ! override ITextReplacerContext createReplacements(ITextReplacerContext context) { val ITextReplacement replacement = region.replaceWith(" ") context.addReplacement(replacement) val int oldIndentation = context.getIndentation() return context.withIndentation(oldIndentation + 1) } } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects sorted
  118. 118. class FormattableDocument { ! Map<ITextRegion, ITextReplacer> replacers; ! def List<ITextReplacement> renderToTextReplacements() { val List<ITextReplacer> sorted = sortByOffset(replacers) var ITextReplacerContext context = createTextReplacerContext() for (ITextReplacer replacer : sorted) { context = replacer.createReplacements(context) } return context.getAllReplacements() } } ! ! ! ! ! ! class MyReplacer implements ITextReplacer { ! override ITextReplacerContext createReplacements(ITextReplacerContext context) { val ITextReplacement replacement = region.replaceWith(" ") context.addReplacement(replacement) val int oldIndentation = context.getIndentation() return context.withIndentation(oldIndentation + 1) } } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects invoke in sorted order
  119. 119. class FormattableDocument { ! Map<ITextRegion, ITextReplacer> replacers; ! def List<ITextReplacement> renderToTextReplacements() { val List<ITextReplacer> sorted = sortByOffset(replacers) var ITextReplacerContext context = createTextReplacerContext() for (ITextReplacer replacer : sorted) { context = replacer.createReplacements(context) } return context.getAllReplacements() } } ! ! ! ! ! ! class MyReplacer implements ITextReplacer { ! override ITextReplacerContext createReplacements(ITextReplacerContext context) { val ITextReplacement replacement = region.replaceWith(" ") context.addReplacement(replacement) val int oldIndentation = context.getIndentation() return context.withIndentation(oldIndentation + 1) } } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects in/out context
  120. 120. class FormattableDocument { ! Map<ITextRegion, ITextReplacer> replacers; ! def List<ITextReplacement> renderToTextReplacements() { val List<ITextReplacer> sorted = sortByOffset(replacers) var ITextReplacerContext context = createTextReplacerContext() for (ITextReplacer replacer : sorted) { context = replacer.createReplacements(context) } return context.getAllReplacements() } } ! ! ! ! ! ! class MyReplacer implements ITextReplacer { ! override ITextReplacerContext createReplacements(ITextReplacerContext context) { val ITextReplacement replacement = region.replaceWith(" ") context.addReplacement(replacement) val int oldIndentation = context.getIndentation() return context.withIndentation(oldIndentation + 1) } } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects return replacements
  121. 121. class FormattableDocument { ! Map<ITextRegion, ITextReplacer> replacers; ! def List<ITextReplacement> renderToTextReplacements() { val List<ITextReplacer> sorted = sortByOffset(replacers) var ITextReplacerContext context = createTextReplacerContext() for (ITextReplacer replacer : sorted) { context = replacer.createReplacements(context) } return context.getAllReplacements() } } ! ! ! ! ! ! class MyReplacer implements ITextReplacer { ! override ITextReplacerContext createReplacements(ITextReplacerContext context) { val ITextReplacement replacement = region.replaceWith(" ") context.addReplacement(replacement) val int oldIndentation = context.getIndentation() return context.withIndentation(oldIndentation + 1) } } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects create replacement
  122. 122. class FormattableDocument { ! Map<ITextRegion, ITextReplacer> replacers; ! def List<ITextReplacement> renderToTextReplacements() { val List<ITextReplacer> sorted = sortByOffset(replacers) var ITextReplacerContext context = createTextReplacerContext() for (ITextReplacer replacer : sorted) { context = replacer.createReplacements(context) } return context.getAllReplacements() } } ! ! ! ! ! ! class MyReplacer implements ITextReplacer { ! override ITextReplacerContext createReplacements(ITextReplacerContext context) { val ITextReplacement replacement = region.replaceWith(" ") context.addReplacement(replacement) val int oldIndentation = context.getIndentation() return context.withIndentation(oldIndentation + 1) } } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects add to context
  123. 123. class FormattableDocument { ! Map<ITextRegion, ITextReplacer> replacers; ! def List<ITextReplacement> renderToTextReplacements() { val List<ITextReplacer> sorted = sortByOffset(replacers) var ITextReplacerContext context = createTextReplacerContext() for (ITextReplacer replacer : sorted) { context = replacer.createReplacements(context) } return context.getAllReplacements() } } ! ! ! ! ! ! class MyReplacer implements ITextReplacer { ! override ITextReplacerContext createReplacements(ITextReplacerContext context) { val ITextReplacement replacement = region.replaceWith(" ") context.addReplacement(replacement) val int oldIndentation = context.getIndentation() return context.withIndentation(oldIndentation + 1) } } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects return cloned context
  124. 124. class FormattableDocument { ! Map<ITextRegion, ITextReplacer> replacers; ! def List<ITextReplacement> renderToTextReplacements() { val List<ITextReplacer> sorted = sortByOffset(replacers) var ITextReplacerContext context = createTextReplacerContext() for (ITextReplacer replacer : sorted) { context = replacer.createReplacements(context) } return context.getAllReplacements() } } ! ! ! ! ! ! class MyReplacer implements ITextReplacer { ! override ITextReplacerContext createReplacements(ITextReplacerContext context) { val ITextReplacement replacement = region.replaceWith(" ") context.addReplacement(replacement) val int oldIndentation = context.getIndentation() return context.withIndentation(oldIndentation + 1) } } createReplacements(ITextReplacerContext context): ITextReplacerContext ITextReplacer offset : int length: int newText: String ITextReplacementHiddenRegionReplacer getIndentation(): int withIndentation(int indent): ITextReplacerContext getParent(): ITextReplacerContext addReplacement(ITextReplacement repl) getAllReplacements():List<ITextReplacement> ITextReplacerContext uses offset : int length: int ITextRegion 1 1 creates collects
  125. 125. EXAMPLES
  126. 126. state foo actions 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; end AutoWrap def dispatch void format(State state, extension IFormattableDocument document) { for (k : state.regionFor.keywords(“,")) { k.prepend[noSpace].append[oneSpace; autowrap] } } State: 'state' name=ID 'actions' actions+=INT ("," actions+=INT)* ";" 'end';
  127. 127. state foo actions 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; end AutoWrap def dispatch void format(State state, extension IFormattableDocument document) { for (k : state.regionFor.keywords(“,")) { k.prepend[noSpace].append[oneSpace; autowrap] } } State: 'state' name=ID 'actions' actions+=INT ("," actions+=INT)* ";" 'end';
  128. 128. def dispatch void format(State state, extension IFormattableDocument document) { val IAutowrapFormatter af = [ region, wrapped, doc | val first = (region as IWhitespace).hiddenRegion val last = state.regionFor.keyword(";").previousHiddenRegion doc.set(first, last, [indent]) ] for (k : state.regionFor.keywords(",")) { k.prepend[noSpace].append[oneSpace; autowrap; onAutowrap = af] } } AutoWrap with Handler State: 'state' name=ID 'actions' actions+=INT ("," actions+=INT)* ";" 'end'; state foo actions 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; end
  129. 129. def dispatch void format(State state, extension IFormattableDocument document) { val IAutowrapFormatter af = [ region, wrapped, doc | val first = (region as IWhitespace).hiddenRegion val last = state.regionFor.keyword(";").previousHiddenRegion doc.set(first, last, [indent]) ] for (k : state.regionFor.keywords(",")) { k.prepend[noSpace].append[oneSpace; autowrap; onAutowrap = af] } } AutoWrap with Handler State: 'state' name=ID 'actions' actions+=INT ("," actions+=INT)* ";" 'end'; state foo actions 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; end
  130. 130. def dispatch void format(State state, extension IFormattableDocument document) { val IAutowrapFormatter af = [ region, wrapped, doc | val first = (region as IWhitespace).hiddenRegion val last = state.regionFor.keyword(";").previousHiddenRegion doc.set(first, last, [indent]) ] for (k : state.regionFor.keywords(",")) { k.prepend[noSpace].append[oneSpace; autowrap; onAutowrap = af] } } AutoWrap with Handler State: 'state' name=ID 'actions' actions+=INT ("," actions+=INT)* ";" 'end'; state foo actions 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; end
  131. 131. def dispatch void format(State state, extension IFormattableDocument document) { val IAutowrapFormatter af = [ region, wrapped, doc | val first = (region as IWhitespace).hiddenRegion val last = state.regionFor.keyword(";").previousHiddenRegion doc.set(first, last, [indent]) ] for (k : state.regionFor.keywords(",")) { k.prepend[noSpace].append[oneSpace; autowrap; onAutowrap = af] } } AutoWrap with Handler State: 'state' name=ID 'actions' actions+=INT ("," actions+=INT)* ";" 'end'; state foo actions 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; end
  132. 132. Conditional Formatting state bar 1 => foo; 2 => foo; 3 => foo; 4 => foo; 5 => foo; end state bar 1 => foo; 2 => foo; 3 => foo; end def dispatch void format(State state, extension IFormattableDocument document) { state.formatConditionally( [ doc | val extension slDoc = doc.requireFitsInLine state.regionFor.feature(STATE__NAME).surround[oneSpace] for (t : state.transitions) { t.format.append[oneSpace] } ], [ extension doc | state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] for (t : state.transitions) { t.format.append[newLine] } ] ) } State: 'state' name=ID transitions+=Transition* 'end';
  133. 133. Conditional Formatting state bar 1 => foo; 2 => foo; 3 => foo; 4 => foo; 5 => foo; end state bar 1 => foo; 2 => foo; 3 => foo; end def dispatch void format(State state, extension IFormattableDocument document) { state.formatConditionally( [ doc | val extension slDoc = doc.requireFitsInLine state.regionFor.feature(STATE__NAME).surround[oneSpace] for (t : state.transitions) { t.format.append[oneSpace] } ], [ extension doc | state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] for (t : state.transitions) { t.format.append[newLine] } ] ) } State: 'state' name=ID transitions+=Transition* 'end'; XOR
  134. 134. Conditional Formatting state bar 1 => foo; 2 => foo; 3 => foo; 4 => foo; 5 => foo; end state bar 1 => foo; 2 => foo; 3 => foo; end def dispatch void format(State state, extension IFormattableDocument document) { state.formatConditionally( [ doc | val extension slDoc = doc.requireFitsInLine state.regionFor.feature(STATE__NAME).surround[oneSpace] for (t : state.transitions) { t.format.append[oneSpace] } ], [ extension doc | state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] for (t : state.transitions) { t.format.append[newLine] } ] ) } State: 'state' name=ID transitions+=Transition* 'end';
  135. 135. Conditional Formatting state bar 1 => foo; 2 => foo; 3 => foo; 4 => foo; 5 => foo; end state bar 1 => foo; 2 => foo; 3 => foo; end def dispatch void format(State state, extension IFormattableDocument document) { state.formatConditionally( [ doc | val extension slDoc = doc.requireFitsInLine state.regionFor.feature(STATE__NAME).surround[oneSpace] for (t : state.transitions) { t.format.append[oneSpace] } ], [ extension doc | state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] for (t : state.transitions) { t.format.append[newLine] } ] ) } State: 'state' name=ID transitions+=Transition* 'end';
  136. 136. Conditional Formatting state bar 1 => foo; 2 => foo; 3 => foo; 4 => foo; 5 => foo; end state bar 1 => foo; 2 => foo; 3 => foo; end def dispatch void format(State state, extension IFormattableDocument document) { state.formatConditionally( [ doc | val extension slDoc = doc.requireFitsInLine state.regionFor.feature(STATE__NAME).surround[oneSpace] for (t : state.transitions) { t.format.append[oneSpace] } ], [ extension doc | state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] for (t : state.transitions) { t.format.append[newLine] } ] ) } State: 'state' name=ID transitions+=Transition* 'end';
  137. 137. Pattern-Aware formatting def dispatch void format(State state, extension IFormattableDocument document) { if (state.regionFor.keyword("end").previousHiddenRegion.isMultiline) { state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] for (t : state.transitions) { t.format.append[newLine] } } else { state.regionFor.feature(STATE__NAME).surround[oneSpace] for (t : state.transitions) { t.format.append[oneSpace] } } } state bar 1 => foo; end state bar 1 => foo; end State: 'state' name=ID transitions+=Transition* 'end';
  138. 138. Pattern-Aware formatting def dispatch void format(State state, extension IFormattableDocument document) { if (state.regionFor.keyword("end").previousHiddenRegion.isMultiline) { state.interior[indent] state.regionFor.feature(STATE__NAME).prepend[oneSpace].append[newLine] for (t : state.transitions) { t.format.append[newLine] } } else { state.regionFor.feature(STATE__NAME).surround[oneSpace] for (t : state.transitions) { t.format.append[oneSpace] } } } State: 'state' name=ID transitions+=Transition* 'end'; state bar 1 => foo; end state bar 1 => foo; end
  139. 139. Table-based formatting state baz 1 => foo; 12 => foo; 123 => foo; end def dispatch void format(State state, extension IFormattableDocument document) { state.interior[indent] val width = state.transitions.map[regionFor.feature(TRANSITION__EVENT).length].max + 1; for (t : state.transitions) { val region = t.regionFor.feature(TRANSITION__EVENT) region.append[space = Strings.repeat(" ", width - region.length)] t.regionFor.keyword(";").prepend[noSpace].append[newLine] } } State: 'state' name=ID transitions+=Transition* 'end';
  140. 140. Table-based formatting state baz 1 => foo; 12 => foo; 123 => foo; end def dispatch void format(State state, extension IFormattableDocument document) { state.interior[indent] val width = state.transitions.map[regionFor.feature(TRANSITION__EVENT).length].max + 1; for (t : state.transitions) { val region = t.regionFor.feature(TRANSITION__EVENT) region.append[space = Strings.repeat(" ", width - region.length)] t.regionFor.keyword(";").prepend[noSpace].append[newLine] } } State: 'state' name=ID transitions+=Transition* 'end';
  141. 141. Table-based formatting state baz 1 => foo; 12 => foo; 123 => foo; end def dispatch void format(State state, extension IFormattableDocument document) { state.interior[indent] val width = state.transitions.map[regionFor.feature(TRANSITION__EVENT).length].max + 1; for (t : state.transitions) { val region = t.regionFor.feature(TRANSITION__EVENT) region.append[space = Strings.repeat(" ", width - region.length)] t.regionFor.keyword(";").prepend[noSpace].append[newLine] } } State: 'state' name=ID transitions+=Transition* 'end';
  142. 142. TESTS
  143. 143. org.eclipse.xtext.junit4.formatter.FormatterTester @Test def void example1() { assertFormatted[ toBeFormatted = ''' entity Foo { propertyName:String op name() { } } ''' ] } @Test def void example2() { assertFormatted[ expectation = ''' entity Foo { op foo():String { "xx" } } ''' toBeFormatted = ''' entity Foo { op foo ( ) : String { "xx" } } ''' ] }
  144. 144. THE OLD FORMATTER • no access to text, Node Model or AST • not enhanceable engine • will be deprecated
  145. 145. RELEASES • Alpha release as part of Xtext 2.8.0, 2.8.1, 2.8.3 • Beta release as of Xtext 2.9-beta • stable (public API) release (hopefully) with Xtext 2.9
  146. 146. Questions?

×