mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-17 01:49:57 -05:00
Merge pull request #11328 from Microsoft/FixTripleSlashCompletions
Fix triple slash completions
This commit is contained in:
@@ -1097,7 +1097,9 @@ namespace ts {
|
||||
return path.replace(/\\/g, "/");
|
||||
}
|
||||
|
||||
// Returns length of path root (i.e. length of "/", "x:/", "//server/share/, file:///user/files")
|
||||
/**
|
||||
* Returns length of path root (i.e. length of "/", "x:/", "//server/share/, file:///user/files")
|
||||
*/
|
||||
export function getRootLength(path: string): number {
|
||||
if (path.charCodeAt(0) === CharacterCodes.slash) {
|
||||
if (path.charCodeAt(1) !== CharacterCodes.slash) return 1;
|
||||
@@ -1126,9 +1128,14 @@ namespace ts {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally, we represent paths as strings with '/' as the directory separator.
|
||||
* When we make system calls (eg: LanguageServiceHost.getDirectory()),
|
||||
* we expect the host to correctly handle paths in our specified format.
|
||||
*/
|
||||
export const directorySeparator = "/";
|
||||
const directorySeparatorCharCode = CharacterCodes.slash;
|
||||
function getNormalizedParts(normalizedSlashedPath: string, rootLength: number) {
|
||||
function getNormalizedParts(normalizedSlashedPath: string, rootLength: number): string[] {
|
||||
const parts = normalizedSlashedPath.substr(rootLength).split(directorySeparator);
|
||||
const normalized: string[] = [];
|
||||
for (const part of parts) {
|
||||
@@ -1168,6 +1175,11 @@ namespace ts {
|
||||
return path.charCodeAt(path.length - 1) === directorySeparatorCharCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path except for its basename. Eg:
|
||||
*
|
||||
* /path/to/file.ext -> /path/to
|
||||
*/
|
||||
export function getDirectoryPath(path: Path): Path;
|
||||
export function getDirectoryPath(path: string): string;
|
||||
export function getDirectoryPath(path: string): any {
|
||||
|
||||
@@ -171,12 +171,16 @@ namespace ts.server {
|
||||
return this.host.fileExists(path);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string {
|
||||
return this.host.readFile(fileName);
|
||||
}
|
||||
|
||||
directoryExists(path: string): boolean {
|
||||
return this.host.directoryExists(path);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string {
|
||||
return this.host.readFile(fileName);
|
||||
readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] {
|
||||
return this.host.readDirectory(path, extensions, exclude, include);
|
||||
}
|
||||
|
||||
getDirectories(path: string): string[] {
|
||||
|
||||
@@ -325,15 +325,28 @@ namespace ts.Completions {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename.
|
||||
*/
|
||||
function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string, result: CompletionEntry[] = []): CompletionEntry[] {
|
||||
if (fragment === undefined) {
|
||||
fragment = "";
|
||||
}
|
||||
|
||||
fragment = normalizeSlashes(fragment);
|
||||
|
||||
/**
|
||||
* Remove the basename from the path. Note that we don't use the basename to filter completions;
|
||||
* the client is responsible for refining completions.
|
||||
*/
|
||||
fragment = getDirectoryPath(fragment);
|
||||
if (!fragment) {
|
||||
fragment = "./";
|
||||
}
|
||||
else {
|
||||
fragment = ensureTrailingDirectorySeparator(fragment);
|
||||
|
||||
if (fragment === "") {
|
||||
fragment = "." + directorySeparator;
|
||||
}
|
||||
|
||||
fragment = ensureTrailingDirectorySeparator(fragment);
|
||||
|
||||
const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment));
|
||||
const baseDirectory = getDirectoryPath(absolutePath);
|
||||
const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
|
||||
@@ -343,6 +356,12 @@ namespace ts.Completions {
|
||||
const files = tryReadDirectory(host, baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]);
|
||||
|
||||
if (files) {
|
||||
/**
|
||||
* Multiple file entries might map to the same truncated name once we remove extensions
|
||||
* (happens iff includeExtensions === false)so we use a set-like data structure. Eg:
|
||||
*
|
||||
* both foo.ts and foo.tsx become foo
|
||||
*/
|
||||
const foundFiles = createMap<boolean>();
|
||||
for (let filePath of files) {
|
||||
filePath = normalizePath(filePath);
|
||||
@@ -539,36 +558,44 @@ namespace ts.Completions {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const completionInfo: CompletionInfo = {
|
||||
/**
|
||||
* We don't want the editor to offer any other completions, such as snippets, inside a comment.
|
||||
*/
|
||||
isGlobalCompletion: false,
|
||||
isMemberCompletion: false,
|
||||
/**
|
||||
* The user may type in a path that doesn't yet exist, creating a "new identifier"
|
||||
* with respect to the collection of identifiers the server is aware of.
|
||||
*/
|
||||
isNewIdentifierLocation: true,
|
||||
|
||||
entries: []
|
||||
};
|
||||
|
||||
const text = sourceFile.text.substr(range.pos, position - range.pos);
|
||||
|
||||
const match = tripleSlashDirectiveFragmentRegex.exec(text);
|
||||
|
||||
if (match) {
|
||||
const prefix = match[1];
|
||||
const kind = match[2];
|
||||
const toComplete = match[3];
|
||||
|
||||
const scriptPath = getDirectoryPath(sourceFile.path);
|
||||
let entries: CompletionEntry[];
|
||||
if (kind === "path") {
|
||||
// Give completions for a relative path
|
||||
const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length);
|
||||
entries = getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/true, span, sourceFile.path);
|
||||
completionInfo.entries = getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/true, span, sourceFile.path);
|
||||
}
|
||||
else {
|
||||
// Give completions based on the typings available
|
||||
const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length };
|
||||
entries = getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span);
|
||||
completionInfo.entries = getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span);
|
||||
}
|
||||
|
||||
return {
|
||||
isGlobalCompletion: false,
|
||||
isMemberCompletion: false,
|
||||
isNewIdentifierLocation: true,
|
||||
entries
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return completionInfo;
|
||||
}
|
||||
|
||||
function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] {
|
||||
@@ -1674,9 +1701,15 @@ namespace ts.Completions {
|
||||
* Matches a triple slash reference directive with an incomplete string literal for its path. Used
|
||||
* to determine if the caret is currently within the string literal and capture the literal fragment
|
||||
* for completions.
|
||||
* For example, this matches /// <reference path="fragment
|
||||
* For example, this matches
|
||||
*
|
||||
* /// <reference path="fragment
|
||||
*
|
||||
* but not
|
||||
*
|
||||
* /// <reference path="fragment"
|
||||
*/
|
||||
const tripleSlashDirectiveFragmentRegex = /^(\/\/\/\s*<reference\s+(path|types)\s*=\s*(?:'|"))([^\3]*)$/;
|
||||
const tripleSlashDirectiveFragmentRegex = /^(\/\/\/\s*<reference\s+(path|types)\s*=\s*(?:'|"))([^\3"]*)$/;
|
||||
|
||||
interface VisibleModuleInfo {
|
||||
moduleName: string;
|
||||
|
||||
@@ -505,7 +505,11 @@ namespace ts {
|
||||
export interface CompletionInfo {
|
||||
isGlobalCompletion: boolean;
|
||||
isMemberCompletion: boolean;
|
||||
isNewIdentifierLocation: boolean; // true when the current location also allows for a new identifier
|
||||
|
||||
/**
|
||||
* true when the current location also allows for a new identifier
|
||||
*/
|
||||
isNewIdentifierLocation: boolean;
|
||||
entries: CompletionEntry[];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user