Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Epic - Mejoras autocompletado #106

Merged
merged 30 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8289c59
First step: removing templates and adding custom snippets
fdodino Oct 8, 2023
33ea908
Fix autocomplete node selection
fdodino Oct 8, 2023
5083773
WIP: adding TODOs
fdodino Oct 8, 2023
2f572f5
Add pending autocompletes
fdodino Oct 9, 2023
eaf676b
fix integration tests
fdodino Oct 10, 2023
bedcb27
back to deleted test
fdodino Oct 10, 2023
ce3783a
Added unit tests for all elements that have autocomplete
fdodino Oct 11, 2023
983e566
fix #85 - filtering symbol messages
fdodino Oct 12, 2023
bf80bab
Fix #53 - autocomplete con prioridades
fdodino Oct 12, 2023
a5e8a2f
Autocomplete: new & literal revamped
fdodino Oct 13, 2023
630366b
Refactored sort methods & added reference classes autocomplete
fdodino Oct 13, 2023
abb310c
Add initializers for New & fix autocomplete for references
fdodino Oct 13, 2023
1673be1
Add all kind of references (singleton, attributes & classes)
fdodino Oct 13, 2023
113ad28
Object methods shoud place last in autocomplete
fdodino Oct 13, 2023
834a48b
Fix autocomplete for singleton references
fdodino Oct 13, 2023
f04b79f
handle failure & remove duplication
fdodino Oct 14, 2023
d7aec1c
remove custom method and use existing objectClass
fdodino Oct 14, 2023
03fac26
Import autocomplete
fdodino Oct 14, 2023
c129233
better performance metrics
fdodino Oct 14, 2023
15df962
Add tests for message completion
fdodino Oct 15, 2023
562f981
Add tests for message completion
fdodino Oct 15, 2023
1cf9dc5
add tests for list & set
fdodino Oct 15, 2023
2dc44f5
Add tests for singleton autocomplete
fdodino Oct 15, 2023
7ac22d4
Add tests: singleton + default case
fdodino Oct 15, 2023
ba110c9
Add tests for new node
fdodino Oct 16, 2023
d6e964a
Add test for reference inside imports
fdodino Oct 16, 2023
9a51ab6
Add last tests and fix sort text in tests
fdodino Oct 16, 2023
badc300
Fix integration tests
fdodino Oct 16, 2023
1b1cf46
PR Review fixes
fdodino Oct 20, 2023
aa382bb
Removing unnecessary test
fdodino Oct 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "wollok-linter-client",
"name": "wollok-lsp-ide-client",
"description": "VSCode part of a language server",
"author": "Microsoft Corporation",
"license": "LGPL-3.0",
Expand Down
82 changes: 61 additions & 21 deletions client/src/test/completion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,71 @@ import {
} from 'vscode'
import { getDocumentURI, activate } from './helper'

const WOLLOK_AUTOCOMPLETE = 'wollok_autocomplete'

