<commit_before>import * as vscode from 'vscode'
import {latexParser} from 'latex-utensils'

import type {Extension} from '../../main'
import type {IProvider, ILwCompletionItem} from './interface'

enum GlossaryType {
    glossary,
    acronym
}

interface GlossaryEntry {
    label: string | undefined,
    description: string | undefined
}

export interface Suggestion extends ILwCompletionItem {
    type: GlossaryType
}

export class Glossary implements IProvider {
    private readonly extension: Extension
    // use object for deduplication
    private readonly glossaries = new Map<string, Suggestion>()
    private readonly acronyms = new Map<string, Suggestion>()

    constructor(extension: Extension) {
        this.extension = extension
    }

    provideFrom(result: RegExpMatchArray) {
        return this.provide(result)
    }

    private provide(result: RegExpMatchArray): vscode.CompletionItem[] {
        this.updateAll()
        let suggestions: Map<string, Suggestion>

        if (result[1] && result[1].match(/^ac/i)) {
            suggestions = this.acronyms
        } else {
            suggestions = new Map( [...this.acronyms, ...this.glossaries] )
        }

        // Compile the suggestion object to array
        const items = Array.from(suggestions.values())
        return items
    }

    private getGlossaryFromNodeArray(nodes: latexParser.Node[]): Suggestion[] {
        const glossaries: Suggestion[] = []
        let entry: GlossaryEntry
        let type: GlossaryType | undefined

        nodes.forEach(node => {
            if (latexParser.isCommand(node) && node.args.length > 0) {
                switch (node.name) {
                    case 'newglossaryentry':
                        type = GlossaryType.glossary
                        entry = this.getShortNodeDescription(node)
                        break
                    case 'provideglossaryentry':
                        type = GlossaryType.glossary
                        entry = this.getShortNodeDescription(node)
                        break
                    case 'longnewglossaryentry':
                        type = GlossaryType.glossary
                        entry = this.getLongNodeLabelDescription(node)
                        break
                    case 'longprovideglossaryentry':
                        type = GlossaryType.glossary
                        entry = this.getLongNodeLabelDescription(node)
                        break
                    case 'newacronym':
                        type = GlossaryType.acronym
                        entry = this.getLongNodeLabelDescription(node)
                        break
                    default:
                        break
                }
                if (type !== undefined && entry.description !== undefined && entry.label !== undefined) {
                    glossaries.push({
                        type,
                        label: entry.label,
                        detail: entry.description,
                        kind: vscode.CompletionItemKind.Reference
                    })
                }
            }
        })
        return glossaries
    }

    /**
     * Parse the description from "long nodes" such as \newacronym and \longnewglossaryentry
     *
     * Spec: \newacronym[〈key-val list〉]{〈label〉}{〈abbrv〉}{〈description〉}
     *
     * Fairly straightforward, a \newacronym command takes the form
     *     \newacronym[optional parameters]{lw}{LW}{LaTeX Workshop}
     *
     *
     * @param node the \newacronym node from the parser
     * @return the pair (label, description)
     */
    private getLongNodeLabelDescription(node: latexParser.Command): GlossaryEntry {
        let description: string | undefined = undefined
        let label: string | undefined = undefined

        const hasOptionalArg: boolean = latexParser.isOptionalArg(node.args[0])
        const labelNode = hasOptionalArg ? node.args[1] : node.args[0]
        const descriptionNode = hasOptionalArg ? node.args[3] : node.args[2]
        if (latexParser.isGroup(descriptionNode)) {
            description = latexParser.stringify(descriptionNode).slice(1, -1)
        }

        if (latexParser.isGroup(labelNode)) {
            label = latexParser.stringify(labelNode).slice(1, -1)
        }

        return {label, description}
    }

