Skip to content

Commit

Permalink
wip(smartAutocomplete): Make attributes go-to-able
Browse files Browse the repository at this point in the history
  • Loading branch information
hiaux0 committed Aug 16, 2019
1 parent 8d8618d commit 96e38b1
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 91 deletions.
2 changes: 1 addition & 1 deletion src/client/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class SearchDefinitionInViewV2 implements vscode.DefinitionProvider {
document: vscode.TextDocument,
position: vscode.Position): Promise<vscode.DefinitionLink[]> {
const extensionsFromSettings = getFileExtensionsFromConfig(workspace);
const definitionsInfo = await this.client.sendRequest('aurelia-definition-provide', extensionsFromSettings);
const { definitionsInfo, definitionsAttributesInfo } = await this.client.sendRequest('aurelia-definition-provide', extensionsFromSettings);

// 1. get value name (property or method) CHECK
const goToSourceWordRange = document.getWordRangeAtPosition(position);
Expand Down
172 changes: 172 additions & 0 deletions src/server/ExposeAureliaDefinitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { ViewModelDocument } from './FileParser/Model/ViewModelDocument';
export declare type DefinitionsInfo = {
[name: string]: DefinitionLink[];
}

import {
Location,
DefinitionLink,
LocationLink,
Range,
MarkedString,
Position,
} from 'vscode-languageserver';
import { AureliaConfigProperties } from '../client/Model/AureliaConfigProperties';
import { AureliaApplication } from './FileParser/Model/AureliaApplication';
import { camelCase } from 'aurelia-binding';
import { WebComponent } from './FileParser/Model/WebComponent';
import { HtmlTemplateDocument } from './FileParser/Model/HtmlTemplateDocument';

function storeDefinitions(
propName: string,
component: WebComponent,
extensionsFromSettings: AureliaConfigProperties['relatedFiles'],
): DefinitionsInfo {
const { viewModel } = component;
if (!viewModel[propName]) return;

const definitionsInfo: DefinitionsInfo = {};
// 1.1 Find target path
const { script: scriptExtension } = extensionsFromSettings;
const viewModelPath = component.paths.find(path => {
return path.endsWith(scriptExtension);
});
const targetUri = `file://${viewModelPath}`;

viewModel[propName].forEach(property => {
const { range } = property
const targetRange = Range.create(range.start, range.end);

const def = definitionsInfo[property.name] || [];
def.push(
LocationLink.create(
targetUri,
targetRange,
targetRange,
)
);

definitionsInfo[property.name] = def;
});
}

function storeViewDefinitions(
component: WebComponent,
aureliaApplication: AureliaApplication,
extensionsFromSettings: AureliaConfigProperties['relatedFiles'],
) {
const viewDocument = component.document;
if (!viewDocument) return;

const definitionsInfo: DefinitionsInfo = {};
const definitionsAttributesInfo = {};

const { script: scriptExtension } = extensionsFromSettings;
const tags = viewDocument.tags.filter(tag => {
const isDontParseTags = (tag.name === 'template') || (tag.name === 'require');
const isStartTag = !isDontParseTags && tag.startTag;
return (!isDontParseTags && tag.startTag) // omit closing tags
});

tags.forEach(tag => {
const customElementName = tag.name;
tag.attributes.forEach(attr => {
if (attr.name === 'repeat') {
/* eg. value = 'button of buttons' */
const { value } = attr;
const definitionWord = value.split(' ')[0];
const targetViewUri = `file://${viewDocument.path}`;
/* parse5 line index starts 1 */
const lineNum = tag.line - 1;

const targetRange = Range.create(
Position.create(lineNum, attr.startOffset),
Position.create(lineNum, attr.endOffset),
);

const def = definitionsInfo[definitionWord] || [];
def.push(
LocationLink.create(
targetViewUri,
targetRange,
targetRange,
)
);
definitionsInfo[definitionWord] = def;
}

/*
* Here, I assume, that .binding will only be true for valid attr. bindings
*/
if (attr.binding === 'bind') {
const { name: bindingName } = attr;

// check if is reference
const references = viewDocument.references;
const getFileName = (path: string): string => {
return path.split('/').pop().replace(/\..+/, '');
}
const foundRefs = references.map(ref => {
return getFileName(ref.path);
});
if (!foundRefs.includes(customElementName)) return;
customElementName
// find in aurelai components
const targetViewPath = aureliaApplication.components
.find(component => {
return (component.name === customElementName)
})
.paths.find(path => {
return path.endsWith(scriptExtension)
});
const targetViewUri = `file://${targetViewPath}`;

/* parse5 line index starts 1 */
const lineNum = tag.line - 1;

const targetRange = Range.create(
Position.create(lineNum, attr.startOffset),
Position.create(lineNum, attr.endOffset),
);

const asCamelCase = camelCase(bindingName);
// definitionsInfo[bindingName] = definitionsInfo[asCamelCase];
const def = definitionsAttributesInfo[bindingName] || [];
def.push(
{
customElementName,
asCamelCase,
}
);
definitionsAttributesInfo[bindingName] = def;
}
});
});
}

export function exposeAureliaDefinitions(
extensionsFromSettings: AureliaConfigProperties['relatedFiles'],
aureliaApplication: AureliaApplication,
): { definitionsInfo: DefinitionsInfo, definitionsAttributesInfo } {
let propertiesDefinition: DefinitionsInfo = {};
let methodsDefinitions: DefinitionsInfo = {};
let definitionsAttributesInfo: any = {};

// 1. For each component
aureliaApplication.components.forEach(component => {
if (!component.viewModel) return;

propertiesDefinition = storeDefinitions('properties', component, extensionsFromSettings);
methodsDefinitions = storeDefinitions('methods', component, extensionsFromSettings);

definitionsAttributesInfo = storeViewDefinitions(component, aureliaApplication, extensionsFromSettings);
});

return {
definitionsInfo: {
...propertiesDefinition,
...methodsDefinitions,
},
definitionsAttributesInfo
};
}
94 changes: 4 additions & 90 deletions src/server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from 'vscode-languageserver';
import { MarkedString, Position } from 'vscode-languageserver';

import { camelCase } from 'aurelia-binding';
import { Container } from 'aurelia-dependency-injection';
import CompletionItemFactory from './CompletionItemFactory';
import ElementLibrary from './Completions/Library/_elementLibrary';
Expand All @@ -38,6 +39,7 @@ import { AureliaApplication } from './FileParser/Model/AureliaApplication';
import { normalizePath } from './Util/NormalizePath';
import { connect } from 'net';
import { AureliaConfigProperties } from '../client/Model/AureliaConfigProperties';
import { exposeAureliaDefinitions } from './ExposeAureliaDefinitions';

// Bind console.log & error to the Aurelia output
const connection: IConnection = createConnection();
Expand Down Expand Up @@ -123,8 +125,8 @@ connection.onRequest('aurelia-view-information', async (filePath: string) => {
});

connection.onRequest('aurelia-definition-provide', (extensionsFromSettings: AureliaConfigProperties['relatedFiles']) => {
const result = exposeAureliaDefinitions(extensionsFromSettings);
return result;
const { definitionsInfo, definitionsAttributesInfo } = exposeAureliaDefinitions(extensionsFromSettings);
return { definitionsInfo, definitionsAttributesInfo };
})

connection.onRequest('aurelia-smart-autocomplete-goto', () => {
Expand All @@ -141,94 +143,6 @@ connection.onDefinition((position: TextDocumentPositionParams): Definition => {

connection.listen();

export declare type DefinitionsInfo = {
[name: string]: DefinitionLink[];
}

function exposeAureliaDefinitions(extensionsFromSettings: AureliaConfigProperties['relatedFiles']): DefinitionsInfo {
const definitionsInfo: DefinitionsInfo = {};

// 1. For each component
aureliaApplication.components.forEach(component => {
if (!component.viewModel) return;

const { viewModel } = component;
// 1.1 Find target path
const { script } = extensionsFromSettings;
const viewModelPath = component.paths.find(path => {
return path.endsWith(script);
});
const targetUri = `file://${viewModelPath}`;
const document = documents.get(targetUri);

function storeDefinitions(propName: string) {
if (!viewModel[propName]) return;

viewModel[propName].forEach(property => {
const { range } = property
const targetRange = Range.create(range.start, range.end);

const def = definitionsInfo[property.name] || [];
def.push(
LocationLink.create(
targetUri,
targetRange,
targetRange,
)
);

definitionsInfo[property.name] = def;
});
}

storeDefinitions('properties');
storeDefinitions('methods');

function storeViewDefinitions() {
const viewDocument = component.document;
if (!viewDocument) return;

const tags = viewDocument.tags.filter(tag => {
const isDontParseTags = (tag.name === 'template') || (tag.name === 'require');
const isStartTag = !isDontParseTags && tag.startTag;
return (!isDontParseTags && tag.startTag) // omit closing tags
});

tags.forEach(tag => {
tag.attributes.forEach(attr => {
if (attr.name !== 'repeat') return;
/* eg. value = 'button of buttons' */
const { value } = attr;
const definitionWord = value.split(' ')[0];
const targetViewUri = `file://${viewDocument.path}`;
/* parse5 line index starts 1 */
const lineNum = tag.line - 1;

const targetRange = Range.create(
Position.create(lineNum, attr.startOffset),
Position.create(lineNum, attr.endOffset),
);

const def = definitionsInfo[definitionWord] || [];
def.push(
LocationLink.create(
targetViewUri,
targetRange,
targetRange,
)
);
definitionsInfo[definitionWord] = def;
});
});
}

storeViewDefinitions();
});

const allDocs = documents.all();
// and add them into defintions array
return definitionsInfo;
}


async function featureToggles(featureToggles) {
Expand Down

0 comments on commit 96e38b1

Please sign in to comment.