@ripple-ts/language-server 0.2.184 → 0.2.186

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ripple-ts/language-server",
3
- "version": "0.2.184",
3
+ "version": "0.2.186",
4
4
  "description": "Language Server Protocol implementation for Ripple",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -19,7 +19,10 @@
19
19
  "volar-service-typescript": "0.0.65",
20
20
  "vscode-languageserver-textdocument": "^1.0.12",
21
21
  "vscode-uri": "^3.1.0",
22
- "@ripple-ts/typescript-plugin": "0.2.184"
22
+ "@ripple-ts/typescript-plugin": "0.2.186"
23
+ },
24
+ "devDependencies": {
25
+ "ripple": "0.2.186"
23
26
  },
24
27
  "peerDependencies": {
25
28
  "typescript": "^5.9.2"
@@ -64,7 +64,7 @@ function createAutoInsertPlugin() {
64
64
  return null;
65
65
  }
66
66
 
67
- const virtualCode = getVirtualCode(document, context);
67
+ const [virtualCode] = getVirtualCode(document, context);
68
68
 
69
69
  // Map position back to source
70
70
  const offset = document.offsetAt(position);
@@ -28,7 +28,7 @@ function createCompileErrorDiagnosticPlugin() {
28
28
  try {
29
29
  log('Providing Ripple diagnostics for:', document.uri);
30
30
 
31
- const virtualCode = getVirtualCode(document, context);
31
+ const [virtualCode] = getVirtualCode(document, context);
32
32
 
33
33
  if (!virtualCode || !virtualCode.errors || virtualCode.errors.length === 0) {
34
34
  return [];
@@ -217,7 +217,7 @@ function createCompletionPlugin() {
217
217
  return { items: [], isIncomplete: false };
218
218
  }
219
219
 
220
- const virtualCode = getVirtualCode(document, context);
220
+ const [virtualCode] = getVirtualCode(document, context);
221
221
 
222
222
  // Check if we're inside an embedded code (like CSS in <style> blocks)
223
223
  // If so, don't provide Ripple snippets - let CSS completions take priority
@@ -1,6 +1,9 @@
1
- /** @import { LanguageServicePlugin } from '@volar/language-server' */
1
+ /** @import { LanguageServicePlugin, LocationLink } from '@volar/language-server'; */
2
+ // @ts-expect-error type-only import from ESM module into CJS is fine
3
+ /** @import { DefinitionLocation } from 'ripple/compiler'; */
2
4
 
3
- const { getVirtualCode, createLogging } = require('./utils.js');
5
+ const { TextDocument } = require('vscode-languageserver-textdocument');
6
+ const { getVirtualCode, createLogging, getWordFromPosition } = require('./utils.js');
4
7
 
5
8
  const { log } = createLogging('[Ripple Definition Plugin]');
6
9
 
@@ -17,6 +20,7 @@ function createDefinitionPlugin() {
17
20
  return {
18
21
  async provideDefinition(document, position, token) {
19
22
  // Get TypeScript definition from typescript-semantic service
23
+ /** @type {LocationLink[]} */
20
24
  let tsDefinitions = [];
21
25
  for (const [plugin, instance] of context.plugins) {
22
26
  if (plugin.name === 'typescript-semantic' && instance.provideDefinition) {
@@ -28,6 +32,92 @@ function createDefinitionPlugin() {
28
32
  }
29
33
  }
30
34
 
35
+ const [virtualCode, sourceUri] = getVirtualCode(document, context);
36
+
37
+ // First check for custom definitions (e.g., CSS class selectors)
38
+ const offset = document.offsetAt(position);
39
+ const text = document.getText();
40
+ // Find word boundaries
41
+ const { word, start, end } = getWordFromPosition(text, offset);
42
+ const customMapping = virtualCode.findMappingByGeneratedRange(start, end);
43
+
44
+ log(`Cursor position in generated code for word '${word}':`, position);
45
+ log(`Cursor offset in generated code for word '${word}':`, offset);
46
+
47
+ // If mapping has custom definition metadata with location, handle it
48
+ if (
49
+ customMapping?.data.customData.definition !== false &&
50
+ customMapping?.data.customData.definition?.location
51
+ ) {
52
+ const def = customMapping.data.customData.definition;
53
+ const loc = /** @type {DefinitionLocation} */ (def.location);
54
+
55
+ const embeddedCode = loc.embeddedId
56
+ ? virtualCode.embeddedCodes?.find(({ id }) => id === loc.embeddedId)
57
+ : undefined;
58
+
59
+ if (embeddedCode) {
60
+ const embedMapping = embeddedCode.mappings[0];
61
+
62
+ // Calculate the position in the source document
63
+ // CSS offset relative to embedded code start + source offset of CSS region
64
+ const sourceStartOffset = embedMapping.sourceOffsets[0] + loc.start;
65
+ const sourceEndOffset = embedMapping.sourceOffsets[0] + loc.end;
66
+
67
+ log(
68
+ 'Source document offsets - start for matching css:',
69
+ sourceStartOffset,
70
+ 'end:',
71
+ sourceEndOffset,
72
+ );
73
+
74
+ // Calculate line/column positions using the source document's proper encoding
75
+ // Create a TextDocument from the source code for proper position calculations
76
+ const sourceDocument = TextDocument.create(
77
+ sourceUri.toString(),
78
+ 'ripple',
79
+ 0,
80
+ virtualCode.originalCode,
81
+ );
82
+ const targetStart = sourceDocument.positionAt(sourceStartOffset);
83
+ const targetEnd = sourceDocument.positionAt(sourceEndOffset);
84
+
85
+ log('Target positions in source - start:', targetStart, 'end:', targetEnd);
86
+
87
+ // The origin selection range should be in the virtual document
88
+ // not in the source document!
89
+ const generatedStart = customMapping.generatedOffsets[0];
90
+ const generatedEnd =
91
+ generatedStart + customMapping.data.customData.generatedLengths[0];
92
+ const originStart = document.positionAt(generatedStart);
93
+ const originEnd = document.positionAt(generatedEnd);
94
+
95
+ log('Origin positions - start:', originStart, 'end:', originEnd);
96
+
97
+ /** @type {LocationLink} */
98
+ tsDefinitions.push({
99
+ targetUri: sourceUri.toString(), // Use the actual source file URI
100
+ targetRange: {
101
+ start: targetStart,
102
+ end: targetEnd,
103
+ },
104
+ targetSelectionRange: {
105
+ start: targetStart,
106
+ end: targetEnd,
107
+ },
108
+ originSelectionRange: {
109
+ start: originStart,
110
+ end: originEnd,
111
+ },
112
+ });
113
+
114
+ return tsDefinitions;
115
+ }
116
+ }
117
+
118
+ // Below here we handle adjusting TypeScript definitions for transformed tokens
119
+ // specifically, when "component" in Ripple maps to "function" in TS
120
+
31
121
  // If no TypeScript definitions, nothing to modify
32
122
  // Volar will let the next ts plugin handle it
33
123
  if (tsDefinitions.length === 0) {
@@ -45,8 +135,6 @@ function createDefinitionPlugin() {
45
135
  const rangeStart = document.offsetAt(range.start);
46
136
  const rangeEnd = document.offsetAt(range.end);
47
137
 
48
- const virtualCode = getVirtualCode(document, context);
49
-
50
138
  // Find the mapping using the exact token range for O(1) lookup
51
139
  const mapping = virtualCode.findMappingByGeneratedRange(rangeStart, rangeEnd);
52
140
 
@@ -56,7 +56,7 @@ function createDocumentHighlightPlugin() {
56
56
  return tsHighlights;
57
57
  }
58
58
 
59
- const virtualCode = getVirtualCode(document, context);
59
+ const [virtualCode] = getVirtualCode(document, context);
60
60
 
61
61
  if (!virtualCode) {
62
62
  return tsHighlights;
@@ -1,7 +1,16 @@
1
- /** @import { LanguageServicePlugin } from '@volar/language-server' */
2
- /** @import { LanguageServicePluginInstance } from '@volar/language-server' */
3
-
4
- const { getVirtualCode, createLogging } = require('./utils.js');
1
+ /**
2
+ @import {
3
+ LanguageServicePlugin,
4
+ LanguageServicePluginInstance,
5
+ MarkupContent,
6
+ } from '@volar/language-server'; */
7
+
8
+ const {
9
+ getVirtualCode,
10
+ createLogging,
11
+ getWordFromPosition,
12
+ concatMarkdownContents,
13
+ } = require('./utils.js');
5
14
 
6
15
  const { log, logError } = createLogging('[Ripple Hover Plugin]');
7
16
 
@@ -44,22 +53,33 @@ function createHoverPlugin() {
44
53
  tsHover = await originalProvideHover.call(originalInstance, document, position, token);
45
54
  }
46
55
 
47
- // If no TypeScript hover, nothing to modify
48
- if (!tsHover || !tsHover.range) {
49
- return;
50
- }
51
-
52
- const virtualCode = getVirtualCode(document, context);
56
+ const [virtualCode] = getVirtualCode(document, context);
53
57
 
54
58
  if (!virtualCode) {
55
59
  return tsHover;
56
60
  }
57
61
 
58
- const range = tsHover.range;
59
- const rangeStart = document.offsetAt(range.start);
60
- const rangeEnd = document.offsetAt(range.end);
62
+ /** @type {number} */
63
+ let starOffset;
64
+ /** @type {number} */
65
+ let endOffset;
66
+
67
+ if (tsHover && tsHover.range) {
68
+ starOffset = document.offsetAt(tsHover.range.start);
69
+ endOffset = document.offsetAt(tsHover.range.end);
70
+ } else {
71
+ const offset = document.offsetAt(position);
72
+ const text = document.getText();
73
+ // Find word boundaries
74
+ const { word, start, end } = getWordFromPosition(text, offset);
75
+ starOffset = start;
76
+ endOffset = end;
77
+
78
+ log(`Cursor position in generated code for word '${word}':`, position);
79
+ log(`Cursor offset in generated code for word '${word}':`, offset);
80
+ }
61
81
 
62
- const mapping = virtualCode.findMappingByGeneratedRange(rangeStart, rangeEnd);
82
+ const mapping = virtualCode.findMappingByGeneratedRange(starOffset, endOffset);
63
83
 
64
84
  if (!mapping) {
65
85
  return tsHover;
@@ -67,11 +87,17 @@ function createHoverPlugin() {
67
87
 
68
88
  const customHover = mapping?.data?.customData?.hover;
69
89
  if (customHover) {
90
+ const contents = tsHover
91
+ ? concatMarkdownContents(
92
+ /** @type {MarkupContent} **/ (tsHover.contents).value,
93
+ customHover.contents,
94
+ )
95
+ : customHover.contents;
70
96
  log('Found custom hover data in mapping');
71
97
  return {
72
98
  contents: {
73
99
  kind: 'markdown',
74
- value: customHover.contents,
100
+ value: contents,
75
101
  },
76
102
  range: {
77
103
  start: position,
@@ -80,33 +106,35 @@ function createHoverPlugin() {
80
106
  };
81
107
  } else if (customHover === false) {
82
108
  log(
83
- `Hover explicitly suppressed in mapping at range start: ${rangeStart}, end: ${rangeEnd}`,
109
+ `Hover explicitly suppressed in mapping at range start: ${starOffset}, end: ${endOffset}`,
84
110
  );
85
111
  return null;
86
112
  }
87
113
 
88
- log('Found mapping for hover at range', 'start: ', rangeStart, 'end: ', rangeEnd);
114
+ log('Found mapping for hover at range', 'start: ', starOffset, 'end: ', endOffset);
89
115
 
90
- // Check if source length is greater than generated length (component -> function)
91
- const customData = mapping.data.customData;
92
- const sourceLength = mapping.lengths[0];
93
- const generatedLength = customData.generatedLengths[0];
116
+ if (tsHover && tsHover.range) {
117
+ // Check if source length is greater than generated length (component -> function)
118
+ const customData = mapping.data.customData;
119
+ const sourceLength = mapping.lengths[0];
120
+ const generatedLength = customData.generatedLengths[0];
94
121
 
95
- // If no generatedLengths, or source and generated are same length, no transformation
96
- if (sourceLength <= generatedLength) {
97
- return tsHover;
98
- }
122
+ // If no generatedLengths, or source and generated are same length, no transformation
123
+ if (sourceLength <= generatedLength) {
124
+ return tsHover;
125
+ }
99
126
 
100
- const diffLength = sourceLength - generatedLength;
127
+ const diffLength = sourceLength - generatedLength;
101
128
 
102
- // Adjust the hover range to highlight the full "component" keyword
103
- tsHover.range = {
104
- start: range.start,
105
- end: {
106
- line: range.end.line,
107
- character: range.end.character + diffLength,
108
- },
109
- };
129
+ // Adjust the hover range to highlight the full "component" keyword
130
+ tsHover.range = {
131
+ start: tsHover.range.start,
132
+ end: {
133
+ line: tsHover.range.end.line,
134
+ character: tsHover.range.end.character + diffLength,
135
+ },
136
+ };
137
+ }
110
138
 
111
139
  return tsHover;
112
140
  },
@@ -56,7 +56,7 @@ function createTypeScriptDiagnosticFilterPlugin() {
56
56
 
57
57
  log(`Filtering ${diagnostics.length} TypeScript diagnostics for ${document.uri}`);
58
58
 
59
- const virtualCode = getVirtualCode(document, context);
59
+ const [virtualCode] = getVirtualCode(document, context);
60
60
 
61
61
  const filtered = diagnostics.filter((diagnostic) => {
62
62
  const range = diagnostic.range;
package/src/utils.js CHANGED
@@ -4,12 +4,21 @@
4
4
 
5
5
  const { URI } = require('vscode-uri');
6
6
  const { createLogging, DEBUG } = require('@ripple-ts/typescript-plugin/src/utils.js');
7
+ const wordRegex = /\w/;
8
+
9
+ /**
10
+ * @param {...string} contents
11
+ * @returns string
12
+ */
13
+ function concatMarkdownContents(...contents) {
14
+ return contents.join('\n\n<br>\n\n---\n\n<br><br>\n\n');
15
+ }
7
16
 
8
17
  /**
9
18
  * Get virtual code from the encoded document URI
10
19
  * @param {LanguageServiceContext} context
11
20
  * @param {TextDocument} document
12
- * @returns {RippleVirtualCode}
21
+ * @returns {[RippleVirtualCode, URI]}
13
22
  */
14
23
  function getVirtualCode(document, context) {
15
24
  const uri = URI.parse(document.uri);
@@ -22,7 +31,7 @@ function getVirtualCode(document, context) {
22
31
  sourceScript?.generated?.embeddedCodes.get(virtualCodeId)
23
32
  );
24
33
 
25
- return virtualCode;
34
+ return [virtualCode, sourceUri];
26
35
  }
27
36
 
28
37
  /**
@@ -34,10 +43,10 @@ function getVirtualCode(document, context) {
34
43
  function getWordFromPosition(text, start) {
35
44
  let wordStart = start;
36
45
  let wordEnd = start;
37
- while (wordStart > 0 && /\w/.test(text[wordStart - 1])) {
46
+ while (wordStart > 0 && wordRegex.test(text[wordStart - 1])) {
38
47
  wordStart--;
39
48
  }
40
- while (wordEnd < text.length && /\w/.test(text[wordEnd])) {
49
+ while (wordEnd < text.length && wordRegex.test(text[wordEnd])) {
41
50
  wordEnd++;
42
51
  }
43
52
 
@@ -54,5 +63,6 @@ module.exports = {
54
63
  getVirtualCode,
55
64
  getWordFromPosition,
56
65
  createLogging,
66
+ concatMarkdownContents,
57
67
  DEBUG,
58
68
  };