suite('Should do completion', () => {
const docUri = getDocumentURI('completion.wlk')
const fileCompletion = {

const fileSnippets = {
items: [
{ label: 'class', kind: CompletionItemKind.Class },
{ label: 'describe', kind: CompletionItemKind.Event },
{ label: 'method (with effect)', kind: CompletionItemKind.Method },
{ label: 'method (without effect)', kind: CompletionItemKind.Method },
{ label: 'object', kind: CompletionItemKind.Text },
{ label: 'test', kind: CompletionItemKind.Event },
{
label: "import",
kind: CompletionItemKind.File,
}, {
label: "const attribute",
kind: CompletionItemKind.Field,
}, {
label: "object",
kind: CompletionItemKind.Module,
}, {
label: "class",
kind: CompletionItemKind.Class,
},
],
}

test('Completes Wollok file', async () => {
await testCompletion(docUri, new Position(0, 0), fileCompletion)
test('Completes Wollok definition file', async () => {
await testCompletion(getDocumentURI('completion.wlk'), new Position(0, 0), fileSnippets)
})

test('Completes Wollok test file', async () => {
await testCompletion(getDocumentURI('completionTest.wtest'), new Position(0, 0), { items: [
{
label:"import",
kind: CompletionItemKind.File,
}, {
label: "const attribute",
kind: CompletionItemKind.Field,
}, {
label:"object",
kind: CompletionItemKind.Module,
}, {
label:"class",
kind: CompletionItemKind.Class,
}, {
label: "describe",
kind: CompletionItemKind.Folder,
}, {
label: "test",
kind: CompletionItemKind.Event,
},
],
})
})

test('Completes unparsed node', async () => {
await testCompletion(docUri, new Position(2, 3), fileCompletion)
test('Completes Wollok program file', async () => {
await testCompletion(getDocumentURI('completionProgram.wpgm'), new Position(0, 0), { items: [
{
label:"import",
kind: CompletionItemKind.File,
}, {
label: "const attribute",
kind: CompletionItemKind.Field,
}, {
label: "program",
kind: CompletionItemKind.Unit,
},
],
})
})

})

async function testCompletion(
Expand All @@ -40,22 +83,19 @@ async function testCompletion(
await activate(docUri)

// Executing the command `executeCompletionItemProvider` to simulate triggering completion
const actualCompletionList = (await commands.executeCommand(
const wollokCompletionList = (await commands.executeCommand(
'vscode.executeCompletionItemProvider',
docUri,
position,
)) as CompletionList

const wollokCompletionList = actualCompletionList.items.filter(
(completionElement) => completionElement.detail === WOLLOK_AUTOCOMPLETE,
)
assert.equal(
expectedCompletionList.items.length,
wollokCompletionList.length,
JSON.stringify(actualCompletionList),
wollokCompletionList.items.length,
JSON.stringify(wollokCompletionList),
)
expectedCompletionList.items.forEach((expectedItem, i) => {
const actualItem = wollokCompletionList[i]
const actualItem = wollokCompletionList.items[i]
assert.equal(actualItem.label, expectedItem.label)
assert.equal(actualItem.kind, expectedItem.kind)
})
Expand Down
Empty file.
Empty file.
1 change: 1 addition & 0 deletions client/testFixture/pepita.wlk
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ object Pepita {
}

class a {}

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
{
"command": "wollok.start.repl",
"title": "Start a new REPL session",
"category": "Wollok"
"category": "Wollok"
},
{
"command": "wollok.run.allTests",
Expand All @@ -145,7 +145,7 @@
"lint-staged": "lint-staged"
},
"dependencies": {
"wollok-ts": "4.0.4"
"wollok-ts": "4.0.5"
},
"devDependencies": {
"@types/expect": "^24.3.0",
Expand Down
2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wollok-lsp-ide-server",
"description": "Wollok Linter - LSP implementation in node.",
"description": "Wollok IDE - LSP implementation in node.",
"version": "0.0.1",
"author": "Uqbar Foundation",
"license": "LGPL-3.0",
Expand Down
92 changes: 88 additions & 4 deletions server/src/functionalities/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { CompletionItem, CompletionItemKind, InsertTextFormat } from 'vscode-languageserver'
import { Field, Method, Module, Name, Node, Parameter, Singleton } from 'wollok-ts'
import { Class, Entity, Field, Method, Mixin, Module, Name, Node, Parameter, Reference, Singleton } from 'wollok-ts'
import { OBJECT_CLASS, parentModule, projectFQN } from '../../utils/vm/wollok'
import { match, when } from 'wollok-ts/dist/extensions'


// -----------------
// -----MAPPERS-----
Expand All @@ -12,16 +15,55 @@ export const fieldCompletionItem: CompletionItemMapper<Field> = namedCompletionI

export const singletonCompletionItem: CompletionItemMapper<Singleton> = moduleCompletionItem(CompletionItemKind.Class)

export const methodCompletionItem: CompletionItemMapper<Method> = (method) => {
const params = method.parameters.map((p, i) => `\${${i+1}:${p.name}}`).join(', ')
/**
* We want
* - first: methods belonging to the same file we are using
* - then, concrete classes/singletons
* - then, library methods having this order: 1. lang, 2. lib, 3. game
* - and last: object
*/
const getSortText = (node: Node, method: Method) => {
const methodContainer = parentModule(method)
fdodino marked this conversation as resolved.
Show resolved Hide resolved
return formatSortText((node.sourceFileName === method.sourceFileName ? 1 : getLibraryIndex(method)) + additionalIndex(method, methodContainer))
}

const getLibraryIndex = (node: Node) => {
switch (node.sourceFileName) {
case 'wollok/lang.wlk': {
return 20
}
case 'wollok/lib.wlk': {
return 30
}
case 'wollok/game.wlk': {
return 40
}
default: {
return 10
}
}
}

const formatSortText = (index: number) => ('000' + index).slice(-3)

const additionalIndex = (method: Method, methodContainer: Module): number => {
if (methodContainer.fullyQualifiedName === OBJECT_CLASS) return 50
if (methodContainer instanceof Class && methodContainer.isAbstract) return 5
if (method.isAbstract()) return 3
return 1
}

export const methodCompletionItem = (node: Node, method: Method): CompletionItem => {
const params = method.parameters.map((parameter, i) => `\${${i+1}:${parameter.name}}`).join(', ')
return {
label: method.name,
filterText: method.name,
insertTextFormat: InsertTextFormat.Snippet,
insertText: `${method.name}(${params})`,
kind: CompletionItemKind.Method,
detail: `${method.parent.name} \n\n\n File ${method.parent.sourceFileName?.split('/').pop()}`,
labelDetails: { description: method.parent.name, detail: `(${method.parameters.map(p => p.name).join(', ')})` },
labelDetails: { description: method.parent.name, detail: `(${method.parameters.map(parameter => parameter.name).join(', ')})` },
sortText: getSortText(node, method),
}
}

Expand All @@ -37,6 +79,48 @@ function namedCompletionItem<T extends {name: string}>(kind: CompletionItemKind)
insertText: namedNode.name,
insertTextFormat: InsertTextFormat.PlainText,
kind,
sortText: '001',
}
}
}

export const classCompletionItem = (clazz: Class): CompletionItem => {
return {
label: clazz.name,
filterText: clazz.name,
insertTextFormat: InsertTextFormat.PlainText,
insertText: `${clazz.name}`,
kind: CompletionItemKind.Class,
detail: `${clazz.name} \n\n\n File ${clazz.parent.sourceFileName?.split('/').pop()}`,
sortText: formatSortText(getLibraryIndex(clazz)),
}
}

export const initializerCompletionItem = (clazz: Class): CompletionItem => {
// TODO: export getAllUninitializedAttributes from wollok-ts and use it
fdodino marked this conversation as resolved.
Show resolved Hide resolved
const initializers = clazz.allFields.map((member, i) => `\${${2*i+1}:${member.name}} = \${${2*i+2}}`).join(', ')
return {
label: 'initializers',
filterText: 'initializers',
insertTextFormat: InsertTextFormat.Snippet,
insertText: initializers,
kind: CompletionItemKind.Constructor,
sortText: '010',
}
}

export const entityCompletionItem = (entity: Entity): CompletionItem => {
const label = projectFQN(entity)
return {
label,
filterText: label,
insertTextFormat: InsertTextFormat.PlainText,
kind: match(entity)(
when(Class)(() => CompletionItemKind.Class),
when(Mixin)(() => CompletionItemKind.Interface),
when(Reference)(() => CompletionItemKind.Reference),
when(Singleton)(() => CompletionItemKind.Module),
),
sortText: formatSortText(getLibraryIndex(entity)),
}
}
Loading