Skip to content

Commit 636a620

Browse files
authored
Merge pull request #28 from lstreckeisen/CMI-61-FR-1.5-Folding
Cmi 61 fr 1.5 folding
2 parents a1e17a4 + 624d308 commit 636a620

File tree

4 files changed

+204
-1
lines changed

4 files changed

+204
-1
lines changed

src/language/ContextMapperDslModule.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { SemanticTokenProviderRegistry } from './semantictokens/SemanticTokenPro
1414
import { ContextMapperDslValidationRegistry } from './validation/ContextMapperDslValidationRegistry.js'
1515
import { ContextMapperValidationProviderRegistry } from './validation/ContextMapperValidationProviderRegistry.js'
1616
import { ContextMapperDslScopeProvider } from './references/ContextMapperDslScopeProvider.js'
17+
import { ContextMapperDslFoldingRangeProvider } from './folding/ContextMapperDslFoldingRageProvider.js'
1718

1819
/**
1920
* Declaration of custom services - add your own service classes here.
@@ -47,7 +48,8 @@ export const ContextMapperDslModule: Module<ContextMapperDslServices, ModuleType
4748
ValidationRegistry: (services) => new ContextMapperDslValidationRegistry(services)
4849
},
4950
lsp: {
50-
SemanticTokenProvider: (services) => new ContextMapperDslSemanticTokenProvider(services, semanticTokenProviderRegistry)
51+
SemanticTokenProvider: (services) => new ContextMapperDslSemanticTokenProvider(services, semanticTokenProviderRegistry),
52+
FoldingRangeProvider: (services) => new ContextMapperDslFoldingRangeProvider(services)
5153
},
5254
references: {
5355
ScopeProvider: (services) => new ContextMapperDslScopeProvider(services)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { DefaultFoldingRangeProvider } from 'langium/lsp'
2+
import { CstNode, LangiumDocument } from 'langium'
3+
import { FoldingRange, FoldingRangeKind } from 'vscode-languageserver-types'
4+
5+
export class ContextMapperDslFoldingRangeProvider extends DefaultFoldingRangeProvider {
6+
// modified version of DefaultFoldingRangeProvider#toFoldingRange
7+
protected override toFoldingRange (document: LangiumDocument, node: CstNode, kind?: string): FoldingRange | undefined {
8+
const range = node.range
9+
let start = range.start
10+
let end = range.end
11+
12+
// Don't generate foldings for nodes that are less than 3 lines
13+
if (end.line - start.line < 2) {
14+
return undefined
15+
}
16+
17+
if (!this.includeLastFoldingLine(node, kind)) { // checks if block is parenthesis/bracket/brace block or comment
18+
// To prevent something like '...}' in the editor when a code block is collapsed, we want to still show the first line of a code block
19+
start = document.textDocument.positionAt(document.textDocument.offsetAt({
20+
line: start.line + 1,
21+
character: 0
22+
}) - 1)
23+
24+
let offsetFromEnd
25+
if (kind === FoldingRangeKind.Comment) {
26+
// So that comments are collapsed like '/*...*/' the end character needs to be at the start of the last line
27+
offsetFromEnd = 0
28+
} else {
29+
// As we don't want to hide the end token like 'if { ... --> } <--', we simply select the end of the previous line as the end position
30+
offsetFromEnd = 1
31+
}
32+
end = document.textDocument.positionAt(document.textDocument.offsetAt({
33+
line: end.line,
34+
character: 0
35+
}) - offsetFromEnd)
36+
}
37+
return FoldingRange.create(start.line, end.line, start.character, end.character, kind)
38+
}
39+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { createContextMapperDslServices } from '../../src/language/ContextMapperDslModule.js'
2+
import { FoldingRangeProvider } from 'langium/lsp'
3+
import { clearDocuments, parseHelper } from 'langium/test'
4+
import { ContextMappingModel } from '../../src/language/generated/ast.js'
5+
import { EmptyFileSystem, LangiumDocument } from 'langium'
6+
import { afterEach, beforeAll, describe, expect, test } from 'vitest'
7+
import { parseValidInput } from '../ParsingTestHelper.js'
8+
import { FoldingRangeParams } from 'vscode-languageserver'
9+
10+
let services: ReturnType<typeof createContextMapperDslServices>
11+
let foldingRangeProvider: FoldingRangeProvider
12+
let parse: ReturnType<typeof parseHelper<ContextMappingModel>>
13+
let document: LangiumDocument<ContextMappingModel> | undefined
14+
15+
beforeAll(async () => {
16+
services = createContextMapperDslServices(EmptyFileSystem)
17+
foldingRangeProvider = services.ContextMapperDsl.lsp.FoldingRangeProvider!
18+
parse = parseHelper<ContextMappingModel>(services.ContextMapperDsl)
19+
})
20+
21+
afterEach(async () => {
22+
document && await clearDocuments(services.shared, [document])
23+
})
24+
25+
describe('Comment block folding tests', () => {
26+
test('check folding range of comment block', async () => {
27+
document = await parseValidInput(parse, `
28+
/*
29+
This is a comment block
30+
*/
31+
BoundedContext TestContext
32+
`)
33+
34+
const params = createFoldingRangeParams(document)
35+
const foldingRanges = await foldingRangeProvider.getFoldingRanges(document, params)
36+
expect(foldingRanges).toHaveLength(1)
37+
expect(foldingRanges[0].startLine).toEqual(1)
38+
expect(foldingRanges[0].startCharacter).toEqual(8)
39+
expect(foldingRanges[0].endLine).toEqual(3)
40+
expect(foldingRanges[0].endCharacter).toEqual(0)
41+
})
42+
43+
test('check folding range of multiline comment', async () => {
44+
document = await parseValidInput(parse, `
45+
BoundedContext TestContext {
46+
/* This is a
47+
looooonger
48+
multiline comment
49+
*/
50+
Aggregate TestAggregate
51+
}
52+
`)
53+
54+
const params = createFoldingRangeParams(document)
55+
const foldingRanges = await foldingRangeProvider.getFoldingRanges(document, params)
56+
expect(foldingRanges).toHaveLength(2)
57+
expect(foldingRanges[1].startLine).toEqual(2)
58+
expect(foldingRanges[1].startCharacter).toEqual(20)
59+
expect(foldingRanges[1].endLine).toEqual(5)
60+
expect(foldingRanges[1].endCharacter).toEqual(0)
61+
})
62+
63+
test('check folding range of single-line comment', async () => {
64+
document = await parseValidInput(parse, `
65+
// This is a single-line comment
66+
BoundedContext TestContext
67+
`)
68+
69+
const params = createFoldingRangeParams(document)
70+
const foldingRanges = await foldingRangeProvider.getFoldingRanges(document, params)
71+
expect(foldingRanges).toHaveLength(0)
72+
})
73+
74+
test('check folding range of single line comment block', async () => {
75+
document = await parseValidInput(parse, `
76+
/* This is a single-line comment */
77+
BoundedContext TestContext
78+
`)
79+
80+
const params = createFoldingRangeParams(document)
81+
const foldingRanges = await foldingRangeProvider.getFoldingRanges(document, params)
82+
expect(foldingRanges).toHaveLength(0)
83+
})
84+
})
85+
86+
function createFoldingRangeParams (document: LangiumDocument<ContextMappingModel>): FoldingRangeParams {
87+
return {
88+
textDocument: {
89+
uri: document.uri.path
90+
}
91+
}
92+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { afterEach, beforeAll, describe, expect, test } from 'vitest'
2+
import { createContextMapperDslServices } from '../../src/language/ContextMapperDslModule.js'
3+
import { EmptyFileSystem, LangiumDocument } from 'langium'
4+
import { FoldingRangeProvider } from 'langium/lsp'
5+
import { clearDocuments, parseHelper } from 'langium/test'
6+
import { ContextMappingModel } from '../../src/language/generated/ast.js'
7+
import { parseValidInput } from '../ParsingTestHelper.js'
8+
import { FoldingRangeParams } from 'vscode-languageserver'
9+
10+
let services: ReturnType<typeof createContextMapperDslServices>
11+
let foldingRangeProvider: FoldingRangeProvider
12+
let parse: ReturnType<typeof parseHelper<ContextMappingModel>>
13+
let document: LangiumDocument<ContextMappingModel> | undefined
14+
15+
beforeAll(async () => {
16+
services = createContextMapperDslServices(EmptyFileSystem)
17+
foldingRangeProvider = services.ContextMapperDsl.lsp.FoldingRangeProvider!
18+
parse = parseHelper<ContextMappingModel>(services.ContextMapperDsl)
19+
})
20+
21+
afterEach(async () => {
22+
document && await clearDocuments(services.shared, [document])
23+
})
24+
25+
describe('Definition block folding tests', () => {
26+
test('check folding range of definition without body', async () => {
27+
document = await parseValidInput(parse, `
28+
BoundedContext TestContext
29+
`)
30+
31+
const params = createFoldingRangeParams(document)
32+
const foldingRanges = await foldingRangeProvider.getFoldingRanges(document, params)
33+
expect(foldingRanges).toHaveLength(0)
34+
})
35+
36+
test('check folding range of definition with one line body', async () => {
37+
document = await parseValidInput(parse, `
38+
BoundedContext TestContext {
39+
}
40+
`)
41+
42+
const params = createFoldingRangeParams(document)
43+
const foldingRanges = await foldingRangeProvider.getFoldingRanges(document, params)
44+
expect(foldingRanges).toHaveLength(0)
45+
})
46+
47+
test('check folding range of definition with two line body', async () => {
48+
document = await parseValidInput(parse, `
49+
BoundedContext TestContext {
50+
type TEAM
51+
}
52+
`)
53+
54+
const params = createFoldingRangeParams(document)
55+
const foldingRanges = await foldingRangeProvider.getFoldingRanges(document, params)
56+
expect(foldingRanges).toHaveLength(1)
57+
expect(foldingRanges[0].startLine).toEqual(1)
58+
expect(foldingRanges[0].startCharacter).toEqual(34)
59+
expect(foldingRanges[0].endLine).toEqual(2)
60+
expect(foldingRanges[0].endCharacter).toEqual(17)
61+
})
62+
})
63+
64+
function createFoldingRangeParams (document: LangiumDocument<ContextMappingModel>): FoldingRangeParams {
65+
return {
66+
textDocument: {
67+
uri: document.uri.path
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)