parent
17fdfd9462
commit
eec50a98f2
6 changed files with 113 additions and 55 deletions
|
@ -2,7 +2,7 @@
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import { CancellationToken, Command, CompletionContext, CompletionItem, InsertTextFormat, MarkupContent, MarkupKind, Position, TextEdit, CompletionList } from 'vscode-languageserver-protocol'
|
import { CancellationToken, Command, CompletionContext, Range, CompletionItem, InsertTextFormat, MarkupContent, MarkupKind, Position, TextEdit, CompletionList } from 'vscode-languageserver-protocol'
|
||||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||||
import { commands, workspace } from 'coc.nvim'
|
import { commands, workspace } from 'coc.nvim'
|
||||||
import { CompletionItemProvider } from 'coc.nvim/lib/provider'
|
import { CompletionItemProvider } from 'coc.nvim/lib/provider'
|
||||||
|
@ -11,7 +11,7 @@ import * as PConst from '../protocol.const'
|
||||||
import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService'
|
import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService'
|
||||||
import API from '../utils/api'
|
import API from '../utils/api'
|
||||||
import { applyCodeAction } from '../utils/codeAction'
|
import { applyCodeAction } from '../utils/codeAction'
|
||||||
import { convertCompletionEntry, getParameterListParts } from '../utils/completionItem'
|
import { DotAccessorContext, convertCompletionEntry, getParameterListParts } from '../utils/completionItem'
|
||||||
import * as Previewer from '../utils/previewer'
|
import * as Previewer from '../utils/previewer'
|
||||||
import * as typeConverters from '../utils/typeConverters'
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
import TypingsStatus from '../utils/typingsStatus'
|
import TypingsStatus from '../utils/typingsStatus'
|
||||||
|
@ -111,8 +111,6 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
|
||||||
}
|
}
|
||||||
|
|
||||||
const { completeOption } = this
|
const { completeOption } = this
|
||||||
const doc = workspace.getDocument(uri)
|
|
||||||
|
|
||||||
const args: Proto.CompletionsRequestArgs & { includeAutomaticOptionalChainCompletions?: boolean } = {
|
const args: Proto.CompletionsRequestArgs & { includeAutomaticOptionalChainCompletions?: boolean } = {
|
||||||
...typeConverters.Position.toFileLocationRequestArgs(file, position),
|
...typeConverters.Position.toFileLocationRequestArgs(file, position),
|
||||||
includeExternalModuleExports: completeOption.autoImports,
|
includeExternalModuleExports: completeOption.autoImports,
|
||||||
|
@ -121,9 +119,14 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
|
||||||
includeAutomaticOptionalChainCompletions: completeOption.includeAutomaticOptionalChainCompletions
|
includeAutomaticOptionalChainCompletions: completeOption.includeAutomaticOptionalChainCompletions
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg: ReadonlyArray<Proto.CompletionEntry> | undefined
|
let entries: ReadonlyArray<Proto.CompletionEntry> | undefined
|
||||||
|
|
||||||
|
let dotAccessorContext: DotAccessorContext | undefined
|
||||||
let isNewIdentifierLocation = true
|
let isNewIdentifierLocation = true
|
||||||
|
let isMemberCompletion = false
|
||||||
|
let isIncomplete = false
|
||||||
|
const isInValidCommitCharacterContext = this.isInValidCommitCharacterContext(document, position)
|
||||||
|
|
||||||
if (this.client.apiVersion.gte(API.v300)) {
|
if (this.client.apiVersion.gte(API.v300)) {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.interruptGetErr(() => this.client.execute('completionInfo', args, token))
|
const response = await this.client.interruptGetErr(() => this.client.execute('completionInfo', args, token))
|
||||||
|
@ -131,7 +134,20 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
isNewIdentifierLocation = response.body.isNewIdentifierLocation
|
isNewIdentifierLocation = response.body.isNewIdentifierLocation
|
||||||
msg = response.body.entries
|
isMemberCompletion = response.body.isMemberCompletion
|
||||||
|
if (isMemberCompletion) {
|
||||||
|
const dotMatch = preText.slice(0, position.character).match(/\??\.\s*$/) || undefined
|
||||||
|
if (dotMatch) {
|
||||||
|
const range = Range.create({
|
||||||
|
line: position.line,
|
||||||
|
character: position.character - dotMatch.length
|
||||||
|
}, position)
|
||||||
|
const text = document.getText(range)
|
||||||
|
dotAccessorContext = { range, text }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isIncomplete = (response as any).metadata && (response as any).metadata.isIncomplete
|
||||||
|
entries = response.body.entries
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message == 'No content available.') {
|
if (e.message == 'No content available.') {
|
||||||
return null
|
return null
|
||||||
|
@ -143,11 +159,11 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
|
||||||
if (response.type !== 'response' || !response.body) {
|
if (response.type !== 'response' || !response.body) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
msg = response.body
|
entries = response.body
|
||||||
}
|
}
|
||||||
|
|
||||||
const completionItems: CompletionItem[] = []
|
const completionItems: CompletionItem[] = []
|
||||||
for (const element of msg) {
|
for (const element of entries) {
|
||||||
if (shouldExcludeCompletionEntry(element, completeOption)) {
|
if (shouldExcludeCompletionEntry(element, completeOption)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -155,21 +171,17 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
|
||||||
element,
|
element,
|
||||||
uri,
|
uri,
|
||||||
position,
|
position,
|
||||||
completeOption.completeFunctionCalls,
|
{
|
||||||
isNewIdentifierLocation
|
isNewIdentifierLocation,
|
||||||
|
isMemberCompletion,
|
||||||
|
enableCallCompletions: completeOption.completeFunctionCalls,
|
||||||
|
isInValidCommitCharacterContext,
|
||||||
|
dotAccessorContext,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
completionItems.push(item)
|
completionItems.push(item)
|
||||||
}
|
}
|
||||||
let startcol: number | null = null
|
return { isIncomplete, items: completionItems }
|
||||||
if (triggerCharacter == '@' && !doc.isWord('@')) {
|
|
||||||
startcol = option.col - 1
|
|
||||||
}
|
|
||||||
let res: any = {
|
|
||||||
startcol,
|
|
||||||
isIncomplete: false,
|
|
||||||
items: completionItems
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTsTriggerCharacter(context: CompletionContext): Proto.CompletionsTriggerCharacter | undefined {
|
private getTsTriggerCharacter(context: CompletionContext): Proto.CompletionsTriggerCharacter | undefined {
|
||||||
|
@ -229,10 +241,9 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
const detail = details[0]
|
const detail = details[0]
|
||||||
item.detail = detail.displayParts.length
|
if (!item.detail && detail.displayParts.length) {
|
||||||
? Previewer.plain(detail.displayParts)
|
item.detail = Previewer.plain(detail.displayParts)
|
||||||
: undefined
|
}
|
||||||
|
|
||||||
item.documentation = this.getDocumentation(detail)
|
item.documentation = this.getDocumentation(detail)
|
||||||
const { command, additionalTextEdits } = this.getCodeActions(detail, filepath)
|
const { command, additionalTextEdits } = this.getCodeActions(detail, filepath)
|
||||||
if (command) item.command = command
|
if (command) item.command = command
|
||||||
|
@ -243,6 +254,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
|
||||||
this.createSnippetOfFunctionCall(item, detail)
|
this.createSnippetOfFunctionCall(item, detail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,6 +402,24 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isInValidCommitCharacterContext(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position
|
||||||
|
): boolean {
|
||||||
|
if (this.client.apiVersion.lt(API.v320)) {
|
||||||
|
// Workaround for https://github.com/Microsoft/TypeScript/issues/27742
|
||||||
|
// Only enable dot completions when previous character not a dot preceded by whitespace.
|
||||||
|
// Prevents incorrectly completing while typing spread operators.
|
||||||
|
if (position.character > 1) {
|
||||||
|
const preText = document.getText(Range.create(
|
||||||
|
position.line, 0,
|
||||||
|
position.line, position.character))
|
||||||
|
return preText.match(/(\s|^)\.$/ig) === null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldExcludeCompletionEntry(
|
function shouldExcludeCompletionEntry(
|
||||||
|
|
|
@ -20,7 +20,7 @@ const getSymbolKind = (kind: string): SymbolKind => {
|
||||||
return SymbolKind.Enum
|
return SymbolKind.Enum
|
||||||
case PConst.Kind.interface:
|
case PConst.Kind.interface:
|
||||||
return SymbolKind.Interface
|
return SymbolKind.Interface
|
||||||
case PConst.Kind.memberFunction:
|
case PConst.Kind.method:
|
||||||
return SymbolKind.Method
|
return SymbolKind.Method
|
||||||
case PConst.Kind.memberVariable:
|
case PConst.Kind.memberVariable:
|
||||||
return SymbolKind.Property
|
return SymbolKind.Property
|
||||||
|
|
|
@ -87,7 +87,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip
|
||||||
return super.getSymbolRange(document, item)
|
return super.getSymbolRange(document, item)
|
||||||
|
|
||||||
case PConst.Kind.class:
|
case PConst.Kind.class:
|
||||||
case PConst.Kind.memberFunction:
|
case PConst.Kind.method:
|
||||||
case PConst.Kind.memberVariable:
|
case PConst.Kind.memberVariable:
|
||||||
case PConst.Kind.memberGetAccessor:
|
case PConst.Kind.memberGetAccessor:
|
||||||
case PConst.Kind.memberSetAccessor:
|
case PConst.Kind.memberSetAccessor:
|
||||||
|
|
|
@ -87,7 +87,7 @@ export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBase
|
||||||
}
|
}
|
||||||
// fallthrough
|
// fallthrough
|
||||||
|
|
||||||
case PConst.Kind.memberFunction:
|
case PConst.Kind.method:
|
||||||
case PConst.Kind.memberVariable:
|
case PConst.Kind.memberVariable:
|
||||||
case PConst.Kind.memberGetAccessor:
|
case PConst.Kind.memberGetAccessor:
|
||||||
case PConst.Kind.memberSetAccessor:
|
case PConst.Kind.memberSetAccessor:
|
||||||
|
|
|
@ -12,6 +12,7 @@ export class Kind {
|
||||||
public static readonly constructSignature = 'construct'
|
public static readonly constructSignature = 'construct'
|
||||||
public static readonly directory = 'directory'
|
public static readonly directory = 'directory'
|
||||||
public static readonly enum = 'enum'
|
public static readonly enum = 'enum'
|
||||||
|
public static readonly enumMember = 'enum member'
|
||||||
public static readonly externalModuleName = 'external module name'
|
public static readonly externalModuleName = 'external module name'
|
||||||
public static readonly function = 'function'
|
public static readonly function = 'function'
|
||||||
public static readonly indexSignature = 'index'
|
public static readonly indexSignature = 'index'
|
||||||
|
@ -20,7 +21,7 @@ export class Kind {
|
||||||
public static readonly let = 'let'
|
public static readonly let = 'let'
|
||||||
public static readonly localFunction = 'local function'
|
public static readonly localFunction = 'local function'
|
||||||
public static readonly localVariable = 'local var'
|
public static readonly localVariable = 'local var'
|
||||||
public static readonly memberFunction = 'method'
|
public static readonly method = 'method'
|
||||||
public static readonly memberGetAccessor = 'getter'
|
public static readonly memberGetAccessor = 'getter'
|
||||||
public static readonly memberSetAccessor = 'setter'
|
public static readonly memberSetAccessor = 'setter'
|
||||||
public static readonly memberVariable = 'property'
|
public static readonly memberVariable = 'property'
|
||||||
|
@ -32,6 +33,7 @@ export class Kind {
|
||||||
public static readonly warning = 'warning'
|
public static readonly warning = 'warning'
|
||||||
public static readonly string = 'string'
|
public static readonly string = 'string'
|
||||||
public static readonly parameter = 'parameter'
|
public static readonly parameter = 'parameter'
|
||||||
|
public static readonly typeParameter = 'type parameter'
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DiagnosticCategory {
|
export class DiagnosticCategory {
|
||||||
|
|
|
@ -2,56 +2,57 @@
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import { workspace } from 'coc.nvim'
|
|
||||||
import { Range, CompletionItem, CompletionItemKind, InsertTextFormat, Position, TextEdit } from 'vscode-languageserver-protocol'
|
import { Range, CompletionItem, CompletionItemKind, InsertTextFormat, Position, TextEdit } from 'vscode-languageserver-protocol'
|
||||||
import * as Proto from '../protocol'
|
import * as Proto from '../protocol'
|
||||||
import * as PConst from '../protocol.const'
|
import * as PConst from '../protocol.const'
|
||||||
|
|
||||||
interface CommitCharactersSettings {
|
|
||||||
readonly isNewIdentifierLocation: boolean
|
|
||||||
readonly isInValidCommitCharacterContext: boolean
|
|
||||||
readonly useCodeSnippetsOnMethodSuggest: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ParamterListParts {
|
interface ParamterListParts {
|
||||||
readonly parts: ReadonlyArray<Proto.SymbolDisplayPart>
|
readonly parts: ReadonlyArray<Proto.SymbolDisplayPart>
|
||||||
readonly hasOptionalParameters: boolean
|
readonly hasOptionalParameters: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DotAccessorContext {
|
||||||
|
readonly range: Range
|
||||||
|
readonly text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompletionContext {
|
||||||
|
readonly isNewIdentifierLocation: boolean
|
||||||
|
readonly isMemberCompletion: boolean
|
||||||
|
readonly isInValidCommitCharacterContext: boolean
|
||||||
|
readonly enableCallCompletions: boolean
|
||||||
|
readonly dotAccessorContext?: DotAccessorContext
|
||||||
|
}
|
||||||
|
|
||||||
export function convertCompletionEntry(
|
export function convertCompletionEntry(
|
||||||
tsEntry: Proto.CompletionEntry,
|
tsEntry: Proto.CompletionEntry,
|
||||||
uri: string,
|
uri: string,
|
||||||
position: Position,
|
position: Position,
|
||||||
useCodeSnippetsOnMethodSuggest: boolean,
|
context: CompletionContext,
|
||||||
isNewIdentifierLocation: boolean
|
|
||||||
): CompletionItem {
|
): CompletionItem {
|
||||||
let label = tsEntry.name
|
let label = tsEntry.name
|
||||||
let sortText = tsEntry.sortText
|
let sortText = tsEntry.sortText
|
||||||
|
let preselect = false
|
||||||
|
let detail: string
|
||||||
if (tsEntry.isRecommended) {
|
if (tsEntry.isRecommended) {
|
||||||
// Make sure isRecommended property always comes first
|
preselect = true
|
||||||
// https://github.com/Microsoft/vscode/issues/40325
|
}
|
||||||
sortText = '\0' + sortText
|
if (tsEntry.source) {
|
||||||
} else if (tsEntry.source) {
|
// De-prioritze auto-imports https://github.com/Microsoft/vscode/issues/40311
|
||||||
// De-prioritze auto-imports
|
|
||||||
// https://github.com/Microsoft/vscode/issues/40311
|
|
||||||
sortText = '\uffff' + sortText
|
sortText = '\uffff' + sortText
|
||||||
} else {
|
} else {
|
||||||
sortText = tsEntry.sortText
|
sortText = tsEntry.sortText
|
||||||
}
|
}
|
||||||
let kind = convertKind(tsEntry.kind)
|
let kind = convertKind(tsEntry.kind)
|
||||||
let insertTextFormat = (
|
let insertTextFormat = (
|
||||||
useCodeSnippetsOnMethodSuggest &&
|
context.enableCallCompletions &&
|
||||||
(kind === CompletionItemKind.Function ||
|
(kind === CompletionItemKind.Function ||
|
||||||
kind === CompletionItemKind.Method)
|
kind === CompletionItemKind.Method)
|
||||||
) ? InsertTextFormat.Snippet : InsertTextFormat.PlainText
|
) ? InsertTextFormat.Snippet : InsertTextFormat.PlainText
|
||||||
|
|
||||||
let insertText = tsEntry.insertText
|
let insertText = tsEntry.insertText
|
||||||
let document = workspace.getDocument(uri)
|
let commitCharacters = getCommitCharacters(tsEntry, context)
|
||||||
let preText = document.getline(position.line).slice(0, position.character)
|
|
||||||
const isInValidCommitCharacterContext = preText.match(/(^|[a-z_$\(\)\[\]\{\}]|[^.]\.)\s*$/ig) !== null
|
|
||||||
|
|
||||||
let commitCharacters = getCommitCharacters(tsEntry, { isNewIdentifierLocation, isInValidCommitCharacterContext, useCodeSnippetsOnMethodSuggest })
|
|
||||||
let optional = tsEntry.kindModifiers && tsEntry.kindModifiers.match(/\boptional\b/)
|
|
||||||
let textEdit: TextEdit | null = null
|
let textEdit: TextEdit | null = null
|
||||||
if (tsEntry.replacementSpan) {
|
if (tsEntry.replacementSpan) {
|
||||||
let { start, end } = tsEntry.replacementSpan
|
let { start, end } = tsEntry.replacementSpan
|
||||||
|
@ -62,17 +63,41 @@ export function convertCompletionEntry(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (tsEntry.kindModifiers) {
|
||||||
|
const kindModifiers = new Set(tsEntry.kindModifiers.split(/,|\s+/g))
|
||||||
|
if (kindModifiers.has(PConst.KindModifiers.optional)) {
|
||||||
|
label += '?'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kindModifiers.has(PConst.KindModifiers.color)) {
|
||||||
|
kind = CompletionItemKind.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tsEntry.kind === PConst.Kind.script) {
|
||||||
|
for (const extModifier of PConst.KindModifiers.fileExtensionKindModifiers) {
|
||||||
|
if (kindModifiers.has(extModifier)) {
|
||||||
|
if (tsEntry.name.toLowerCase().endsWith(extModifier)) {
|
||||||
|
detail = tsEntry.name
|
||||||
|
} else {
|
||||||
|
detail = tsEntry.name + extModifier
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
label,
|
label,
|
||||||
insertText,
|
insertText,
|
||||||
textEdit,
|
textEdit,
|
||||||
kind,
|
kind,
|
||||||
|
preselect,
|
||||||
insertTextFormat,
|
insertTextFormat,
|
||||||
sortText,
|
sortText,
|
||||||
commitCharacters,
|
commitCharacters,
|
||||||
|
detail,
|
||||||
data: {
|
data: {
|
||||||
uri,
|
uri,
|
||||||
optional,
|
|
||||||
position,
|
position,
|
||||||
source: tsEntry.source || ''
|
source: tsEntry.source || ''
|
||||||
}
|
}
|
||||||
|
@ -97,7 +122,7 @@ function convertKind(kind: string): CompletionItemKind {
|
||||||
return CompletionItemKind.Field
|
return CompletionItemKind.Field
|
||||||
case PConst.Kind.function:
|
case PConst.Kind.function:
|
||||||
return CompletionItemKind.Function
|
return CompletionItemKind.Function
|
||||||
case PConst.Kind.memberFunction:
|
case PConst.Kind.method:
|
||||||
case PConst.Kind.constructSignature:
|
case PConst.Kind.constructSignature:
|
||||||
case PConst.Kind.callSignature:
|
case PConst.Kind.callSignature:
|
||||||
case PConst.Kind.indexSignature:
|
case PConst.Kind.indexSignature:
|
||||||
|
@ -121,8 +146,8 @@ function convertKind(kind: string): CompletionItemKind {
|
||||||
return CompletionItemKind.Variable
|
return CompletionItemKind.Variable
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCommitCharacters(tsEntry: Proto.CompletionEntry, settings: CommitCharactersSettings): string[] | undefined {
|
function getCommitCharacters(tsEntry: Proto.CompletionEntry, context: CompletionContext): string[] | undefined {
|
||||||
if (settings.isNewIdentifierLocation || !settings.isInValidCommitCharacterContext) {
|
if (context.isNewIdentifierLocation || !context.isInValidCommitCharacterContext) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const commitCharacters: string[] = []
|
const commitCharacters: string[] = []
|
||||||
|
@ -145,10 +170,11 @@ function getCommitCharacters(tsEntry: Proto.CompletionEntry, settings: CommitCha
|
||||||
case PConst.Kind.memberVariable:
|
case PConst.Kind.memberVariable:
|
||||||
case PConst.Kind.class:
|
case PConst.Kind.class:
|
||||||
case PConst.Kind.function:
|
case PConst.Kind.function:
|
||||||
case PConst.Kind.memberFunction:
|
case PConst.Kind.method:
|
||||||
case PConst.Kind.keyword:
|
case PConst.Kind.keyword:
|
||||||
|
case PConst.Kind.parameter:
|
||||||
commitCharacters.push('.', ',', ';')
|
commitCharacters.push('.', ',', ';')
|
||||||
if (settings.useCodeSnippetsOnMethodSuggest) {
|
if (context.enableCallCompletions) {
|
||||||
commitCharacters.push('(')
|
commitCharacters.push('(')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
Loading…
Reference in a new issue