/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CompletionItemKind, TextEdit, Range, Position, FileType } from '../htmlLanguageTypes';
import { startsWith } from '../utils/strings';
export class PathCompletionParticipant {
constructor(dataManager, readDirectory) {
this.dataManager = dataManager;
this.readDirectory = readDirectory;
this.atributeCompletions = [];
}
onHtmlAttributeValue(context) {
if (this.dataManager.isPathAttribute(context.tag, context.attribute)) {
this.atributeCompletions.push(context);
}
}
async computeCompletions(document, documentContext) {
const result = { items: [], isIncomplete: false };
for (const attributeCompletion of this.atributeCompletions) {
const fullValue = stripQuotes(document.getText(attributeCompletion.range));
if (isCompletablePath(fullValue)) {
if (fullValue === '.' || fullValue === '..') {
result.isIncomplete = true;
}
else {
const replaceRange = pathToReplaceRange(attributeCompletion.value, fullValue, attributeCompletion.range);
const suggestions = await this.providePathSuggestions(attributeCompletion.value, replaceRange, document, documentContext, attributeCompletion);
for (const item of suggestions) {
result.items.push(item);
}
}
}
}
return result;
}
async providePathSuggestions(valueBeforeCursor, replaceRange, document, documentContext, context) {
const valueBeforeLastSlash = valueBeforeCursor.substring(0, valueBeforeCursor.lastIndexOf('/') + 1); // keep the last slash
let parentDir = documentContext.resolveReference(valueBeforeLastSlash || '.', document.uri);
if (parentDir) {
try {
const result = [];
const infos = await this.readDirectory(parentDir);
// Determine file extensions to prioritize/filter based on tag and attributes
const extensionFilter = this.getExtensionFilter(context);
for (const [name, type] of infos) {
// Exclude paths that start with `.`
if (name.charCodeAt(0) !== CharCode_dot) {
const item = createCompletionItem(name, type === FileType.Directory, replaceRange);
// Apply filtering/sorting based on file extension
if (extensionFilter) {
if (type === FileType.Directory) {
// Always include directories
result.push(item);
}
else {
// For files, check if they match the filter
const matchesFilter = extensionFilter.extensions.some(ext => name.toLowerCase().endsWith(ext));
if (matchesFilter) {
// Add matching files with higher sort priority
item.sortText = '0_' + name;
result.push(item);
}
else if (!extensionFilter.exclusive) {
// Add non-matching files with lower sort priority if not exclusive
item.sortText = '1_' + name;
result.push(item);
}
// If exclusive and doesn't match, don't add the file
}
}
else {
result.push(item);
}
}
}
return result;
}
catch (e) {
// ignore
}
}
return [];
}
/**
* Determines which file extensions to filter/prioritize based on the HTML tag and attributes
*/
getExtensionFilter(context) {
if (!context) {
return undefined;
}
// Handle tag with rel="stylesheet"
if (context.tag === 'link' && context.attribute === 'href' && context.attributes) {
const rel = context.attributes['rel'];
if (rel === 'stylesheet' || rel === '"stylesheet"' || rel === "'stylesheet'") {
// Filter to CSS files for stylesheets
return { extensions: ['.css', '.scss', '.sass', '.less'], exclusive: false };
}
if (rel === 'icon' || rel === '"icon"' || rel === "'icon'" ||
rel === 'apple-touch-icon' || rel === '"apple-touch-icon"' || rel === "'apple-touch-icon'") {
// Filter to image files for icons
return { extensions: ['.ico', '.png', '.svg', '.jpg', '.jpeg', '.gif', '.webp'], exclusive: false };
}
}
// Handle