    /**
     * Parse the description from "short nodes" like \newglossaryentry
     *
     * Spec: \newglossaryentry{〈label〉}{〈key=value list〉}
     *
     * Example glossary entries:
     *     \newglossaryentry{lw}{name={LaTeX Workshop}, description={What this extension is}}
     *     \newglossaryentry{vscode}{name=VSCode, description=Editor}
     *
     * Note: descriptions can be single words or a {group of words}
     *
     * @param node the \newglossaryentry node from the parser
     * @returns the value of the description field
     */
    private getShortNodeDescription(node: latexParser.Command): GlossaryEntry {
        let result: RegExpExecArray | null
        let description: string | undefined = undefined
        let label: string | undefined = undefined
        let lastNodeWasDescription = false

        // Get label
        if (latexParser.isGroup(node.args[0])) {
            label = latexParser.stringify(node.args[0]).slice(1, -1)
        }

        // Get description
        if (latexParser.isGroup(node.args[1])) {
            for (const subNode of node.args[1].content) {
                if (latexParser.isTextString(subNode)) {
                    // Description is of the form description=single_word
                    if ((result = /description=(.*)/.exec(subNode.content)) !== null) {
                        if (result[1] !== '') {
                            description = result[1]
                            break
                        }
                        lastNodeWasDescription = true
                    }
                } else if (lastNodeWasDescription && latexParser.isGroup(subNode)) {
                    // otherwise we have description={group of words}
                    description = latexParser.stringify(subNode).slice(1, -1)
                    break
                }<commit_msg>Check the number of arguments<commit_after>import * as vscode from 'vscode'
import {latexParser} from 'latex-utensils'

import type {Extension} from '../../main'
import type {IProvider, ILwCompletionItem} from './interface'

enum GlossaryType {
    glossary,
    acronym
}

interface GlossaryEntry {
    label: string | undefined,
    description: string | undefined
}

export interface Suggestion extends ILwCompletionItem {
    type: GlossaryType
}

export class Glossary implements IProvider {
    private readonly extension: Extension
    // use object for deduplication
    private readonly glossaries = new Map<string, Suggestion>()
    private readonly acronyms = new Map<string, Suggestion>()

    constructor(extension: Extension) {
        this.extension = extension
    }

    provideFrom(result: RegExpMatchArray) {
        return this.provide(result)
    }

    private provide(result: RegExpMatchArray): vscode.CompletionItem[] {
        this.updateAll()
        let suggestions: Map<string, Suggestion>

        if (result[1] && result[1].match(/^ac/i)) {
            suggestions = this.acronyms
        } else {
            suggestions = new Map( [...this.acronyms, ...this.glossaries] )
        }

        // Compile the suggestion object to array
        const items = Array.from(suggestions.values())
        return items
    }

    private getGlossaryFromNodeArray(nodes: latexParser.Node[]): Suggestion[] {
        const glossaries: Suggestion[] = []
        let entry: GlossaryEntry
        let type: GlossaryType | undefined

        nodes.forEach(node => {
            if (latexParser.isCommand(node) && node.args.length > 0) {
                switch (node.name) {
                    case 'newglossaryentry':
                        type = GlossaryType.glossary
                        entry = this.getShortNodeDescription(node)
                        break
                    case 'provideglossaryentry':
                        type = GlossaryType.glossary
                        entry = this.getShortNodeDescription(node)
                        break
                    case 'longnewglossaryentry':
                        type = GlossaryType.glossary
                        entry = this.getLongNodeLabelDescription(node)
                        break
                    case 'longprovideglossaryentry':
                        type = GlossaryType.glossary
                        entry = this.getLongNodeLabelDescription(node)
                        break
                    case 'newacronym':
                        type = GlossaryType.acronym
                        entry = this.getLongNodeLabelDescription(node)
                        break
                    default:
                        break
                }
                if (type !== undefined && entry.description !== undefined && entry.label !== undefined) {
                    glossaries.push({
                        type,
                        label: entry.label,
                        detail: entry.description,
                        kind: vscode.CompletionItemKind.Reference
                    })
                }
            }
        })
        return glossaries
    }

    /**
     * Parse the description from "long nodes" such as \newacronym and \longnewglossaryentry
     *
     * Spec: \newacronym[〈key-val list〉]{〈label〉}{〈abbrv〉}{〈description〉}
     *
     * Fairly straightforward, a \newacronym command takes the form
     *     \newacronym[optional parameters]{lw}{LW}{LaTeX Workshop}
     *
     *
     * @param node the \newacronym node from the parser
     * @return the pair (label, description)
     */
    private getLongNodeLabelDescription(node: latexParser.Command): GlossaryEntry {
        let description: string | undefined = undefined
        let label: string | undefined = undefined

        // We expect 3 arguments + 1 optional argument
        if (node.args.length < 3 || node.args.length > 4) {
            return {label: undefined, description: undefined}
        }
        const hasOptionalArg: boolean = latexParser.isOptionalArg(node.args[0])

        // First arg is optional, we must have 4 arguments
        if (hasOptionalArg && node.args.length !== 4) {
            return {label: undefined, description: undefined}
        }

        const labelNode = hasOptionalArg ? node.args[1] : node.args[0]
        const descriptionNode = hasOptionalArg ? node.args[3] : node.args[2]
        if (latexParser.isGroup(descriptionNode)) {
            description = latexParser.stringify(descriptionNode).slice(1, -1)
        }

        if (latexParser.isGroup(labelNode)) {
            label = latexParser.stringify(labelNode).slice(1, -1)
        }

        return {label, description}
    }

    /**
     * Parse the description from "short nodes" like \newglossaryentry
     *
     * Spec: \newglossaryentry{〈label〉}{〈key=value list〉}
     *
     * Example glossary entries:
     *     \newglossaryentry{lw}{name={LaTeX Workshop}, description={What this extension is}}
     *     \newglossaryentry{vscode}{name=VSCode, description=Editor}
     *
     * Note: descriptions can be single words or a {group of words}
     *
     * @param node the \newglossaryentry node from the parser
     * @returns the value of the description field
     */
    private getShortNodeDescription(node: latexParser.Command): GlossaryEntry {
        let result: RegExpExecArray | null
        let description: string | undefined = undefined
        let label: string | undefined = undefined
        let lastNodeWasDescription = false

        // We expect 2 arguments
        if (node.args.length !== 2) {
            return {label: undefined, description: undefined}
        }

        // Get label
        if (latexParser.isGroup(node.args[0])) {
            label = latexParser.stringify(node.args[0]).slice(1, -1)
        }

        // Get description
        if (latexParser.isGroup(node.args[1])) {
            for (const subNode of node.args[1].content) {
                if (latexParser.isTextString(subNode)) {
                    // Description is of the form description=single_word
                    if ((result = /description=(.*)/.exec(subNode.content)) !== null) {
                        if (result[1] !== '') {
                            description = result[1]
                            break
                        }
                        lastNodeWasDescription = true
                    }
                } else if (lastNodeWasDescription && latexParser.isGroup(subNode)) {
                    // otherwise we have description={group of words}
                    description = latexParser.stringify(subNode).slice(1, -1)
                    break
                }
{'fraction_non_alphanumeric': 0.09216839498529639, 'fraction_numerical': 0.004024144869215292, 'mean_word_length': 1.7630960017104982, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 14, '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': '2119861', 'n_tokens_mistral': 3451, 'n_tokens_neox': 3260, 'n_words': 801}