-
-
Notifications
You must be signed in to change notification settings - Fork 217
(feat)Go to definition from class in template to css #1518
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
base: master
Are you sure you want to change the base?
Changes from 4 commits
c22d449
6e42c84
65920ea
bd2b9c4
b01e66c
7f86d5c
c87212b
6010236
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
{ | ||
"typescript.preferences.quoteStyle": "single" | ||
"typescript.preferences.quoteStyle": "single", | ||
"files.exclude": { | ||
"**/**/dist": true, | ||
"**/**/node_modules": true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { Position, Range } from 'vscode-languageserver'; | ||
import { SvelteDocumentSnapshot } from '../typescript/DocumentSnapshot'; | ||
import { Document } from '../../lib/documents'; | ||
import { SvelteNode } from '../typescript/svelte-ast-utils'; | ||
export class CSSClassDefinitionLocator { | ||
initialNodeAt: SvelteNode; | ||
constructor( | ||
public tsDoc: SvelteDocumentSnapshot, | ||
public position: Position, | ||
public document: Document | ||
) { | ||
this.initialNodeAt = this.tsDoc.svelteNodeAt(this.position) as SvelteNode; | ||
} | ||
|
||
public GetCSSClassDefinition() { | ||
if (this.IsStandardClassFormat()) { | ||
return this.GetStandardFormatClassName(); | ||
} | ||
|
||
if (this.IsDirectiveFormat() && this.initialNodeAt.name) { | ||
return this.GetDefinitionRangeWithinStyleSection(`.${this.initialNodeAt.name}`); | ||
} | ||
|
||
if (this.IsConditionalExpressionClassFormat() && this.initialNodeAt.value) { | ||
return this.GetDefinitionRangeWithinStyleSection(`.${this.initialNodeAt.value}`); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Standard format: | ||
* class="test test1" | ||
*/ | ||
public IsStandardClassFormat() { | ||
if (this.initialNodeAt?.type == 'Text' && this.initialNodeAt?.parent?.name == 'class') { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Conditional Expression format: | ||
* class="{current === 'baz' ? 'selected' : '' | ||
*/ | ||
public IsConditionalExpressionClassFormat() { | ||
if ( | ||
this.initialNodeAt?.type == 'Literal' && | ||
this.initialNodeAt?.parent?.type == 'ConditionalExpression' && | ||
this.initialNodeAt?.parent.parent?.parent?.name == 'class' | ||
) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Class Directive format: | ||
* class:active="{current === 'foo'}" | ||
*/ | ||
public IsDirectiveFormat() { | ||
if (this.initialNodeAt?.type == 'Class' && this.initialNodeAt?.parent?.type == 'Element') { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public GetStandardFormatClassName() { | ||
const testEndTagRange = Range.create( | ||
Position.create(this.position.line, 0), | ||
Position.create(this.position.line, this.position.character) | ||
); | ||
const text = this.document.getText(testEndTagRange); | ||
|
||
let loopLength = text.length; | ||
let testPosition = this.position.character; | ||
let spaceCount = 0; | ||
|
||
//Go backwards until hitting a " and keep track of how many spaces happened along the way | ||
while (loopLength) { | ||
const testEndTagRange = Range.create( | ||
Position.create(this.position.line, testPosition - 1), | ||
Position.create(this.position.line, testPosition) | ||
); | ||
const text = this.document.getText(testEndTagRange); | ||
if (text === ' ') { | ||
spaceCount++; | ||
} | ||
|
||
if (text === '"') { | ||
break; | ||
} | ||
|
||
testPosition--; | ||
loopLength--; | ||
} | ||
|
||
const cssClassName = this.initialNodeAt?.data.split(' ')[spaceCount]; | ||
|
||
return this.GetDefinitionRangeWithinStyleSection(`.${cssClassName}`); | ||
} | ||
|
||
public GetDefinitionRangeWithinStyleSection(targetClass: string) { | ||
let indexOccurence = this.document.content.indexOf(targetClass, 0); | ||
|
||
while (indexOccurence >= 0) { | ||
if (this.IsOffsetWithinStyleSection(indexOccurence)) { | ||
const startPosition = this.document.positionAt(indexOccurence); | ||
const targetRange = Range.create( | ||
startPosition, | ||
Position.create( | ||
startPosition.line, | ||
startPosition.character + targetClass.length | ||
) | ||
); | ||
indexOccurence = this.document.content.indexOf(targetClass, indexOccurence + 1); | ||
|
||
if (!this.IsExactClassMatch(targetRange)) { | ||
continue; | ||
} | ||
|
||
return targetRange; | ||
} | ||
} | ||
} | ||
|
||
public IsOffsetWithinStyleSection(offsetPosition: number) { | ||
Jojoshua marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (this.document.styleInfo) { | ||
if ( | ||
offsetPosition > this.document.styleInfo?.start && | ||
offsetPosition < this.document.styleInfo?.end | ||
) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public IsExactClassMatch(testRange: Range) { | ||
//Check nothing before the test position | ||
if (testRange.start.character > 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would fail for something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a feeling about that one. The case I thought would be the most straightforward turned out, not. I will try to improve it. I was looking for a way to pull the text for an entire line but couldn't find it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dummdidumm So this actually did already work for .foo .bar but did not for .foo.bar Try the latest, it handles all sorts of combinations. I feel this is a good compromise especially since I really don't feel that style's inside a svelte component would/should be very complex. If after testing you still feel like we need more horsepower I can look into the CSS AST(just point me in the right direction for that) |
||
const beforerange = Range.create( | ||
Position.create(testRange.start.line, testRange.start.character - 1), | ||
Position.create(testRange.start.line, testRange.start.character) | ||
); | ||
if (this.document.getText(beforerange).trim()) { | ||
return false; | ||
} | ||
} | ||
|
||
//Check space or { is after the test position | ||
const afterRange = Range.create( | ||
Position.create(testRange.end.line, testRange.end.character), | ||
Position.create(testRange.end.line, testRange.end.character + 1) | ||
); | ||
const afterRangeText = this.document.getText(afterRange).trim(); | ||
if (afterRangeText == '' || afterRangeText == '{') { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ import { | |
import { Document, getTextInRange, mapSymbolInformationToOriginal } from '../../lib/documents'; | ||
import { LSConfigManager, LSTypescriptConfig } from '../../ls-config'; | ||
import { isNotNullOrUndefined, isZeroLengthRange, pathToUrl } from '../../utils'; | ||
import { CSSClassDefinitionLocator } from '../css/CSSClassDefinitionLocator'; | ||
import { | ||
AppCompletionItem, | ||
AppCompletionList, | ||
|
@@ -329,6 +330,31 @@ export class TypeScriptPlugin | |
const { lang, tsDoc } = await this.getLSAndTSDoc(document); | ||
const mainFragment = tsDoc.getFragment(); | ||
|
||
const cssClassHelper = new CSSClassDefinitionLocator(tsDoc, position, document); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea of the plugin folders is that they are mostly independent of each other, so it would be better to put this logic into the CSSPlugin or move the class into the TypeScript plugin folder - but I feel like the best place would be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I was having trouble where to place this. I moved to TypeScript folder for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feel free to relocate wherever you feel is best |
||
const cssDefinitionRange = cssClassHelper.GetCSSClassDefinition(); | ||
if (cssDefinitionRange) { | ||
const results: DefinitionLink[] = []; | ||
cssDefinitionRange.start.character++; //Report start of name instead of start at . for easy rename (F2) possibilities | ||
|
||
const originRange = Range.create( | ||
Position.create(position.line, position.character), | ||
Position.create(position.line, position.character) | ||
); | ||
|
||
results.push( | ||
LocationLink.create( | ||
pathToUrl(document.getFilePath() as string), | ||
cssDefinitionRange, | ||
cssDefinitionRange, | ||
originRange | ||
) | ||
); | ||
|
||
if (results) { | ||
return results; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The results should probably be merged with what else comes up. In the case of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I follow. In all the tests and variations I've used it seems to work. When you hit F12 to go to definition there can only be 1 result (if a definition can be found). |
||
} | ||
} | ||
|
||
const defs = lang.getDefinitionAndBoundSpan( | ||
tsDoc.filePath, | ||
mainFragment.offsetAt(mainFragment.getGeneratedPosition(position)) | ||
|
Uh oh!
There was an error while loading. Please reload this page.