@prisma/language-server
Version:
Prisma Language Server
180 lines (155 loc) • 5.3 kB
text/typescript
import { Range, Position } from 'vscode-languageserver'
import type { TextDocument } from 'vscode-languageserver-textdocument'
import { MAX_SAFE_VALUE_i32 } from '../constants'
import { Block, getBlocks } from './block'
import { PrismaSchema } from '../Schema'
export function fullDocumentRange(document: TextDocument): Range {
const lastLineId = document.lineCount - 1
return {
start: { line: 0, character: 0 },
end: { line: lastLineId, character: MAX_SAFE_VALUE_i32 },
}
}
export function getCurrentLine(document: TextDocument, line: number): string {
return document.getText({
start: { line: line, character: 0 },
end: { line: line, character: MAX_SAFE_VALUE_i32 },
})
}
export function isFirstInsideBlock(position: Position, currentLine: string): boolean {
if (currentLine.trim().length === 0) {
return true
}
const stringTilPosition = currentLine.slice(0, position.character)
const matchArray = /\w+/.exec(stringTilPosition)
if (!matchArray) {
return true
}
return (
matchArray.length === 1 &&
matchArray.index !== undefined &&
stringTilPosition.length - matchArray.index - matchArray[0].length === 0
)
}
export function getWordAtPosition(document: TextDocument, position: Position): string {
const currentLine = getCurrentLine(document, position.line)
// search for the word's beginning and end
const beginning: number = currentLine.slice(0, position.character + 1).search(/\S+$/)
const end: number = currentLine.slice(position.character).search(/\W/)
if (end < 0) {
return ''
}
return currentLine.slice(beginning, end + position.character)
}
export function getBlockAtPosition(fileUri: string, line: number, schema: PrismaSchema): Block | undefined {
for (const block of getBlocks(schema)) {
if (fileUri !== block.definingDocument.uri) {
continue
}
if (block.range.start.line > line) {
return
}
if (line <= block.range.end.line) {
return block
}
}
return
}
export function getSymbolBeforePosition(document: TextDocument, position: Position): string {
return document.getText({
start: {
line: position.line,
character: position.character - 1,
},
end: { line: position.line, character: position.character },
})
}
export function positionIsAfterFieldAndType(
position: Position,
document: TextDocument,
wordsBeforePosition: string[],
): boolean {
const symbolBeforePosition = getSymbolBeforePosition(document, position)
const symbolBeforeIsWhiteSpace = symbolBeforePosition.search(/\s/)
const hasAtRelation = wordsBeforePosition.length === 2 && symbolBeforePosition === '@'
const hasWhiteSpaceBeforePosition = wordsBeforePosition.length === 2 && symbolBeforeIsWhiteSpace !== -1
return wordsBeforePosition.length > 2 || hasAtRelation || hasWhiteSpaceBeforePosition
}
// checks if e.g. inside 'fields' or 'references' attribute
export function isInsideGivenProperty(
currentLineUntrimmed: string,
wordsBeforePosition: string[],
attributeName: string,
position: Position,
): boolean {
if (!isInsideAttribute(currentLineUntrimmed, position, '[]')) {
return false
}
// We sort all attributes by their position
const sortedAttributes = [
{
name: 'fields',
position: wordsBeforePosition.findIndex((word) => word.includes('fields')),
},
{
name: 'references',
position: wordsBeforePosition.findIndex((word) => word.includes('references')),
},
].sort((a, b) => (a.position < b.position ? 1 : -1))
// If the last attribute (higher position)
// is the one we are looking for we are in this attribute
if (sortedAttributes[0].name === attributeName) {
return true
} else {
return false
}
}
/***
* @param symbols expects e.g. '()', '[]' or '""'
*/
export function isInsideAttribute(currentLineUntrimmed: string, position: Position, symbols: string): boolean {
let numberOfOpenBrackets = 0
let numberOfClosedBrackets = 0
for (let i = 0; i < position.character; i++) {
if (currentLineUntrimmed[i] === symbols[0]) {
numberOfOpenBrackets++
} else if (currentLineUntrimmed[i] === symbols[1]) {
numberOfClosedBrackets++
}
}
return numberOfOpenBrackets > numberOfClosedBrackets
}
export function isInsideFieldArgument(currentLineUntrimmed: string, position: Position): boolean {
const symbols = '()'
let numberOfOpenBrackets = 0
let numberOfClosedBrackets = 0
for (let i = 0; i < position.character; i++) {
if (currentLineUntrimmed[i] === symbols[0]) {
numberOfOpenBrackets++
} else if (currentLineUntrimmed[i] === symbols[1]) {
numberOfClosedBrackets++
}
}
return numberOfOpenBrackets >= 2 && numberOfOpenBrackets > numberOfClosedBrackets
}
export function getValuesInsideSquareBrackets(line: string): string[] {
const regexp = /\[([^\]]+)\]/
const matches = regexp.exec(line)
if (!matches || !matches[1]) {
return []
}
const result = matches[1].split(',').map((name) => {
name = name
// trim whitespace
.trim()
// remove ""?
.replace('"', '')
.replace('"', '')
// Remove period at the end for composite types
if (name.endsWith('.')) {
return name.slice(0, -1)
}
return name
})
return result
}