Skip to content

Commit b6e0589

Browse files
authored
Merge pull request #35 from wbm-mkopp/feat/twig-block-versioning
feat: twig block hashing
2 parents f557262 + 2351634 commit b6e0589

14 files changed

Lines changed: 1352 additions & 39 deletions

File tree

internal/lsp/codeaction/twig_codeaction.go

Lines changed: 252 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,31 @@ import (
77
"github.com/shopware/shopware-lsp/internal/lsp"
88
"github.com/shopware/shopware-lsp/internal/lsp/protocol"
99
treesitterhelper "github.com/shopware/shopware-lsp/internal/tree_sitter_helper"
10+
"github.com/shopware/shopware-lsp/internal/twig"
11+
tree_sitter "github.com/tree-sitter/go-tree-sitter"
1012
)
1113

1214
type TwigCodeActionProvider struct {
15+
twigIndexer *twig.TwigIndexer
16+
projectRoot string
1317
}
1418

15-
func NewTwigCodeActionProvider(server *lsp.Server) *TwigCodeActionProvider {
16-
return &TwigCodeActionProvider{}
19+
func NewTwigCodeActionProvider(projectRoot string, server *lsp.Server) *TwigCodeActionProvider {
20+
indexer, ok := server.GetIndexer("twig.indexer")
21+
if !ok {
22+
return &TwigCodeActionProvider{twigIndexer: nil, projectRoot: projectRoot}
23+
}
24+
twigIndexer, ok := indexer.(*twig.TwigIndexer)
25+
if !ok {
26+
return &TwigCodeActionProvider{twigIndexer: nil, projectRoot: projectRoot}
27+
}
28+
return &TwigCodeActionProvider{twigIndexer: twigIndexer, projectRoot: projectRoot}
1729
}
1830

1931
func (p *TwigCodeActionProvider) GetCodeActionKinds() []protocol.CodeActionKind {
2032
return []protocol.CodeActionKind{
2133
protocol.CodeActionRefactorExtract,
34+
protocol.CodeActionQuickFix,
2235
}
2336
}
2437

@@ -27,27 +40,250 @@ func (p *TwigCodeActionProvider) GetCodeActions(ctx context.Context, params *pro
2740
return nil
2841
}
2942

30-
if !strings.Contains(params.TextDocument.URI, "Resources/views/storefront") {
43+
var codeActions []protocol.CodeAction
44+
45+
if IsBlock().Matches(params.Node, params.DocumentContent) {
46+
if strings.Contains(params.TextDocument.URI, "Resources/views/storefront") {
47+
textValue := treesitterhelper.GetNodeText(params.Node, params.DocumentContent)
48+
49+
codeActions = append(codeActions, protocol.CodeAction{
50+
Title: "Overwrite this block in Extension",
51+
Kind: protocol.CodeActionRefactorExtract,
52+
Command: &protocol.CommandAction{
53+
Title: "Overwrite Block",
54+
Command: "shopware.twig.extendBlock",
55+
Arguments: []any{params.TextDocument.URI, textValue},
56+
},
57+
})
58+
}
59+
60+
if action := p.getVersioningHashAction(params); action != nil {
61+
codeActions = append(codeActions, *action)
62+
}
63+
64+
if action := p.getShowDiffAction(params); action != nil {
65+
codeActions = append(codeActions, *action)
66+
}
67+
}
68+
69+
if action := p.getShowDiffActionFromComment(params); action != nil {
70+
codeActions = append(codeActions, *action)
71+
}
72+
73+
return codeActions
74+
}
75+
76+
func (p *TwigCodeActionProvider) getVersioningHashAction(params *protocol.CodeActionParams) *protocol.CodeAction {
77+
if p.twigIndexer == nil {
78+
return nil
79+
}
80+
81+
if twig.IsStorefrontTemplate(params.TextDocument.URI) {
3182
return nil
3283
}
3384

34-
var codeActions []protocol.CodeAction
85+
blockNode := params.Node.Parent()
86+
if blockNode == nil || blockNode.Kind() != "block" {
87+
return nil
88+
}
3589

36-
if IsBlock().Matches(params.Node, params.DocumentContent) {
37-
textValue := treesitterhelper.GetNodeText(params.Node, params.DocumentContent)
38-
39-
codeActions = append(codeActions, protocol.CodeAction{
40-
Title: "Overwrite this block in Extension",
41-
Kind: protocol.CodeActionRefactorExtract,
42-
Command: &protocol.CommandAction{
43-
Title: "Overwrite Block",
44-
Command: "shopware.twig.extendBlock",
45-
Arguments: []any{params.TextDocument.URI, textValue},
90+
blockName := treesitterhelper.GetNodeText(params.Node, params.DocumentContent)
91+
92+
if p.hasVersioningComment(blockNode, params.DocumentContent) {
93+
return nil
94+
}
95+
96+
allBlockHashes, err := p.twigIndexer.GetTwigBlockHashes(blockName)
97+
if err != nil || len(allBlockHashes) == 0 {
98+
return nil
99+
}
100+
101+
originalHash := twig.FindOriginalStorefrontHash(allBlockHashes)
102+
if originalHash == nil {
103+
return nil
104+
}
105+
106+
blockLine := int(blockNode.Range().StartPoint.Row)
107+
versionComment := twig.FormatVersionComment(originalHash.Hash, twig.DetectShopwareVersion(p.projectRoot))
108+
109+
edit := &protocol.WorkspaceEdit{
110+
Changes: map[string][]protocol.TextEdit{
111+
params.TextDocument.URI: {
112+
{
113+
Range: protocol.Range{
114+
Start: protocol.Position{Line: blockLine, Character: 0},
115+
End: protocol.Position{Line: blockLine, Character: 0},
116+
},
117+
NewText: versionComment,
118+
},
46119
},
47-
})
120+
},
48121
}
49122

50-
return codeActions
123+
return &protocol.CodeAction{
124+
Title: "Add twig versioning hash",
125+
Kind: protocol.CodeActionQuickFix,
126+
Edit: edit,
127+
}
128+
}
129+
130+
func (p *TwigCodeActionProvider) getShowDiffAction(params *protocol.CodeActionParams) *protocol.CodeAction {
131+
if p.twigIndexer == nil {
132+
return nil
133+
}
134+
135+
if twig.IsStorefrontTemplate(params.TextDocument.URI) {
136+
return nil
137+
}
138+
139+
blockNode := params.Node.Parent()
140+
if blockNode == nil || blockNode.Kind() != "block" {
141+
return nil
142+
}
143+
144+
blockName := treesitterhelper.GetNodeText(params.Node, params.DocumentContent)
145+
146+
rootNode := params.Node
147+
for rootNode.Parent() != nil {
148+
rootNode = rootNode.Parent()
149+
}
150+
151+
twigFile, err := twig.ParseTwig(params.TextDocument.URI, rootNode, params.DocumentContent)
152+
if err != nil {
153+
return nil
154+
}
155+
156+
block, exists := twigFile.Blocks[blockName]
157+
if !exists || block.VersionComment == nil {
158+
return nil
159+
}
160+
161+
allBlockHashes, err := p.twigIndexer.GetTwigBlockHashes(blockName)
162+
if err != nil || len(allBlockHashes) == 0 {
163+
return nil
164+
}
165+
166+
originalHash := twig.FindOriginalStorefrontHash(allBlockHashes)
167+
if originalHash == nil {
168+
return nil
169+
}
170+
171+
if block.VersionComment.Hash == originalHash.Hash {
172+
return nil
173+
}
174+
175+
return &protocol.CodeAction{
176+
Title: "Show block difference",
177+
Kind: protocol.CodeActionQuickFix,
178+
Command: &protocol.CommandAction{
179+
Title: "Show Block Difference",
180+
Command: "shopware.twig.showBlockDiff",
181+
Arguments: []any{params.TextDocument.URI, blockName},
182+
},
183+
}
184+
}
185+
186+
func (p *TwigCodeActionProvider) getShowDiffActionFromComment(params *protocol.CodeActionParams) *protocol.CodeAction {
187+
if p.twigIndexer == nil {
188+
return nil
189+
}
190+
191+
if twig.IsStorefrontTemplate(params.TextDocument.URI) {
192+
return nil
193+
}
194+
195+
if params.Node.Kind() != "comment" {
196+
return nil
197+
}
198+
199+
commentText := string(params.Node.Utf8Text(params.DocumentContent))
200+
if !strings.Contains(commentText, twig.VersionCommentPrefix) {
201+
return nil
202+
}
203+
204+
versionComment := twig.ParseVersionComment(commentText, int(params.Node.Range().StartPoint.Row)+1)
205+
if versionComment == nil {
206+
return nil
207+
}
208+
209+
commentLine := int(params.Node.Range().StartPoint.Row) + 1
210+
211+
rootNode := params.Node
212+
for rootNode.Parent() != nil {
213+
rootNode = rootNode.Parent()
214+
}
215+
216+
twigFile, err := twig.ParseTwig(params.TextDocument.URI, rootNode, params.DocumentContent)
217+
if err != nil {
218+
return nil
219+
}
220+
221+
var blockName string
222+
for _, block := range twigFile.Blocks {
223+
if block.VersionComment != nil && block.VersionComment.Line == commentLine {
224+
blockName = block.Name
225+
break
226+
}
227+
}
228+
229+
if blockName == "" {
230+
return nil
231+
}
232+
233+
allBlockHashes, err := p.twigIndexer.GetTwigBlockHashes(blockName)
234+
if err != nil || len(allBlockHashes) == 0 {
235+
return nil
236+
}
237+
238+
originalHash := twig.FindOriginalStorefrontHash(allBlockHashes)
239+
if originalHash == nil {
240+
return nil
241+
}
242+
243+
if versionComment.Hash == originalHash.Hash {
244+
return nil
245+
}
246+
247+
return &protocol.CodeAction{
248+
Title: "Show block difference",
249+
Kind: protocol.CodeActionQuickFix,
250+
Command: &protocol.CommandAction{
251+
Title: "Show Block Difference",
252+
Command: "shopware.twig.showBlockDiff",
253+
Arguments: []any{params.TextDocument.URI, blockName},
254+
},
255+
}
256+
}
257+
258+
func (p *TwigCodeActionProvider) hasVersioningComment(blockNode *tree_sitter.Node, content []byte) bool {
259+
parent := blockNode.Parent()
260+
if parent == nil {
261+
return false
262+
}
263+
264+
blockStartLine := blockNode.Range().StartPoint.Row
265+
266+
for i := 0; i < int(parent.NamedChildCount()); i++ {
267+
child := parent.NamedChild(uint(i))
268+
269+
if child.Range().StartPoint.Row == blockNode.Range().StartPoint.Row &&
270+
child.Range().StartPoint.Column == blockNode.Range().StartPoint.Column {
271+
if i > 0 {
272+
prevSibling := parent.NamedChild(uint(i - 1))
273+
if prevSibling.Kind() == "comment" {
274+
commentEndLine := prevSibling.Range().EndPoint.Row
275+
if blockStartLine-commentEndLine <= 1 {
276+
commentText := string(prevSibling.Utf8Text(content))
277+
if strings.Contains(commentText, twig.VersionCommentPrefix) {
278+
return true
279+
}
280+
}
281+
}
282+
}
283+
break
284+
}
285+
}
286+
return false
51287
}
52288

53289
func IsBlock() treesitterhelper.Pattern {

0 commit comments

Comments
 (0)