<commit_before>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { Position, Range, CompletionItemProvider, CompletionItemKind, TextDocument, CancellationToken, CompletionItem, window, Uri, TextEditor, SnippetString, workspace } from 'vscode';

import { ITypeScriptServiceClient } from '../typescriptService';
import * as Proto from '../protocol';

import * as nls from 'vscode-nls';
import * as typeConverters from '../utils/typeConverters';
import { Command, CommandManager } from '../utils/commandManager';
const localize = nls.loadMessageBundle();

const configurationNamespace = 'jsDocCompletion';

namespace Configuration {
	export const enabled = 'enabled';
}

class JsDocCompletionItem extends CompletionItem {
	constructor(
		document: TextDocument,
		position: Position,
		shouldGetJSDocFromTSServer: boolean,
	) {
		super('/** */', CompletionItemKind.Snippet);
		this.detail = localize('typescript.jsDocCompletionItem.documentation', 'JSDoc comment');
		this.insertText = '';
		this.sortText = '\0';

		const line = document.lineAt(position.line).text;
		const prefix = line.slice(0, position.character).match(/\/\**\s*$/);
		const suffix = line.slice(position.character).match(/^\s*\**\//);
		const start = position.translate(0, prefix ? -prefix[0].length : 0);
		this.range = new Range(
			start,
			position.translate(0, suffix ? suffix[0].length : 0));

		this.command = {
			title: 'Try Complete JSDoc',
			command: TryCompleteJsDocCommand.COMMAND_NAME,
			arguments: [document.uri, start, shouldGetJSDocFromTSServer]
		};
	}
}

export default class JsDocCompletionProvider implements CompletionItemProvider {

	constructor(
		private readonly client: ITypeScriptServiceClient,
		commandManager: CommandManager
	) {
		commandManager.register(new TryCompleteJsDocCommand(client));
	}

	public async provideCompletionItems(
		document: TextDocument,
		position: Position,
		token: CancellationToken
	): Promise<CompletionItem[]> {
		const file = this.client.normalizePath(document.uri);
		if (!file) {
			return [];
		}

		// TODO: unregister provider when disabled
		const enableJsDocCompletions = workspace.getConfiguration(configurationNamespace, document.uri).get<boolean>(Configuration.enabled, true);
		if (!enableJsDocCompletions) {
			return [];
		}

		// Only show the JSdoc completion when the everything before the cursor is whitespace
		// or could be the opening of a comment
		const line = document.lineAt(position.line).text;
		const prefix = line.slice(0, position.character);
		if (prefix.match(/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/) === null) {
			return [];
		}

		const args: Proto.FileRequestArgs = {
			file
		};
		const response = await Promise.race([
			this.client.execute('navtree', args, token),
			new Promise<Proto.NavTreeResponse>((resolve) => setTimeout(resolve, 250))
		]);
		if (!response || !response.body) {
			return [];
		}

		const body = response.body;

		function matchesPosition(tree: Proto.NavigationTree): boolean {
			if (!tree.spans.length) {
				return false;
			}
			const span = typeConverters.Range.fromTextSpan(tree.spans[0]);
			if (position.line === span.start.line - 1 || position.line === span.start.line) {
				return true;
			}

			return tree.childItems ? tree.childItems.some(matchesPosition) : false;
		}

		if (!matchesPosition(body)) {
			return [];
		}

		return [new JsDocCompletionItem(document, position, enableJsDocCompletions)];
	}

