cssmodules-language-server
Advanced tools
Comparing version
@@ -46,3 +46,5 @@ "use strict"; | ||
textDocumentSync: lsp.TextDocumentSyncKind.Incremental, | ||
hoverProvider: true, | ||
definitionProvider: true, | ||
implementationProvider: true, | ||
completionProvider: { | ||
@@ -68,4 +70,6 @@ /** | ||
connection.onDefinition(definitionProvider.definition); | ||
connection.onImplementation(definitionProvider.definition); | ||
connection.onHover(definitionProvider.hover); | ||
return connection; | ||
} | ||
exports.createConnection = createConnection; |
@@ -1,2 +0,2 @@ | ||
import { Location, Position } from 'vscode-languageserver-protocol'; | ||
import { Hover, Location, Position } from 'vscode-languageserver-protocol'; | ||
import { TextDocument } from 'vscode-languageserver-textdocument'; | ||
@@ -10,3 +10,5 @@ import * as lsp from 'vscode-languageserver/node'; | ||
definition: (params: lsp.DefinitionParams) => Promise<never[] | Location | null>; | ||
hover: (params: lsp.HoverParams) => Promise<Hover | null>; | ||
provideHover(textdocument: TextDocument, position: Position): Promise<null | Hover>; | ||
provideDefinition(textdocument: TextDocument, position: Position): Promise<Location | null>; | ||
} |
@@ -30,2 +30,9 @@ "use strict"; | ||
}); | ||
this.hover = (params) => __awaiter(this, void 0, void 0, function* () { | ||
const textdocument = textDocuments_1.textDocuments.get(params.textDocument.uri); | ||
if (textdocument === undefined) { | ||
return null; | ||
} | ||
return this.provideHover(textdocument, params.position); | ||
}); | ||
this._camelCaseConfig = camelCaseConfig; | ||
@@ -36,2 +43,32 @@ } | ||
} | ||
provideHover(textdocument, position) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const fileContent = textdocument.getText(); | ||
const lines = fileContent.split(os_1.EOL); | ||
const currentLine = lines[position.line]; | ||
if (typeof currentLine !== 'string') { | ||
return null; | ||
} | ||
const currentDir = (0, utils_1.getCurrentDirFromUri)(textdocument.uri); | ||
const words = (0, utils_1.getWords)(currentLine, position); | ||
if (words === '' || words.indexOf('.') === -1) { | ||
return null; | ||
} | ||
const [obj, field] = words.split('.'); | ||
const importPath = (0, utils_1.findImportPath)(fileContent, obj, currentDir); | ||
if (importPath === '') { | ||
return null; | ||
} | ||
const dict = yield (0, utils_1.filePathToClassnameDict)(importPath, (0, utils_1.getTransformer)(this._camelCaseConfig)); | ||
const node = dict[`.${field}`]; | ||
if (!node) | ||
return null; | ||
return { | ||
contents: { | ||
language: 'css', | ||
value: (0, utils_1.stringiyClassname)(field, node.declarations, node.comments), | ||
}, | ||
}; | ||
}); | ||
} | ||
provideDefinition(textdocument, position) { | ||
@@ -38,0 +75,0 @@ return __awaiter(this, void 0, void 0, function* () { |
@@ -15,6 +15,12 @@ import { Position } from 'vscode-languageserver-protocol'; | ||
export declare function getWords(line: string, position: Position): string; | ||
declare type Classname = { | ||
declare type ClassnamePostion = { | ||
line: number; | ||
column: number; | ||
}; | ||
export declare type Classname = { | ||
position: ClassnamePostion; | ||
declarations: string[]; | ||
comments: string[]; | ||
}; | ||
declare type ClassnameDict = Record<string, Classname>; | ||
export declare const log: (...args: unknown[]) => void; | ||
@@ -26,11 +32,17 @@ /** | ||
* | ||
* ``` | ||
* ```js | ||
* { | ||
* '.foo': { | ||
* line: 10, | ||
* column: 5, | ||
* declarations: [], | ||
* position: { | ||
* line: 10, | ||
* column: 5, | ||
* }, | ||
* }, | ||
* '.bar': { | ||
* line: 22, | ||
* column: 1, | ||
* declarations: ['width: 52px'], | ||
* position: { | ||
* line: 22, | ||
* column: 1, | ||
* } | ||
* } | ||
@@ -40,3 +52,3 @@ * } | ||
*/ | ||
export declare function filePathToClassnameDict(filepath: string, classnameTransformer: StringTransformer): Promise<Record<string, Classname>>; | ||
export declare function filePathToClassnameDict(filepath: string, classnameTransformer: StringTransformer): Promise<ClassnameDict>; | ||
/** | ||
@@ -46,2 +58,3 @@ * Get all classnames from the file contents | ||
export declare function getAllClassNames(filePath: string, keyword: string, classnameTransformer: StringTransformer): Promise<string[]>; | ||
export declare function stringiyClassname(classname: string, declarations: string[], comments: string[]): string; | ||
export {}; |
@@ -15,3 +15,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getAllClassNames = exports.filePathToClassnameDict = exports.log = exports.getWords = exports.getPosition = exports.isImportLineMatch = exports.getTransformer = exports.findImportPath = exports.genImportRegExp = exports.getCurrentDirFromUri = void 0; | ||
exports.stringiyClassname = exports.getAllClassNames = exports.filePathToClassnameDict = exports.log = exports.getWords = exports.getPosition = exports.isImportLineMatch = exports.getTransformer = exports.findImportPath = exports.genImportRegExp = exports.getCurrentDirFromUri = void 0; | ||
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); | ||
@@ -84,3 +84,5 @@ const path_1 = __importDefault(require("path")); | ||
const target = classDict[`.${className}`]; | ||
return target ? vscode_languageserver_protocol_1.Position.create(target.line - 1, target.column) : null; | ||
return target | ||
? vscode_languageserver_protocol_1.Position.create(target.position.line - 1, target.position.column) | ||
: null; | ||
}); | ||
@@ -149,11 +151,17 @@ } | ||
* | ||
* ``` | ||
* ```js | ||
* { | ||
* '.foo': { | ||
* line: 10, | ||
* column: 5, | ||
* declarations: [], | ||
* position: { | ||
* line: 10, | ||
* column: 5, | ||
* }, | ||
* }, | ||
* '.bar': { | ||
* line: 22, | ||
* column: 1, | ||
* declarations: ['width: 52px'], | ||
* position: { | ||
* line: 22, | ||
* column: 1, | ||
* } | ||
* } | ||
@@ -187,10 +195,20 @@ * } | ||
const stack = [...ast.root.nodes]; | ||
let commentStack = []; | ||
while (stack.length) { | ||
const node = stack.shift(); | ||
if ((node === null || node === void 0 ? void 0 : node.type) === 'atrule' && node.name.toLowerCase() === 'media') { | ||
stack.unshift(...node.nodes); | ||
if (node === undefined) | ||
continue; | ||
if (node.type === 'comment') { | ||
commentStack.push(node); | ||
continue; | ||
} | ||
if ((node === null || node === void 0 ? void 0 : node.type) !== 'rule') | ||
if (node.type === 'atrule') { | ||
if (node.name.toLowerCase() === 'media') { | ||
stack.unshift(...node.nodes); | ||
} | ||
commentStack = []; | ||
continue; | ||
} | ||
if (node.type !== 'rule') | ||
continue; | ||
const selectors = node.selector.split(',').map(sanitizeSelector); | ||
@@ -214,5 +232,15 @@ selectors.forEach(sels => { | ||
dict[classnameTransformer(name)] = { | ||
column: column + lastLine.length, | ||
line: line + lines.length - 1, | ||
declarations: node.nodes.reduce((acc, x) => { | ||
if (x.type === 'decl') { | ||
acc.push(`${x.prop}: ${x.value};`); | ||
} | ||
return acc; | ||
}, []), | ||
position: { | ||
column: column + lastLine.length, | ||
line: line + lines.length - 1, | ||
}, | ||
comments: commentStack.map(x => x.text), | ||
}; | ||
commentStack = []; | ||
}); | ||
@@ -240,5 +268,15 @@ visitedNodes.set(node, { selectors }); | ||
dict[classnameTransformer(classname)] = { | ||
column: column, | ||
line: line, | ||
declarations: node.nodes.reduce((acc, x) => { | ||
if (x.type === 'decl') { | ||
acc.push(`${x.prop}: ${x.value};`); | ||
} | ||
return acc; | ||
}, []), | ||
position: { | ||
column: column, | ||
line: line, | ||
}, | ||
comments: commentStack.map(x => x.text), | ||
}; | ||
commentStack = []; | ||
})); | ||
@@ -267,1 +305,27 @@ visitedNodes.set(node, { selectors: finishedSelectors }); | ||
exports.getAllClassNames = getAllClassNames; | ||
function stringiyClassname(classname, declarations, comments) { | ||
const commentString = comments.length | ||
? comments | ||
.map(x => { | ||
const lines = x.split(os_1.EOL); | ||
if (lines.length < 2) { | ||
return `/*${x} */`; | ||
} | ||
else { | ||
return [ | ||
`/*${lines[0]}`, | ||
...lines.slice(1).map(y => ` ${y.trimStart()}`), | ||
' */', | ||
].join(os_1.EOL); | ||
} | ||
}) | ||
.join(os_1.EOL) + os_1.EOL | ||
: ''; | ||
return (commentString + | ||
[ | ||
`.${classname} {${declarations.length ? '' : '}'}`, | ||
...declarations.map(x => ` ${x}`), | ||
...(declarations.length ? ['}'] : []), | ||
].join(os_1.EOL)); | ||
} | ||
exports.stringiyClassname = stringiyClassname; |
{ | ||
"name": "cssmodules-language-server", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "language server for cssmodules", | ||
@@ -5,0 +5,0 @@ "bin": { |
@@ -7,2 +7,8 @@ # cssmodules-language-server | ||
Features: | ||
- **definition** jumps to class name under cursor. | ||
- **implementation** (works the same as definition). | ||
- **hover** provides comments before the class name with direct declarations within the class name. | ||
The supported languages are `css`(postcss), `sass` and `scss`. `styl` files are parsed as regular `css`. | ||
@@ -22,29 +28,25 @@ | ||
Example uses [`nvim-lspconfig`](https://github.com/neovim/lspconfig) | ||
Example uses [`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig) | ||
```lua | ||
local configs = require'lspconfig/configs' | ||
require'lspconfig'.cssmodules_ls.setup { | ||
-- provide your on_attach to bind keymappings | ||
on_attach = custom_on_attach, | ||
-- optionally | ||
init_options = { | ||
camelCase = 'dashes', | ||
}, | ||
} | ||
``` | ||
if not configs.cssmodules then | ||
configs.cssmodules = { | ||
default_config = { | ||
cmd = {'cssmodules-language-server'}, | ||
filetypes = {'javascript', 'javascriptreact', 'typescript', 'typescriptreact'}, | ||
init_options = { | ||
camelCase = 'dashes', | ||
}, | ||
root_dir = require('lspconfig.util').root_pattern('package.json') | ||
}, | ||
docs = { | ||
description = 'TODO description', | ||
default_config = { | ||
root_dir = '[[root_pattern("package.json")]]' | ||
} | ||
} | ||
} | ||
end | ||
**Known issue**: if you have multiple LSP that provide hover and go-to-definition support, there can be races(example typescript and cssmodules-language-server work simultaneously). As a workaround you can disable **definition** in favor of **implementation** to avoid conflicting with typescript's go-to-definition. | ||
configs.cssmodules.setup {} | ||
-- or | ||
-- configs.cssmodules.setup {on_attach = custom_on_attach} | ||
```lua | ||
require'lspconfig'.cssmodules_ls.setup { | ||
on_attach = function (client) | ||
-- avoid accepting `go-to-definition` responses from this LSP | ||
client.resolved_capabilities.goto_definition = false | ||
custom_on_attach(client) | ||
end, | ||
} | ||
``` | ||
@@ -51,0 +53,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
36601
20.06%15
7.14%705
20.72%88
2.33%0
-100%