	public resolveCompletionItem(item: CompletionItem, _token: CancellationToken) {
		return item;
	}
}

class TryCompleteJsDocCommand implements Command {
	public static readonly COMMAND_NAME = '_typeScript.tryCompleteJsDoc';<commit_msg>Extracting methods<commit_after>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { Position, Range, CompletionItemProvider, CompletionItemKind, TextDocument, CancellationToken, CompletionItem, window, Uri, TextEditor, SnippetString, workspace } from 'vscode';

import { ITypeScriptServiceClient } from '../typescriptService';
import * as Proto from '../protocol';

import * as nls from 'vscode-nls';
import * as typeConverters from '../utils/typeConverters';
import { Command, CommandManager } from '../utils/commandManager';
const localize = nls.loadMessageBundle();

const configurationNamespace = 'jsDocCompletion';

namespace Configuration {
	export const enabled = 'enabled';
}

class JsDocCompletionItem extends CompletionItem {
	constructor(
		document: TextDocument,
		position: Position,
		shouldGetJSDocFromTSServer: boolean,
	) {
		super('/** */', CompletionItemKind.Snippet);
		this.detail = localize('typescript.jsDocCompletionItem.documentation', 'JSDoc comment');
		this.insertText = '';
		this.sortText = '\0';

		const line = document.lineAt(position.line).text;
		const prefix = line.slice(0, position.character).match(/\/\**\s*$/);
		const suffix = line.slice(position.character).match(/^\s*\**\//);
		const start = position.translate(0, prefix ? -prefix[0].length : 0);
		this.range = new Range(
			start,
			position.translate(0, suffix ? suffix[0].length : 0));

		this.command = {
			title: 'Try Complete JSDoc',
			command: TryCompleteJsDocCommand.COMMAND_NAME,
			arguments: [document.uri, start, shouldGetJSDocFromTSServer]
		};
	}
}

export default class JsDocCompletionProvider implements CompletionItemProvider {

	constructor(
		private readonly client: ITypeScriptServiceClient,
		commandManager: CommandManager
	) {
		commandManager.register(new TryCompleteJsDocCommand(client));
	}

	public async provideCompletionItems(
		document: TextDocument,
		position: Position,
		token: CancellationToken
	): Promise<CompletionItem[]> {
		const file = this.client.normalizePath(document.uri);
		if (!file) {
			return [];
		}

		// TODO: unregister provider when disabled
		const enableJsDocCompletions = workspace.getConfiguration(configurationNamespace, document.uri).get<boolean>(Configuration.enabled, true);
		if (!enableJsDocCompletions) {
			return [];
		}

		if (!this.isValidCursorPosition(document, position)) {
			return [];
		}

		if (!await this.isCommentableLocation(file, position, token)) {
			return [];
		}

		return [new JsDocCompletionItem(document, position, enableJsDocCompletions)];
	}

	private async isCommentableLocation(
		file: string,
		position: Position,
		token: CancellationToken
	): Promise<boolean> {
		const args: Proto.FileRequestArgs = {
			file
		};
		const response = await Promise.race([
			this.client.execute('navtree', args, token),
			new Promise<Proto.NavTreeResponse>((resolve) => setTimeout(resolve, 250))
		]);

		if (!response || !response.body) {
			return false;
		}

		const body = response.body;

		function matchesPosition(tree: Proto.NavigationTree): boolean {
			if (!tree.spans.length) {
				return false;
			}
			const span = typeConverters.Range.fromTextSpan(tree.spans[0]);
			if (position.line === span.start.line - 1 || position.line === span.start.line) {
				return true;
			}

			return tree.childItems ? tree.childItems.some(matchesPosition) : false;
		}

		return matchesPosition(body);
	}

	private isValidCursorPosition(document: TextDocument, position: Position): boolean {
		// Only show the JSdoc completion when the everything before the cursor is whitespace
		// or could be the opening of a comment
		const line = document.lineAt(position.line).text;
		const prefix = line.slice(0, position.character);
		return prefix.match(/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/) !== null;
	}

	public resolveCompletionItem(item: CompletionItem, _token: CancellationToken) {
		return item;
	}
}

class TryCompleteJsDocCommand implements Command {
	public static readonly COMMAND_NAME = '_typeScript.tryCompleteJsDoc';
{'fraction_non_alphanumeric': 0.16600120992135511, 'fraction_numerical': 0.0033877797943133695, 'mean_word_length': 3.625629546726357, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2280017', 'n_tokens_mistral': 2538, 'n_tokens_neox': 2311, 'n_words': 571}