diff --git a/l10n/bundle.l10n.es.json b/l10n/bundle.l10n.es.json index dbcbb1828..2c6db0f63 100644 --- a/l10n/bundle.l10n.es.json +++ b/l10n/bundle.l10n.es.json @@ -243,5 +243,14 @@ "Would you like to reload the window to activate the clangd extension?": "¿Le gustaría recargar la ventana para activar la extensión clangd?", "Reload": "Recargar", "Later": "Más tarde", - "Failed to install clangd extension. You can install it manually from the Extensions marketplace.": "Error al instalar la extensión clangd. Puede instalarla manualmente desde el marketplace de extensiones." + "Failed to install clangd extension. You can install it manually from the Extensions marketplace.": "Error al instalar la extensión clangd. Puede instalarla manualmente desde el marketplace de extensiones.", + "🔗 Reference Documentation": "🔗 Documentación de Referencia", + "Open {0}": "Abrir {0}", + "💡 New ESP-IDF Hints!": "💡 ¡Nuevas Pistas ESP-IDF!", + "ESP-IDF: Hints available. Click to view.": "ESP-IDF: Pistas disponibles. Haga clic para ver.", + "Error Hints": "Pistas de Error", + "Clear All Error Hints": "Borrar Todas las Pistas de Error", + "Clear Build Error Hints": "Borrar Pistas de Error de Compilación", + "Clear OpenOCD Error Hints": "Borrar Pistas de Error de OpenOCD", + "Open Reference": "Abrir Referencia" } diff --git a/l10n/bundle.l10n.pt.json b/l10n/bundle.l10n.pt.json index 232c53764..b5c9c4651 100644 --- a/l10n/bundle.l10n.pt.json +++ b/l10n/bundle.l10n.pt.json @@ -244,5 +244,14 @@ "Would you like to reload the window to activate the clangd extension?": "Gostaria de reabrir a janela para ativar a extensão clangd?", "Reload": "Reabrir", "Later": "Mais tarde", - "Failed to install clangd extension. You can install it manually from the Extensions marketplace.": "Falha ao instalar a extensão clangd. Você pode instalá-la manualmente no marketplace de extensões." + "Failed to install clangd extension. You can install it manually from the Extensions marketplace.": "Falha ao instalar a extensão clangd. Você pode instalá-la manualmente no marketplace de extensões.", + "🔗 Reference Documentation": "🔗Documentação de Referência", + "Open {0}": "Abrir {0}", + "💡 New ESP-IDF Hints!": "💡 Novas Dicas ESP-IDF!", + "ESP-IDF: Hints available. Click to view.": "ESP-IDF: Dicas disponíveis. Clique para visualizar.", + "Error Hints": "Dicas de Erro", + "Clear All Error Hints": "Limpar Todas as Dicas de Erro", + "Clear Build Error Hints": "Limpar Dicas de Erro de Compilação", + "Clear OpenOCD Error Hints": "Limpar Dicas de Erro do OpenOCD", + "Open Reference": "Abrir Referência" } diff --git a/l10n/bundle.l10n.ru.json b/l10n/bundle.l10n.ru.json index 994a81d7f..e8d70dd12 100644 --- a/l10n/bundle.l10n.ru.json +++ b/l10n/bundle.l10n.ru.json @@ -244,5 +244,14 @@ "Would you like to reload the window to activate the clangd extension?": "Хотите перезагрузить окно для активации расширения clangd?", "Reload": "Перезагрузить", "Later": "Позже", - "Failed to install clangd extension. You can install it manually from the Extensions marketplace.": "Не удалось установить расширение clangd. Вы можете установить его вручную из маркетплейса расширений." + "Failed to install clangd extension. You can install it manually from the Extensions marketplace.": "Не удалось установить расширение clangd. Вы можете установить его вручную из маркетплейса расширений.", + "🔗 Reference Documentation": "🔗 Справочная Документация", + "Open {0}": "Открыть {0}", + "💡 New ESP-IDF Hints!": "💡 Новые подсказки ESP-IDF!", + "ESP-IDF: Hints available. Click to view.": "ESP-IDF: Подсказки доступны. Нажмите для просмотра.", + "Error Hints": "Подсказки по ошибкам", + "Clear All Error Hints": "Очистить все подсказки по ошибкам", + "Clear Build Error Hints": "Очистить подсказки по ошибкам сборки", + "Clear OpenOCD Error Hints": "Очистить подсказки по ошибкам OpenOCD", + "Open Reference": "Открыть ссылку" } diff --git a/l10n/bundle.l10n.zh-CN.json b/l10n/bundle.l10n.zh-CN.json index 4c56d7649..89c2cf843 100644 --- a/l10n/bundle.l10n.zh-CN.json +++ b/l10n/bundle.l10n.zh-CN.json @@ -244,5 +244,14 @@ "Would you like to reload the window to activate the clangd extension?": "您是否要重新加载窗口以激活 clangd 扩展?", "Reload": "重新加载", "Later": "稍后", - "Failed to install clangd extension. You can install it manually from the Extensions marketplace.": "安装 clangd 扩展失败。您可以从扩展市场手动安装它。" + "Failed to install clangd extension. You can install it manually from the Extensions marketplace.": "安装 clangd 扩展失败。您可以从扩展市场手动安装它。", + "🔗 Reference Documentation": "🔗 参考文档", + "Open {0}": "打开 {0}", + "💡 New ESP-IDF Hints!": "💡 新的 ESP-IDF 提示!", + "ESP-IDF: Hints available. Click to view.": "ESP-IDF:提示可用。点击查看。", + "Error Hints": "错误提示", + "Clear All Error Hints": "清除所有错误提示", + "Clear Build Error Hints": "清除构建错误提示", + "Clear OpenOCD Error Hints": "清除 OpenOCD 错误提示", + "Open Reference": "打开参考" } diff --git a/package.json b/package.json index eb6b454dd..ef5ef569b 100644 --- a/package.json +++ b/package.json @@ -273,7 +273,8 @@ "column": 3, "severity": 4, "message": 5 - } + }, + "source": "esp-idf" }, { "name": "espIdfLd", @@ -288,7 +289,8 @@ "file": 1, "line": 2, "message": 3 - } + }, + "source": "esp-idf" } ], "viewsContainers": { @@ -310,9 +312,9 @@ "views": { "espIdfHints": [ { - "id": "errorHints", - "name": "Error Hints", - "title": "Error Hints ($errorHints.count$)" + "id": "espIdf.errorHints", + "name": "%view.idf.errorHints%", + "title": "%view.idf.errorHints% ($espIdf.errorHints.count$)" } ], "debug": [ @@ -398,10 +400,15 @@ } ], "view/title": [ + { + "command": "espIdf.errorHints.clearAll", + "when": "view == espIdf.errorHints", + "group": "navigation" + }, { "command": "espIdf.searchError", "group": "navigation", - "when": "view == errorHints" + "when": "view == espIdf.errorHints" }, { "command": "espIdf.partition.table.refresh", @@ -449,6 +456,14 @@ } ], "view/item/context": [ + { + "command": "espIdf.errorHints.clearBuildErrors", + "when": "view == espIdf.errorHints && viewItem == buildError" + }, + { + "command": "espIdf.errorHints.clearOpenOCDErrors", + "when": "view == espIdf.errorHints && viewItem == openocdError" + }, { "command": "esp.rainmaker.backend.logout", "when": "view == espRainmaker && viewItem == account", @@ -1277,6 +1292,24 @@ } ], "commands": [ + { + "command": "espIdf.errorHints.clearAll", + "title": "%command.errorHints.clearAll.title%", + "icon": "$(clear-all)", + "category": "ESP-IDF" + }, + { + "command": "espIdf.errorHints.clearBuildErrors", + "title": "%command.errorHints.clearBuild.title%", + "icon": "$(trash)", + "category": "ESP-IDF" + }, + { + "command": "espIdf.errorHints.clearOpenOCDErrors", + "title": "%command.errorHints.clearOpenOCD.title%", + "icon": "$(trash)", + "category": "ESP-IDF" + }, { "command": "espIdf.removeEspIdfSettings", "title": "%espIdf.removeEspIdfSettings.title%", @@ -1284,11 +1317,13 @@ }, { "command": "espIdf.openWalkthrough", - "title": "ESP-IDF: Open Get Started Walkthrough" + "title": "ESP-IDF: Open Get Started Walkthrough", + "category": "ESP-IDF" }, { "command": "espIdf.searchError", - "title": "%espIdf.searchError.title%" + "title": "%espIdf.searchError.title%", + "category": "ESP-IDF" }, { "command": "espIdf.addArduinoAsComponentToCurFolder", diff --git a/package.nls.es.json b/package.nls.es.json index dcdb3283c..821cba5f3 100644 --- a/package.nls.es.json +++ b/package.nls.es.json @@ -55,7 +55,7 @@ "espIdf.getEspMdf.title": "Instalar ESP-MDF", "espIdf.getEspRainmaker.title": "Instalar ESP-Rainmaker", "espIdf.heaptrace.title": "Rastreo de montón", - "espIdf.idfReconfigureTask.title": "ESP-IDF: Ejecute la tarea de reconfiguración de idf.py", + "espIdf.idfReconfigureTask.title": "ESP-IDF: Ejecute la tarea de reconfiguración de idf.py", "espIdf.importProject.title": "Importar proyecto ESP-IDF", "espIdf.installEspMatterPyReqs.title": "Instalar paquetes Python de ESP-Matter", "espIdf.installPyReqs.title": "Instalar paquetes Python de la extensión ESP-IDF", @@ -84,7 +84,7 @@ "espIdf.selectNotificationMode.title": "Seleccionar modo de notificación y salida", "espIdf.selectOpenOcdConfigFiles.title": "Seleccionar archivos de configuración de placa OpenOCD", "espIdf.selectPort.title": "Seleccionar puerto a usar (COM, tty, usbserial)", - "espIdf.setClangSettings.title": "Configurar el proyecto para usar ESP-Clang", + "espIdf.setClangSettings.title": "Configurar el proyecto para usar ESP-Clang", "espIdf.setGcovConfig.title": "Configurar SDKConfig del proyecto para cobertura", "espIdf.setMatterDevicePath.title": "Establecer Ruta del Dispositivo ESP-MATTER (ESP_MATTER_DEVICE_PATH)", "espIdf.setTarget.title": "Establecer Objetivo del Dispositivo Espressif", @@ -98,7 +98,7 @@ "espIdf.welcome.title": "Bienvenido", "espIdf.viewAsHex.title": "Ver como Hexadecimal", "espIdf.hexView.copyValue.title": "Copiar valor al portapapeles", - "espIdf.hexView.deleteElement.title": "Eliminar valor hexadecimal de la lista", + "espIdf.hexView.deleteElement.title": "Eliminar valor hexadecimal de la lista", "esp_idf.appOffset.description": "Anular la dirección de inicio del programa de compilación (ESP32_APP_FLASH_OFF)", "esp_idf.debuggers.text.description": "El comando a ejecutar", "esp_idf.gdbinitFile.description": "Ruta del archivo gdbinit para el Adaptador de Depuración ESP-IDF", @@ -138,7 +138,7 @@ "param.flashBaudRate": "Tasa de baudios de flasheo ESP-IDF", "param.flashPartitionToUse": "Especifica la partición a grabar durante el proceso de flasheo.", "param.gitPath.title": "Ruta del ejecutable de Git", - "param.jtagFlashCommandExtraArgs.title": "Openocd JTAG Flash Argumentos adicionales", + "param.jtagFlashCommandExtraArgs.title": "Openocd JTAG Flash Argumentos adicionales", "param.launchMonitorOnDebugSession.title": "Iniciar Monitor IDF junto con la sesión de Adaptador de Depuración ESP-IDF", "param.monitorBaudRate": "Tasa de baudios de Monitor IDF ESP-IDF", "param.monitorCustomTimestampFormat": "Formato de marca de tiempo personalizado en Monitor IDF", @@ -178,11 +178,11 @@ "param.uncoveredLightTheme": "Color de fondo para líneas no cubiertas en tema claro para Cobertura de ESP-IDF.", "param.useIDFKConfigStyle": "Habilitar/Deshabilitar validación de estilo ESP-IDF para archivos Kconfig", "param.hintsViewer.title": "Ruta al archivo de sugerencias.", - "param.enableSerialPortChipIdRequest.title": "Habilite la detección de la identificación del chip y muéstrela en la lista de selección del puerto serie", - "param.useSerialPortVendorProductFilter.title": "Utilice el USB productID y el USB vendorID para filtrar dispositivos Espressif conocidos", + "param.enableSerialPortChipIdRequest.title": "Habilite la detección de la identificación del chip y muéstrela en la lista de selección del puerto serie", + "param.useSerialPortVendorProductFilter.title": "Utilice el USB productID y el USB vendorID para filtrar dispositivos Espressif conocidos", "param.usbSerialPortFilters.title": "Lista de USB productID y de USB vendorID para filtrar", - "param.usbSerialPortFilters.vendorId": "Número hexadecimal del USB vendorID como texto.", - "param.usbSerialPortFilters.productId": "Número hexadecimal del USB productID como texto.", + "param.usbSerialPortFilters.vendorId": "Número hexadecimal del USB vendorID como texto.", + "param.usbSerialPortFilters.productId": "Número hexadecimal del USB productID como texto.", "param.hasWalkthroughBeenShown": "Indica si se ha mostrado el recorrido de bienvenida", "param.unitTestFilePattern.title": "Patrón glob para descubrir archivos de prueba unitaria", "param.pyTestEmbeddedServices.title": "Lista de servicios integrados para la ejecución de pytest", @@ -204,5 +204,9 @@ "viewContainer.title": "ESP-IDF: Explorador", "viewWelcome.idfPartitionExplorer": "Muestra la lista de particiones de su dispositivo con la opción de flashear binarios (.bin) en la partición seleccionada.\n\nSeleccione el puerto serial de su dispositivo y haga clic en Actualizar tabla de particiones.", "viewWelcome.idfSearchResults": "En cualquier editor de archivos abierto, seleccione un texto y haga clic con el botón derecho y seleccione ESP-IDF: Buscar en la documentación para obtener resultados coincidentes aquí.\n\nLos resultados se basan en su idioma actual de VS Code, versión de idf.espIdfPath (si no, la última)", - "viewWelcome.peripheralView": "Muestra los registros periféricos del archivo SVD definido en la configuración idf.svdFilePath durante la sesión de depuración activa" + "viewWelcome.peripheralView": "Muestra los registros periféricos del archivo SVD definido en la configuración idf.svdFilePath durante la sesión de depuración activa", + "view.idf.errorHints": "Pistas de Error", + "command.errorHints.clearAll.title": "Borrar Todas las Pistas de Error", + "command.errorHints.clearBuild.title": "Borrar Pistas de Error de Compilación", + "command.errorHints.clearOpenOCD.title": "Borrar Pistas de Error de OpenOCD" } \ No newline at end of file diff --git a/package.nls.json b/package.nls.json index f6b3279d3..3f8fb7842 100644 --- a/package.nls.json +++ b/package.nls.json @@ -178,6 +178,7 @@ "param.uncoveredLightTheme": "Background color for uncovered lines in light theme for ESP-IDF coverage", "param.useIDFKConfigStyle": "Enable/Disable ESP-IDF style validation for Kconfig files", "param.hintsViewer.title": "Path to the hints file", + "param.enableSerialPortChipIdRequest.title": "Enable detecting the chip ID and show on serial port selection list", "param.useSerialPortVendorProductFilter.title": "Use USB Product ID and Vendor ID to filter known Espressif devices", "param.usbSerialPortFilters.title": "USB serial port Product ID and Vendor ID list to filter", @@ -204,5 +205,9 @@ "viewContainer.title": "ESP-IDF: Explorer", "viewWelcome.idfPartitionExplorer": "Show the partition list from your device with the option to flash binaries (.bin) to the selected partition.\n\nSelect your device serial port and click Refresh Partition Table.", "viewWelcome.idfSearchResults": "On any opened file editor, select some text, right click and select ESP-IDF: Search in Documentation to get matching results here.\n\nResults are based on your current VS Code language and idf.espIdfPath version (latest otherwise).", - "viewWelcome.peripheralView": "Show peripheral registers from SVD files defined in ESP-IDF SVD file path (idf.svdFilePath) configuration setting during active debug session." + "viewWelcome.peripheralView": "Show peripheral registers from SVD files defined in ESP-IDF SVD file path (idf.svdFilePath) configuration setting during active debug session.", + "view.idf.errorHints": "Error Hints", + "command.errorHints.clearAll.title": "Clear All Error Hints", + "command.errorHints.clearBuild.title": "Clear Build Error Hints", + "command.errorHints.clearOpenOCD.title": "Clear OpenOCD Error Hints" } diff --git a/package.nls.pt.json b/package.nls.pt.json index edc70348c..3a6c32659 100644 --- a/package.nls.pt.json +++ b/package.nls.pt.json @@ -55,7 +55,7 @@ "espIdf.getEspMdf.title": "Instale ESP-MDF", "espIdf.getEspRainmaker.title": "Instale ESP-Rainmaker", "espIdf.heaptrace.title": "Rastreamento de pilha", - "espIdf.idfReconfigureTask.title": "ESP-IDF: Execute a tarefa de reconfiguração idf.py", + "espIdf.idfReconfigureTask.title": "ESP-IDF: Execute a tarefa de reconfiguração idf.py", "espIdf.importProject.title": "Importar projeto ESP-IDF", "espIdf.installEspMatterPyReqs.title": "Instale pacotes ESP-Matter Python", "espIdf.installPyReqs.title": "Instale pacotes Python de extensão ESP-IDF", @@ -84,7 +84,7 @@ "espIdf.selectNotificationMode.title": "Selecione o modo de saída e notificação", "espIdf.selectOpenOcdConfigFiles.title": "Selecione a configuração da placa OpenOCD", "espIdf.selectPort.title": "Selecione a porta a ser usada (COM, tty, usbserial)", - "espIdf.setClangSettings.title": "Configurar o projeto para ESP-Clang", + "espIdf.setClangSettings.title": "Configurar o projeto para ESP-Clang", "espIdf.setGcovConfig.title": "Configurar o projeto SDKConfig para cobertura", "espIdf.setMatterDevicePath.title": "Definir caminho do dispositivo ESP-MATTER (ESP_MATTER_DEVICE_PATH)", "espIdf.setTarget.title": "Definir destino do dispositivo Espressif", @@ -98,7 +98,7 @@ "espIdf.welcome.title": "Bem-vindo", "espIdf.viewAsHex.title": "Ver como Hexadecimal", "espIdf.hexView.copyValue.title": "Copiar valor para a área de transferência", - "espIdf.hexView.deleteElement.title": "Excluir valor hexadecimal da lista", + "espIdf.hexView.deleteElement.title": "Excluir valor hexadecimal da lista", "esp_idf.appOffset.description": "Substituir o deslocamento do endereço inicial do programa de construção (ESP32_APP_FLASH_OFF)", "esp_idf.debuggers.text.description": "O comando para executar", "esp_idf.gdbinitFile.description": "caminho do arquivo gdbinit para adaptador de depuração ESP-IDF", @@ -138,7 +138,7 @@ "param.flashBaudRate": "Taxa de transmissão de flash ESP-IDF", "param.flashPartitionToUse": "Especifica a partição a ser gravada.", "param.gitPath.title": "Caminho executável do Git", - "param.jtagFlashCommandExtraArgs.title": "Argumentos extras do Openocd JTAG Flash", + "param.jtagFlashCommandExtraArgs.title": "Argumentos extras do Openocd JTAG Flash", "param.launchMonitorOnDebugSession.title": "Inicie o IDF Monitor junto com a sessão do adaptador de depuração ESP-IDF", "param.monitorBaudRate": "Taxa de transmissão do monitor ESP-IDF", "param.monitorCustomTimestampFormat": "Formato de carimbo de data/hora personalizado no IDF Monitor", @@ -178,10 +178,10 @@ "param.uncoveredLightTheme": "Cor de fundo para linhas descobertas no tema Light para cobertura ESP-IDF.", "param.useIDFKConfigStyle": "Habilitar/desabilitar validação de estilo ESP-IDF para arquivos Kconfig", "param.enableSerialPortChipIdRequest.title": "Habilite a detecção do ID do chip e mostre na lista de seleção de porta serial", - "param.useSerialPortVendorProductFilter.title": "Use USB productID e vendorID para filtrar dispositivos Espressif conhecidos", + "param.useSerialPortVendorProductFilter.title": "Use USB productID e vendorID para filtrar dispositivos Espressif conhecidos", "param.usbSerialPortFilters.title": "Lista de productID e vendorID da porta serial USB para filtrar", - "param.usbSerialPortFilters.vendorId": "Número hexadecimal do USB vendorID como string.", - "param.usbSerialPortFilters.productId": "Número hexadecimal de USB productID como string.", + "param.usbSerialPortFilters.vendorId": "Número hexadecimal do USB vendorID como string.", + "param.usbSerialPortFilters.productId": "Número hexadecimal de USB productID como string.", "param.hasWalkthroughBeenShown": "Mostrar o guia de introdução na inicialização da extensão", "param.unitTestFilePattern.title": "Padrão glob para descobrir arquivos de teste unitário", "param.pyTestEmbeddedServices.title": "Lista de serviços incorporados para execução do pytest", @@ -203,5 +203,9 @@ "viewContainer.title": "ESP-IDF: Explorador", "viewWelcome.idfPartitionExplorer": "Mostre a lista de partições do seu dispositivo com a opção de atualizar binários (.bin) para a partição selecionada.\n\n", "viewWelcome.idfSearchResults": "Em qualquer editor de arquivo aberto, selecione algum texto e clique com o botão direito e selecione ESP-IDF: Pesquise na documentação para obter resultados correspondentes aqui.\n\n", - "viewWelcome.peripheralView": "Mostrar registros de periféricos do arquivo SVD definido na configuração ESP-IDF Svd File Path (idf.svdFilePath) durante a sessão de depuração ativa" + "viewWelcome.peripheralView": "Mostrar registros de periféricos do arquivo SVD definido na configuração ESP-IDF Svd File Path (idf.svdFilePath) durante a sessão de depuração ativa", + "view.idf.errorHints": "Dicas de Erro", + "command.errorHints.clearAll.title": "Limpar Todas as Dicas de Erro", + "command.errorHints.clearBuild.title": "Limpar Dicas de Erro de Compilação", + "command.errorHints.clearOpenOCD.title": "Limpar Dicas de Erro do OpenOCD" } \ No newline at end of file diff --git a/package.nls.ru.json b/package.nls.ru.json index 123558920..cfc82a564 100644 --- a/package.nls.ru.json +++ b/package.nls.ru.json @@ -55,7 +55,7 @@ "espIdf.getEspMdf.title": "Установить ESP-MDF", "espIdf.getEspRainmaker.title": "Установить ESP-Rainmaker", "espIdf.heaptrace.title": "Трассировка кучи", - "espIdf.idfReconfigureTask.title": "ESP-IDF: Запустить задачу перенастройки idf.py", + "espIdf.idfReconfigureTask.title": "ESP-IDF: Запустить задачу перенастройки idf.py", "espIdf.importProject.title": "Импортировать проект ESP-IDF", "espIdf.installEspMatterPyReqs.title": "Установить пакеты Python ESP-Matter", "espIdf.installPyReqs.title": "Установить пакеты Python расширения ESP-IDF", @@ -84,7 +84,7 @@ "espIdf.selectNotificationMode.title": "Выбор режима вывода и уведомлений", "espIdf.selectOpenOcdConfigFiles.title": "Выбор конфигурации платы OpenOCD", "espIdf.selectPort.title": "Выбор порта (COM, tty, usbserial)", - "espIdf.setClangSettings.title": "Настроить проект для ESP-Clang", + "espIdf.setClangSettings.title": "Настроить проект для ESP-Clang", "espIdf.setGcovConfig.title": "Конфигурация Project SDKConfig для покрытия", "espIdf.setMatterDevicePath.title": "Установка пути к устройству ESP-MATTER (ESP_MATTER_DEVICE_PATH)", "espIdf.setTarget.title": "Установка целевого устройства Espressif", @@ -96,7 +96,7 @@ "espIdf.unitTest.installPyTest.title": "Unit Test: Установка требований ESP-IDF PyTest.", "espIdf.viewAsHex.title": "Просмотреть как шестнадцатеричное", "espIdf.hexView.copyValue.title": "Скопировать значение в буфер обмена", - "espIdf.hexView.deleteElement.title": "Удалить шестнадцатеричное значение из списка", + "espIdf.hexView.deleteElement.title": "Удалить шестнадцатеричное значение из списка", "espIdf.webview.nvsPartitionEditor.title": "Открыть редактор разделов NVS", "espIdf.welcome.title": "Добро пожаловать", "esp_idf.appOffset.description": "Переопределить смещение начального адреса программы сборки (ESP32_APP_FLASH_OFF)", @@ -137,7 +137,7 @@ "param.exportVars": "Переменные, добавляемые к переменным системного окружения", "param.flashBaudRate": "Скорость прошивки ESP-IDF", "param.flashPartitionToUse": "Указывает раздел для прошивки.", - "param.jtagFlashCommandExtraArgs.title": "Openocd jtag flash дополнительные аргументы", + "param.jtagFlashCommandExtraArgs.title": "Openocd jtag flash дополнительные аргументы", "param.gitPath.title": "Путь к исполняемому файлу Git", "param.launchMonitorOnDebugSession.title": "Запуск монитора IDF вместе с сессией Адаптера Отладки ESP-IDF", "param.monitorBaudRate": "Скорость передачи данных монитора ESP-IDF", @@ -179,10 +179,10 @@ "param.useIDFKConfigStyle": "Включить/отключить проверку стиля ESP-IDF для файлов Kconfig", "param.hintsViewer.title": "Путь к файлу подсказок", "param.enableSerialPortChipIdRequest.title": "Включить определение ID чипа и отобразить его в списке выбора последовательного порта", - "param.useSerialPortVendorProductFilter.title": "Использовать Product ID и Vendor ID USB для фильтрации известных устройств Espressif", + "param.useSerialPortVendorProductFilter.title": "Использовать Product ID и Vendor ID USB для фильтрации известных устройств Espressif", "param.usbSerialPortFilters.title": "Список Product ID и Vendor ID для последовательного порта USB для фильтрации", - "param.usbSerialPortFilters.vendorId": "Шестнадцатеричное число USB Vendor ID в виде строки, например, 0x0403", - "param.usbSerialPortFilters.productId": "Шестнадцатеричное число USB Product ID в виде строки, например, 0x6010", + "param.usbSerialPortFilters.vendorId": "Шестнадцатеричное число USB Vendor ID в виде строки, например, 0x0403", + "param.usbSerialPortFilters.productId": "Шестнадцатеричное число USB Product ID в виде строки, например, 0x6010", "param.hasWalkthroughBeenShown": "Показывать руководство по настройке ESP-IDF при первом запуске", "param.unitTestFilePattern.title": "Шаблон glob для обнаружения файлов модульных тестов", "param.pyTestEmbeddedServices.title": "Список встроенных сервисов для выполнения pytest", @@ -204,5 +204,9 @@ "viewContainer.title": "ESP-IDF: Проводник", "viewWelcome.idfPartitionExplorer": "Показать список разделов устройства с возможностью перепрошивки бинарников (.bin) в выбранный раздел.\n\nВыберите последовательный порт устройства и кликните 'Обновить таблицу разделов'.", "viewWelcome.idfSearchResults": "В любом открытом файле редактора выделите текст, кликните правой кнопкой мыши и выберите 'ESP-IDF: Поиск в документации', чтобы получить соответствующие результаты.\n\nРезультаты основаны на вашем текущем языке VS Code и версии idf.espIdfPath (в противном случае последней).", - "viewWelcome.peripheralView": "Показывать периферийные регистры из SVD-файлов, определенных в параметре конфигурации ESP-IDF путь к файлу SVD (idf.svdFilePath) во время активного сеанса отладки." + "viewWelcome.peripheralView": "Показывать периферийные регистры из SVD-файлов, определенных в параметре конфигурации ESP-IDF путь к файлу SVD (idf.svdFilePath) во время активного сеанса отладки.", + "view.idf.errorHints": "Подсказки по ошибкам", + "command.errorHints.clearAll.title": "Очистить все подсказки по ошибкам", + "command.errorHints.clearBuild.title": "Очистить подсказки по ошибкам сборки", + "command.errorHints.clearOpenOCD.title": "Очистить подсказки по ошибкам OpenOCD" } diff --git a/package.nls.zh-CN.json b/package.nls.zh-CN.json index 84d69d7d8..fa6e1a0be 100644 --- a/package.nls.zh-CN.json +++ b/package.nls.zh-CN.json @@ -204,5 +204,9 @@ "viewContainer.title": "ESP-IDF:资源管理器", "viewWelcome.idfPartitionExplorer": "显示设备的分区列表,选择烧录二进制文件 (.bin) 的分区。\n\n选择设备串口,单击\"刷新分区表\"。", "viewWelcome.idfSearchResults": "在任一打开的文件编辑器中选定文本,右键单击并选择\"ESP-IDF:在文档中搜索\",搜索匹配的结果。\n\n搜索结果基于当前的 VS Code 语言以及 idf.espIdfPath 版本 (若未指定版本,则默认使用最新版 ESP-IDF)。", - "viewWelcome.peripheralView": "在活动调试会话中,显示 ESP-IDF SVD 文件 (路径为 idf.svdFilePath) 中包含的外设寄存器信息。" + "viewWelcome.peripheralView": "在活动调试会话中,显示 ESP-IDF SVD 文件 (路径为 idf.svdFilePath) 中包含的外设寄存器信息。", + "view.idf.errorHints": "错误提示", + "command.errorHints.clearAll.title": "清除所有错误提示", + "command.errorHints.clearBuild.title": "清除构建错误提示", + "command.errorHints.clearOpenOCD.title": "清除 OpenOCD 错误提示" } diff --git a/src/espIdf/hints/index.ts b/src/espIdf/hints/index.ts index dee8c2028..cd1dd5a83 100644 --- a/src/espIdf/hints/index.ts +++ b/src/espIdf/hints/index.ts @@ -1,66 +1,191 @@ -import * as os from "os"; +// Copyright 2025 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import * as yaml from "js-yaml"; import { readFile, pathExists } from "fs-extra"; import * as idfConf from "../../idfConfiguration"; import { Logger } from "../../logger/logger"; import * as utils from "../../utils"; +import { getOpenOcdHintsYmlPath } from "./utils"; import * as vscode from "vscode"; +import * as path from "path"; +import { OpenOCDManager } from "../openOcd/openOcdManager"; +/** + * Class representing a pair of regular expression and its corresponding hint. + * Used to match error messages and provide helpful guidance. + */ class ReHintPair { - re: string; - hint: string; - match_to_output: boolean; - - constructor(re: string, hint: string, match_to_output: boolean = false) { + re: string; // Regular expression to match error messages + hint: string; // Hint text to show when the regex matches + match_to_output: boolean; // Whether to insert matched groups into the hint + ref?: string; // Link to documentation for openOCD hints + + constructor( + re: string, + hint: string, + match_to_output: boolean = false, + ref?: string + ) { this.re = re; this.hint = hint; this.match_to_output = match_to_output; + this.ref = ref; } } -class ErrorHint { - public type: "error" | "hint"; - public children: ErrorHint[] = []; - - constructor(public label: string, type: "error" | "hint") { - this.type = type; - } +export class ErrorHintTreeItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly type: "error" | "hint" | "reference", + public readonly children: ErrorHintTreeItem[] = [], + public readonly reference?: string + ) { + super( + label, + children.length > 0 + ? vscode.TreeItemCollapsibleState.Expanded + : vscode.TreeItemCollapsibleState.None + ); - addChild(child: ErrorHint) { - this.children.push(child); + // Set different appearances based on the type + if (type === "error") { + if (label.startsWith("No hints found")) { + this.label = `⚠️ ${label}`; + } else { + this.label = `🔍 ${label}`; + } + } else if (type === "hint") { + this.label = `💡 ${label}`; + this.tooltip = label; + } else if (type === "reference") { + this.label = vscode.l10n.t(`🔗 Reference Documentation`); + this.tooltip = vscode.l10n.t(`Open {0}`, label); + this.command = { + command: "vscode.open", + title: vscode.l10n.t("Open Reference"), + arguments: [vscode.Uri.parse(label)], + }; + this.iconPath = new vscode.ThemeIcon("link-external"); + } } } -export class ErrorHintProvider implements vscode.TreeDataProvider { - constructor(private context: vscode.ExtensionContext) {} +export class ErrorHintProvider + implements vscode.TreeDataProvider { private _onDidChangeTreeData: vscode.EventEmitter< - ErrorHint | undefined | null | void - > = new vscode.EventEmitter(); + ErrorHintTreeItem | undefined | null | void + > = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event< - ErrorHint | undefined | null | void + ErrorHintTreeItem | undefined | null | void > = this._onDidChangeTreeData.event; - private data: ErrorHint[] = []; + private buildErrorData: ErrorHintTreeItem[] = []; + private openocdErrorData: ErrorHintTreeItem[] = []; + + constructor(private context: vscode.ExtensionContext) {} + + // Get tree item for display + getTreeItem(element: ErrorHintTreeItem): vscode.TreeItem { + return element; + } + + // Get children of a tree item + getChildren(element?: ErrorHintTreeItem): Thenable { + if (element) { + return Promise.resolve(element.children); + } else { + return Promise.resolve([ + ...this.buildErrorData, + ...this.openocdErrorData, + ]); + } + } + + // Clear error hints + clearErrorHints(clearOpenOCD: boolean = false): void { + this.buildErrorData = []; + if (clearOpenOCD) { + this.openocdErrorData = []; + } + this._onDidChangeTreeData.fire(); + } - public getHintForError(errorMsg: string): string | undefined { - for (const errorHint of this.data) { - if (errorHint.label.includes(errorMsg) && errorHint.children.length > 0) { - return errorHint.children[0].label; + // Clear only OpenOCD errors + clearOpenOCDErrorsOnly(): void { + this.openocdErrorData = []; + this._onDidChangeTreeData.fire(); + } + + // Show OpenOCD error hint + public async showOpenOCDErrorHint( + errorMsg: string, + hintMsg: string, + reference?: string + ): Promise { + try { + this.openocdErrorData = []; + + // Create hint nodes + const hintItems: ErrorHintTreeItem[] = []; + + // Add reference as a child if available + if (reference) { + hintItems.push(new ErrorHintTreeItem(reference, "reference")); } + + // Create hint with children + const hint = new ErrorHintTreeItem(hintMsg, "hint", hintItems); + + // Create error with hint as child + const error = new ErrorHintTreeItem(errorMsg, "error", [hint]); + + // Add to OpenOCD data array + this.openocdErrorData.push(error); + + // Notify that the tree data has changed + this._onDidChangeTreeData.fire(); + + return true; + } catch (error) { + Logger.errorNotify( + `Error showing OpenOCD error hint: ${error.message}`, + error, + "ErrorHintProvider showOpenOCDErrorHint" + ); + return false; } - return undefined; } - async searchError(errorMsg: string, workspace): Promise { + // Method to search for error hints + public async searchError( + errorMsg: string, + workspace: vscode.Uri + ): Promise { + this.buildErrorData = []; + const espIdfPath = idfConf.readParameter( "idf.espIdfPath", workspace ) as string; + const version = await utils.getEspIdfFromCMake(espIdfPath); if (utils.compareVersion(version.trim(), "5.0") === -1) { - this.data.push( - new ErrorHint( + this.buildErrorData.push( + new ErrorHintTreeItem( `Error hints feature is not supported in ESP-IDF version ${version}`, "error" ) @@ -69,73 +194,85 @@ export class ErrorHintProvider implements vscode.TreeDataProvider { return false; } - const hintsPath = getHintsYmlPath(espIdfPath); - + // Get paths for both hint files + const idfHintsPath = getIdfHintsYmlPath(espIdfPath); + const toolsPath = idfConf.readParameter( + "idf.toolsPath", + workspace + ) as string; + let openOcdHintsPath: string | null = null; try { - if (!(await pathExists(hintsPath))) { - Logger.infoNotify(`${hintsPath} does not exist.`); - return false; + const openOCDManager = OpenOCDManager.init(); + const openOCDVersion = await openOCDManager.version(); + if (toolsPath && openOCDVersion) { + openOcdHintsPath = await getOpenOcdHintsYmlPath( + toolsPath, + openOCDVersion + ); + } else { + Logger.info( + "Could not get toolsPath or OpenOCD version, skipping OpenOCD hints lookup.", + "ErrorHintProvider searchError" + ); } + } catch (error) { + Logger.error( + `Failed to initialize OpenOCDManager or get version: ${error.message}`, + error, + "ErrorHintProvider searchError" + ); + } - const fileContents = await readFile(hintsPath, "utf-8"); - const hintsData = yaml.load(fileContents); - - const reHintsPairArray: ReHintPair[] = this.loadHints(hintsData); - - this.data = []; + try { let meaningfulHintFound = false; - for (const hintPair of reHintsPairArray) { - const match = new RegExp(hintPair.re, "i").exec(errorMsg); - const regexParts = Array.from(hintPair.re.matchAll(/\(([^)]+)\)/g)) - .map((m) => m[1].split("|")) - .flat() - .map((part) => part.toLowerCase()); - if ( - match || - regexParts.some((part) => errorMsg.toLowerCase().includes(part)) - ) { - let finalHint = hintPair.hint; - if ( - match && - hintPair.match_to_output && - hintPair.hint.includes("{}") - ) { - finalHint = hintPair.hint.replace("{}", match[0]); - } else if (!match && hintPair.hint.includes("{}")) { - const matchedSubstring = regexParts.find((part) => - errorMsg.toLowerCase().includes(part.toLowerCase()) - ); - finalHint = hintPair.hint.replace("{}", matchedSubstring || ""); // Handle case where nothing is matched - } - const error = new ErrorHint(errorMsg, "error"); - const hint = new ErrorHint(finalHint, "hint"); - error.addChild(hint); - this.data.push(error); - if (!finalHint.startsWith("No hints found for")) { - meaningfulHintFound = true; - } + // Process ESP-IDF hints + if (await pathExists(idfHintsPath)) { + try { + const fileContents = await readFile(idfHintsPath, "utf-8"); + const hintsData = yaml.load(fileContents) as []; + const reHintsPairArray: ReHintPair[] = this.loadHints(hintsData); + + meaningfulHintFound = + (await this.processHints(errorMsg, reHintsPairArray)) || + meaningfulHintFound; + } catch (error) { + Logger.errorNotify( + `Error processing ESP-IDF hints file (line ${error.mark?.line}): ${error.message}`, + error, + "ErrorHintProvider searchError" + ); } + } else { + Logger.infoNotify(`${idfHintsPath} does not exist.`); } - if (this.data.length === 0) { - for (const hintPair of reHintsPairArray) { - if (hintPair.re.toLowerCase().includes(errorMsg.toLowerCase())) { - const error = new ErrorHint(hintPair.re, "error"); - const hint = new ErrorHint(hintPair.hint, "hint"); - error.addChild(hint); - this.data.push(error); - - if (!hintPair.hint.startsWith("No hints found for")) { - meaningfulHintFound = true; - } - } + // Process OpenOCD hints + if (openOcdHintsPath && (await pathExists(openOcdHintsPath))) { + try { + const fileContents = await readFile(openOcdHintsPath, "utf-8"); + const hintsData = yaml.load(fileContents); + const reHintsPairArray: ReHintPair[] = this.loadOpenOcdHints( + hintsData + ); + + meaningfulHintFound = + (await this.processHints(errorMsg, reHintsPairArray)) || + meaningfulHintFound; + } catch (error) { + Logger.errorNotify( + `Error processing OpenOCD hints file (line ${error.mark?.line}): ${error.message}`, + error, + "ErrorHintProvider searchError" + ); } + } else if (openOcdHintsPath) { + Logger.info(`${openOcdHintsPath} does not exist.`); } - if (!this.data.length) { - this.data.push( - new ErrorHint(`No hints found for ${errorMsg}`, "error") + if (this.buildErrorData.length === 0) { + this.buildErrorData.push( + new ErrorHintTreeItem(`No hints found for ${errorMsg}`, "error") ); } @@ -143,7 +280,7 @@ export class ErrorHintProvider implements vscode.TreeDataProvider { return meaningfulHintFound; } catch (error) { Logger.errorNotify( - `Error processing hints file (line ${error.mark?.line}): ${error.message}`, + `Error processing hints file: ${error.message}`, error, "ErrorHintProvider searchError" ); @@ -151,10 +288,212 @@ export class ErrorHintProvider implements vscode.TreeDataProvider { } } - private loadHints(hintsArray: any): ReHintPair[] { + // Process hints + private async processHints( + errorMsg: string, + reHintsPairArray: ReHintPair[] + ): Promise { + let meaningfulHintFound = false; + let errorFullMessage = ""; + + for (const hintPair of reHintsPairArray) { + const match = new RegExp(hintPair.re, "i").exec(errorMsg); + const regexParts = []; + // Extract meaningful parts from regex by breaking at top-level pipes + // outside of parentheses or use a proper regex parser + const mainPattern = hintPair.re.replace(/^.*?'(.*)'.*$/, "$1"); // Extract pattern between quotes if present + if (mainPattern) { + // Split by top-level pipes, preserving grouped expressions + const parts = mainPattern.split(/\|(?![^(]*\))/); + for (const part of parts) { + // Clean up any remaining parentheses for direct string matching + const cleaned = part.replace(/[()]/g, ""); + if (cleaned.length > 3) { + // Avoid very short fragments + regexParts.push(cleaned.toLowerCase()); + } + } + } + if ( + match || + hintPair.re.toLowerCase().includes(errorMsg) || + regexParts.some((part) => errorMsg.toLowerCase().includes(part)) + ) { + let finalHint = hintPair.hint; + + if (match && hintPair.match_to_output && hintPair.hint.includes("{}")) { + finalHint = hintPair.hint.replace("{}", match[0]); + } else if (!match && hintPair.hint.includes("{}")) { + const matchedSubstring = regexParts.find((part) => + errorMsg.toLowerCase().includes(part.toLowerCase()) + ); + finalHint = hintPair.hint.replace("{}", matchedSubstring || ""); // Handle case where nothing is matched + } + + // Create hint children + const hintChildren: ErrorHintTreeItem[] = []; + + // Add reference as child if available + if (hintPair.ref) { + hintChildren.push(new ErrorHintTreeItem(hintPair.ref, "reference")); + } + + // Create hint with children + const hint = new ErrorHintTreeItem( + finalHint, + "hint", + hintChildren, + hintPair.ref + ); + + // Create error with hint as child + // Display matched error message + errorFullMessage = hintPair.re; + const error = new ErrorHintTreeItem(errorFullMessage, "error", [hint]); + + // Add to build error data + this.buildErrorData.push(error); + + if (!finalHint.startsWith("No hints found for")) { + meaningfulHintFound = true; + } + } + } + + // If no direct matches, try partial matches + if (this.buildErrorData.length === 0) { + for (const hintPair of reHintsPairArray) { + if (hintPair.re.toLowerCase().includes(errorMsg.toLowerCase())) { + // Create hint children + const hintChildren: ErrorHintTreeItem[] = []; + + // Add reference as child if available + if (hintPair.ref) { + hintChildren.push(new ErrorHintTreeItem(hintPair.ref, "reference")); + } + + // Create hint with children + const hint = new ErrorHintTreeItem( + hintPair.hint, + "hint", + hintChildren, + hintPair.ref + ); + + // Create error with hint as child + const error = new ErrorHintTreeItem(hintPair.re, "error", [hint]); + + // Add to build error data + this.buildErrorData.push(error); + + if (!hintPair.hint.startsWith("No hints found for")) { + meaningfulHintFound = true; + } + } + } + } + + return meaningfulHintFound; + } + + // Get hint for an error message + public getHintForError( + errorMsg: string + ): { hint?: string; ref?: string } | undefined { + // Check in build errors + for (const errorHint of this.buildErrorData) { + if ( + errorHint.label.toLowerCase().includes(errorMsg.toLowerCase()) && + errorHint.children.length > 0 + ) { + const hintItem = errorHint.children[0]; + return { + hint: hintItem.label.replace(/^💡 /, ""), + ref: hintItem.reference, + }; + } + } + + // No match found + return undefined; + } + + public hasHints(): boolean { + return this.buildErrorData.length > 0 || this.openocdErrorData.length > 0; + } + + /** + * Loads hints from the parsed YAML array and converts them to ReHintPair objects + * + * @param hintsArray - Array of hint definitions from YAML + * @returns Array of ReHintPair objects ready for matching against error messages + * + * Example input: + * [ + * { + * re: "fatal error: (spiram.h|esp_spiram.h): No such file or directory", + * hint: "{} was removed. Include esp_psram.h instead.", + * match_to_output: true + * }, + * { + * re: "error: implicit declaration of function '{}'", + * hint: "Function '{}' has been removed. Please use {}.", + * variables: [ + * { + * re_variables: ["esp_random"], + * hint_variables: ["esp_random()", "esp_fill_random()"] + * } + * ] + * } + * ] + */ + loadHints(hintsArray: any[]): ReHintPair[] { + let reHintsPairArray: ReHintPair[] = []; + + for (const entry of hintsArray) { + // Case 1: Entry has variables + if (entry.variables && entry.variables.length) { + // Handle variable-based entries + for (const variableSet of entry.variables) { + const reVariables = variableSet.re_variables || []; + const hintVariables = variableSet.hint_variables || []; + + let re = this.formatEntry(reVariables, entry.re); + let hint = this.formatEntry(hintVariables, entry.hint); + + reHintsPairArray.push( + new ReHintPair(re, hint, entry.match_to_output) + ); + } + } else { + // Case 2: Simple entry without variables + let re = String(entry.re); + let hint = String(entry.hint); + + if (!entry.match_to_output) { + // Simple case: no need to insert matched content into hint + re = this.formatEntry([], re); + hint = this.formatEntry([], hint); + reHintsPairArray.push(new ReHintPair(re, hint, false)); + } else { + // Complex case: need to expand alternatives and insert matches + // E.g., "(spiram.h|esp_spiram.h)" should create two separate entries + const reHintPairs = this.expandAlternatives(re, hint); + for (const pair of reHintPairs) { + reHintsPairArray.push(new ReHintPair(pair.re, pair.hint, true)); + } + } + } + } + + return reHintsPairArray; + } + + private loadOpenOcdHints(hintsArray: any): ReHintPair[] { let reHintsPairArray: ReHintPair[] = []; for (const entry of hintsArray) { + // Ignore all properties that are not "re", "hint", "match_to_output", "variables" or "ref" if (entry.variables && entry.variables.length) { for (const variableSet of entry.variables) { const reVariables = variableSet.re_variables; @@ -164,7 +503,7 @@ export class ErrorHintProvider implements vscode.TreeDataProvider { let hint = this.formatEntry(hintVariables, entry.hint); reHintsPairArray.push( - new ReHintPair(re, hint, entry.match_to_output) + new ReHintPair(re, hint, entry.match_to_output, entry.ref) ); } } else { @@ -176,61 +515,118 @@ export class ErrorHintProvider implements vscode.TreeDataProvider { hint = this.formatEntry([], hint); } - reHintsPairArray.push(new ReHintPair(re, hint, entry.match_to_output)); + reHintsPairArray.push( + new ReHintPair(re, hint, entry.match_to_output, entry.ref) + ); } } return reHintsPairArray; } + /** + * Formats an entry string by replacing {} placeholders with values from vars array + * + * @param vars - Array of values to insert into placeholders + * @param entry - Template string with {} placeholders + * @returns Formatted string with placeholders replaced by values + * + * Example: + * formatEntry(["esp_random", "esp_fill_random"], "Function '{}' has been renamed to '{}'.") + * Result: "Function 'esp_random' has been renamed to 'esp_fill_random'." + */ private formatEntry(vars: string[], entry: string): string { let i = 0; - while (entry.includes("{}")) { - entry = entry.replace("{}", "{" + i++ + "}"); + let formattedEntry = entry; + + // Replace {} with indexed placeholders {0}, {1}, etc. + while (formattedEntry.includes("{}")) { + formattedEntry = formattedEntry.replace("{}", "{" + i++ + "}"); } - const result = entry.replace( + + // Replace indexed placeholders with values from vars array + const result = formattedEntry.replace( /\{(\d+)\}/g, (_, idx) => vars[Number(idx)] || "" ); + return result; } - getTreeItem(element: ErrorHint): vscode.TreeItem { - let treeItem = new vscode.TreeItem(element.label); + /** + * Expands regex patterns with alternatives into separate entries + * This is critical for match_to_output: true cases where the match needs + * to be inserted into the hint + * + * @param re - Regular expression string with possible alternatives in parentheses + * @param hint - Hint template with {} placeholders for matches + * @returns Array of {re, hint} pairs with alternatives expanded + * + * Example: + * expandAlternatives( + * "fatal error: (spiram.h|esp_spiram.h): No such file or directory", + * "{} was removed. Include esp_psram.h instead." + * ) + * + * Results in: + * [ + * { + * re: "fatal error: spiram.h: No such file or directory", + * hint: "spiram.h was removed. Include esp_psram.h instead." + * }, + * { + * re: "fatal error: esp_spiram.h: No such file or directory", + * hint: "esp_spiram.h was removed. Include esp_psram.h instead." + * } + * ] + */ + private expandAlternatives( + re: string, + hint: string + ): Array<{ re: string; hint: string }> { + const result: Array<{ re: string; hint: string }> = []; + + // Find all alternatives in parentheses with pipe characters + // Example: in "(spiram.h|esp_spiram.h)" we'll find alternatives "spiram.h" and "esp_spiram.h" + const alternativeMatches = re.match(/\(([^()]*\|[^()]*)\)/g); + + if (!alternativeMatches) { + // No alternatives found, return the original as-is + return [{ re, hint }]; + } - if (element.type === "error") { - if (element.label.startsWith("No hints found")) { - treeItem.label = `⚠️ ${element.label}`; + // Process each alternative group + for (const match of alternativeMatches) { + const alternatives = match.slice(1, -1).split("|"); // Remove parens and split by pipe + + if (result.length === 0) { + // Initial population of result + for (const alt of alternatives) { + const newRe = re.replace(match, alt); + // For each alternative, create a new hint with the alternative inserted + const newHint = hint.replace(/\{\}/, alt); + result.push({ re: newRe, hint: newHint }); + } } else { - treeItem.label = `🔍 ${element.label}`; - treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; // Ensure errors are expanded by default + // For subsequent alternative groups, multiply the existing results + const currentResults = [...result]; + result.length = 0; + + for (const existingEntry of currentResults) { + for (const alt of alternatives) { + const newRe = existingEntry.re.replace(match, alt); + // For each alternative, create a new hint with the alternative inserted + const newHint = existingEntry.hint.replace(/\{\}/, alt); + result.push({ re: newRe, hint: newHint }); + } + } } - } else if (element.type === "hint") { - treeItem.label = `💡 ${element.label}`; } - return treeItem; - } - - getChildren(element?: ErrorHint): Thenable { - if (element) { - return Promise.resolve(element.children); // Return children if there's a parent element - } else { - return Promise.resolve(this.data); - } - } - - clearErrorHints() { - this.data = []; - this._onDidChangeTreeData.fire(); // Notify the view to refresh + return result; } } -function getHintsYmlPath(espIdfPath: string): string { - const separator = os.platform() === "win32" ? "\\" : "/"; - return `${espIdfPath}${separator}tools${separator}idf_py_actions${separator}hints.yml`; -} - export class HintHoverProvider implements vscode.HoverProvider { constructor(private hintProvider: ErrorHintProvider) {} @@ -239,25 +635,55 @@ export class HintHoverProvider implements vscode.HoverProvider { position: vscode.Position, token: vscode.CancellationToken ): vscode.ProviderResult { - const diagnostics = vscode.languages.getDiagnostics(document.uri); + // Get all diagnostics for this document + const diagnostics = vscode.languages + .getDiagnostics(document.uri) + .filter( + (diagnostic) => + diagnostic.source === "esp-idf" && + diagnostic.severity === vscode.DiagnosticSeverity.Error + ); + // No ESP-IDF diagnostics found for this document + if (!diagnostics.length) { + return null; + } + + // Find diagnostics that contain the hover position for (const diagnostic of diagnostics) { - const start = diagnostic.range.start; - const end = diagnostic.range.end; + // Check if position is within the diagnostic range + // We'll be slightly more generous with the range to make it easier to hover + const range = diagnostic.range; + + // Expand the range slightly to make it easier to hover + const lineText = document.lineAt(range.start.line).text; + const expandedRange = new vscode.Range( + new vscode.Position(range.start.line, 0), + new vscode.Position(range.end.line, lineText.length) + ); - // Check if the position is within or immediately adjacent to the diagnostic range - if ( - diagnostic.severity === vscode.DiagnosticSeverity.Error && - position.line === start.line && - position.character >= start.character - 1 && - position.character <= end.character + 1 - ) { - const hint = this.hintProvider.getHintForError(diagnostic.message); - if (hint) { - return new vscode.Hover(`ESP-IDF Hint: ${hint}`); + // Check if position is within the expanded range + if (expandedRange.contains(position)) { + // Get hint object for this error message + const hintInfo = this.hintProvider.getHintForError(diagnostic.message); + + if (hintInfo && hintInfo.hint) { + let hoverMessage = `**ESP-IDF Hint**: ${hintInfo.hint}`; + + // Add reference link if available + if (hintInfo.ref) { + hoverMessage += `\n\n[Reference Documentation](${hintInfo.ref})`; + } + // We found a hint, return it with markdown formatting + return new vscode.Hover(new vscode.MarkdownString(`${hoverMessage}`)); } } } + + // No matching diagnostics found at this position return null; } } +function getIdfHintsYmlPath(espIdfPath: string): string { + return path.join(espIdfPath, "tools", "idf_py_actions", "hints.yml"); +} diff --git a/src/espIdf/hints/openocdhint.ts b/src/espIdf/hints/openocdhint.ts new file mode 100644 index 000000000..5f25901fd --- /dev/null +++ b/src/espIdf/hints/openocdhint.ts @@ -0,0 +1,305 @@ +// Copyright 2025 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as vscode from "vscode"; +import * as yaml from "js-yaml"; +import { readFile, pathExists } from "fs-extra"; +import * as path from "path"; +import * as idfConf from "../../idfConfiguration"; +import { Logger } from "../../logger/logger"; +import { PreCheck } from "../../utils"; +import { getOpenOcdHintsYmlPath } from "./utils"; +import { OpenOCDManager } from "../openOcd/openOcdManager"; +import { ErrorHintProvider } from "./index"; + +interface OpenOCDHint { + source?: string; + re: string; + hint: string; + ref?: string; + match_to_output?: boolean; + variables?: Array<{ + re_variables: string[]; + hint_variables: string[]; + }>; +} + +export class OpenOCDErrorMonitor { + private static instance: OpenOCDErrorMonitor; + private errorHintProvider: ErrorHintProvider; + private hintsData: OpenOCDHint[] = []; + private errorBuffer: string[] = []; + private workspaceRoot: vscode.Uri; + private debounceTimer: NodeJS.Timeout | null = null; + private openOCDLogWatcher: vscode.Disposable | null = null; + private readonly DEBOUNCE_TIME = 300; // ms + + private constructor( + errorHintProvider: ErrorHintProvider, + workspaceRoot: vscode.Uri + ) { + this.errorHintProvider = errorHintProvider; + this.workspaceRoot = workspaceRoot; + } + + public static init( + errorHintProvider: ErrorHintProvider, + workspaceRoot: vscode.Uri + ): OpenOCDErrorMonitor { + if (!OpenOCDErrorMonitor.instance) { + OpenOCDErrorMonitor.instance = new OpenOCDErrorMonitor( + errorHintProvider, + workspaceRoot + ); + } + return OpenOCDErrorMonitor.instance; + } + + public async initialize(): Promise { + try { + // Check OpenOCD version first + const openOCDManager = OpenOCDManager.init(); + const version = await openOCDManager.version(); + + if (!version) { + Logger.info( + "Could not determine OpenOCD version. Hints file won't be loaded." + ); + return null; + } + + // Skip initialization if openOCD version is not supporting hints + const minRequiredVersion = "v0.12.0-esp32-20250226"; + if (!version || !PreCheck.openOCDVersionValidator(minRequiredVersion, version)) { + Logger.info(`OpenOCD version ${version} doesn't support hints. Minimum required: ${minRequiredVersion}`); + return; + } + + // Load OpenOCD hints data + const toolsPath = idfConf.readParameter( + "idf.toolsPath", + this.workspaceRoot + ) as string; + + const openOcdHintsPath = await getOpenOcdHintsYmlPath(toolsPath, version); + + if (openOcdHintsPath) { + try { + const fileContents = await readFile(openOcdHintsPath, "utf-8"); + this.hintsData = yaml.load(fileContents) as OpenOCDHint[]; + Logger.info(`Loaded OpenOCD hints from ${openOcdHintsPath}`); + } catch (error) { + Logger.errorNotify( + `Error processing OpenOCD hints file: ${error.message}`, + error, + "OpenOCDErrorMonitor initialize" + ); + this.hintsData = []; + } + } else { + Logger.info(`OpenOCD hints file not found at ${openOcdHintsPath}`); + this.hintsData = []; + } + + // Start monitoring OpenOCD output + this.watchOpenOCDStatus(); + } catch (error) { + Logger.errorNotify( + `Error initializing OpenOCD error monitor: ${error.message}`, + error, + "OpenOCDErrorMonitor initialize" + ); + } + } + + public dispose(): void { + if (this.openOCDLogWatcher) { + this.openOCDLogWatcher.dispose(); + this.openOCDLogWatcher = null; + } + + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + this.debounceTimer = null; + } + } + + private watchOpenOCDStatus(): void { + // Set up watcher for OpenOCD output + if (this.openOCDLogWatcher) { + this.openOCDLogWatcher.dispose(); + } + + const openOCDManager = OpenOCDManager.init(); + + // Add event listener to OpenOCDManager to detect when there's new output + openOCDManager.on("data", (data) => { + const content = data.toString(); + this.processOutput(content); + }); + + openOCDManager.on("error", (error, data) => { + const content = data ? data.toString() : error.message; + this.processOutput(content); + }); + + this.openOCDLogWatcher = { + dispose: () => { + openOCDManager.removeAllListeners("data"); + openOCDManager.removeAllListeners("error"); + } + }; + } + + private processOutput(content: string): void { + if (!content) return; + + // Split into lines and process each line + const lines = content.split('\n'); + + for (const line of lines) { + this.processOutputLine(line.trim()); + } + } + + public processOutputLine(line: string): void { + // Skip empty lines + if (!line) return; + + // Add line to buffer + this.errorBuffer.push(line); + + // Keep the buffer at a reasonable size (last 100 lines) + if (this.errorBuffer.length > 100) { + this.errorBuffer.shift(); + } + + // Check for OpenOCD error patterns in the recent output + if (line.includes("Error:") || line.includes("Warn:")) { + this.debouncedAnalyzeErrors(); + } + } + + private debouncedAnalyzeErrors(): void { + // Debounce to avoid excessive processing + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + + this.debounceTimer = setTimeout(() => { + this.analyzeErrors(); + }, this.DEBOUNCE_TIME); + } + + private async analyzeErrors(): Promise { + try { + // Get the last few lines of context that might contain errors + const context = this.errorBuffer.join("\n"); + + // Look for error patterns + for (const hint of this.hintsData) { + // Skip if the hint is for a specific source that doesn't match + if (hint.source && hint.source !== 'ocd') { + continue; + } + + // Process variables if they exist + if (hint.variables && hint.variables.length > 0) { + let foundMatch = false; + + for (const variableSet of hint.variables) { + const reVariables = variableSet.re_variables; + const hintVariables = variableSet.hint_variables; + + let re = this.formatEntry(reVariables, hint.re); + let hintMsg = this.formatEntry(hintVariables, hint.hint); + + const regex = new RegExp(re, 'i'); + const match = regex.exec(context); + + if (match) { + // Format the hint message if match_to_output is true + if (hint.match_to_output && match.length > 0 && hintMsg.includes("{}")) { + hintMsg = hintMsg.replace("{}", match[0]); + } + + // Show the error hint + await this.showErrorHint(match[0], hintMsg, hint.ref); + foundMatch = true; + break; + } + } + + if (foundMatch) break; + } else { + // No variables, just check the regex directly + const regex = new RegExp(hint.re, 'i'); + const match = regex.exec(context); + + if (match) { + // Format the hint message + let hintMessage = hint.hint; + + if (hint.match_to_output && match.length > 0 && hintMessage.includes("{}")) { + hintMessage = hintMessage.replace("{}", match[0]); + } + + // Trigger the error hint display + await this.showErrorHint(match[0], hintMessage, hint.ref); + + // Only show one hint at a time to avoid overwhelming the user + break; + } + } + } + } catch (error) { + Logger.errorNotify( + `Error analyzing OpenOCD output: ${error.message}`, + error, + "analyzeErrors" + ); + } + } + + private formatEntry(vars: string[], entry: string): string { + let formattedEntry = entry; + + // Replace numbered placeholders with variables + let i = 0; + while (formattedEntry.includes("{}")) { + formattedEntry = formattedEntry.replace("{}", `{${i++}}`); + } + + // Replace {n} with corresponding variable from vars array + formattedEntry = formattedEntry.replace(/\{(\d+)\}/g, (match, idx) => { + const index = parseInt(idx, 10); + return index < vars.length ? vars[index] : ""; + }); + + return formattedEntry; + } + + private async showErrorHint(errorMessage: string, hintMessage: string, reference?: string): Promise { + try { + // Use the existing error hint provider to display the hint + await this.errorHintProvider.showOpenOCDErrorHint(errorMessage, hintMessage, reference); + } catch (error) { + Logger.errorNotify( + `Error showing OpenOCD error hint: ${error.message}`, + error, + "showErrorHint" + ); + } + } +} \ No newline at end of file diff --git a/src/espIdf/hints/utils.ts b/src/espIdf/hints/utils.ts new file mode 100644 index 000000000..d4ab4cea1 --- /dev/null +++ b/src/espIdf/hints/utils.ts @@ -0,0 +1,49 @@ +import * as path from "path"; +import { pathExists } from "fs-extra"; +import { Logger } from "../../logger/logger"; + +/** + * Gets the path to the OpenOCD hints YAML file for the specified version. + * @param toolsPath - The base path where IDF tools are installed. + * @param version - The specific OpenOCD version string (e.g., "v0.12.0-esp32-20250226"). + * @returns The full path to the hints file, or null if not found or an error occurs. + */ +export async function getOpenOcdHintsYmlPath( + toolsPath: string, + version: string +): Promise { + if (!toolsPath || !version) { + Logger.warn("Missing toolsPath or OpenOCD version for getting hints path.", "getOpenOcdHintsYmlPath"); + return null; + } + try { + const hintsPath = path.join( + toolsPath, + "tools", + "openocd-esp32", + version, + "openocd-esp32", + "share", + "openocd", + "espressif", + "tools", + "esp_problems_hints.yml" + ); + + if (!(await pathExists(hintsPath))) { + Logger.info( + `OpenOCD hints file not found at expected location: ${hintsPath}. Hints may require a specific OpenOCD version or setup.`, "getOpenOcdHintsYmlPath" + ); + return null; + } + + return hintsPath; + } catch (error) { + Logger.errorNotify( + `Error determining OpenOCD hints path: ${error.message}`, + error, + "getOpenOcdHintsYmlPath" + ); + return null; + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 1ad3862e7..50b824d70 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -40,7 +40,10 @@ import { AppTraceTreeDataProvider } from "./espIdf/tracing/tree/appTraceTreeData import * as idfConf from "./idfConfiguration"; import { Logger } from "./logger/logger"; import { OutputChannel } from "./logger/outputChannel"; -import { showInfoNotificationWithAction } from "./logger/utils"; +import { + showInfoNotificationWithAction, + showInfoNotificationWithMultipleActions, +} from "./logger/utils"; import * as utils from "./utils"; import { PreCheck } from "./utils"; import { @@ -155,7 +158,11 @@ import { checkDebugAdapterRequirements } from "./espIdf/debugAdapter/checkPyReqs import { CDTDebugConfigurationProvider } from "./cdtDebugAdapter/debugConfProvider"; import { CDTDebugAdapterDescriptorFactory } from "./cdtDebugAdapter/server"; import { IdfReconfigureTask } from "./espIdf/reconfigure/task"; -import { ErrorHintProvider, HintHoverProvider } from "./espIdf/hints/index"; +import { + ErrorHintProvider, + ErrorHintTreeItem, + HintHoverProvider, +} from "./espIdf/hints/index"; import { installWebsocketClient } from "./espIdf/monitor/checkWebsocketClient"; import { TroubleshootingPanel } from "./support/troubleshootPanel"; import { createCmdsStatusBarItems, statusBarItems } from "./statusBar"; @@ -177,6 +184,8 @@ import { HexViewProvider, } from "./cdtDebugAdapter/hexViewProvider"; import { configureClangSettings } from "./clang"; +import { OpenOCDErrorMonitor } from "./espIdf/hints/openocdhint"; +import { updateHintsStatusBarItem } from "./statusBar"; // Global variables shared by commands let workspaceRoot: vscode.Uri; @@ -355,6 +364,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand(name, telemetryCallback) ); }; + // init rainmaker cache store ESP.Rainmaker.store = RainmakerStore.init(context); @@ -3768,63 +3778,111 @@ export async function activate(context: vscode.ExtensionContext) { "espressif.esp-idf-extension#espIdf.walkthrough.basic-usage" ); } - // Hints Viewer + if (PreCheck.isWorkspaceFolderOpen()) { + const treeDataProvider = new ErrorHintProvider(context); + + const treeView = vscode.window.createTreeView("espIdf.errorHints", { + treeDataProvider: treeDataProvider, + showCollapseAll: true, + }); + + treeView.title = "Error Hints"; - const treeDataProvider = new ErrorHintProvider(context); - vscode.window.registerTreeDataProvider("errorHints", treeDataProvider); + // Add the tree view to disposables + context.subscriptions.push(treeView); - vscode.commands.registerCommand("espIdf.searchError", async () => { - const errorMsg = await vscode.window.showInputBox({ - placeHolder: "Enter the error message", + // Register commands for clearing error hints + registerIDFCommand("espIdf.errorHints.clearAll", () => { + treeDataProvider.clearErrorHints(true); // Clear both build and OpenOCD errors + updateHintsStatusBarItem(false); }); - if (errorMsg) { - treeDataProvider.searchError(errorMsg, workspaceRoot); - await vscode.commands.executeCommand("errorHints.focus"); - } - }); - // Function to process diagnostics and update error hints - const processDiagnostics = async (uri: vscode.Uri) => { - const diagnostics = vscode.languages.getDiagnostics(uri); + registerIDFCommand("espIdf.errorHints.clearBuildErrors", () => { + treeDataProvider.clearErrorHints(false); // Clear only build errors + updateHintsStatusBarItem(false); + }); - const errorDiagnostics = diagnostics.filter( - (d) => d.severity === vscode.DiagnosticSeverity.Error + registerIDFCommand("espIdf.errorHints.clearOpenOCDErrors", () => { + treeDataProvider.clearOpenOCDErrorsOnly(); // Clear only OpenOCD errors + updateHintsStatusBarItem(false); + }); + + const openOCDErrorMonitor = OpenOCDErrorMonitor.init( + treeDataProvider, + workspaceRoot ); + await openOCDErrorMonitor.initialize(); - if (errorDiagnostics.length > 0) { - const errorMsg = errorDiagnostics[0].message; - await treeDataProvider.searchError(errorMsg, workspaceRoot); - } else { - treeDataProvider.clearErrorHints(); - } - }; + // Register disposal of the monitor + context.subscriptions.push({ + dispose: () => { + openOCDErrorMonitor.dispose(); + }, + }); - // Attach a listener to the diagnostics collection - context.subscriptions.push( - vscode.languages.onDidChangeDiagnostics((event) => { - event.uris.forEach((uri) => { - processDiagnostics(uri); + // Register command to manually search for errors + registerIDFCommand("espIdf.searchError", async () => { + const errorMsg = await vscode.window.showInputBox({ + placeHolder: "Enter the error message", }); - }) - ); + if (errorMsg) { + treeDataProvider.searchError(errorMsg, workspaceRoot); + await vscode.commands.executeCommand("espIdf.errorHints.focus"); + } + }); - // Listen to the active text editor change event - context.subscriptions.push( - vscode.window.onDidChangeActiveTextEditor((editor) => { - if (editor) { - processDiagnostics(editor.document.uri); + // Function to process all ESP-IDF diagnostics from the problems panel + const processEspIdfDiagnostics = async () => { + // Get all diagnostics from all files that have source "esp-idf" + const espIdfDiagnostics: Array<{ + uri: vscode.Uri; + diagnostic: vscode.Diagnostic; + }> = []; + + // Collect all diagnostics from all files that have source "esp-idf" + vscode.languages.getDiagnostics().forEach(([uri, diagnostics]) => { + diagnostics + .filter( + (d) => + d.source === "esp-idf" && + d.severity === vscode.DiagnosticSeverity.Error + ) + .forEach((diagnostic) => { + espIdfDiagnostics.push({ uri, diagnostic }); + }); + }); + + // Only clear build errors if no ESP-IDF diagnostics + if (espIdfDiagnostics.length === 0) { + treeDataProvider.clearErrorHints(false); // Don't clear OpenOCD errors + return; } - }) - ); - // Register the HintHoverProvider - context.subscriptions.push( - vscode.languages.registerHoverProvider( - { pattern: "**" }, - new HintHoverProvider(treeDataProvider) - ) - ); + // Process all errors and collect hints + for (const { diagnostic } of espIdfDiagnostics) { + await treeDataProvider.searchError(diagnostic.message, workspaceRoot); + } + }; + + // Attach a listener to the diagnostics collection + vscode.languages.onDidChangeDiagnostics((_event) => { + processEspIdfDiagnostics(); + }); + + // Register the HintHoverProvider + context.subscriptions.push( + vscode.languages.registerHoverProvider( + { pattern: "**" }, + new HintHoverProvider(treeDataProvider) + ) + ); + + // Subscribe to changes in the hints tree and update the status bar item + treeDataProvider.onDidChangeTreeData(() => { + updateHintsStatusBarItem(treeDataProvider.hasHints()); + }); + } checkAndNotifyMissingCompileCommands(); diff --git a/src/logger/utils.ts b/src/logger/utils.ts index 2d4576381..216520cde 100644 --- a/src/logger/utils.ts +++ b/src/logger/utils.ts @@ -23,6 +23,39 @@ export async function showInfoNotificationWithAction( } } +/** + * Shows an information notification with multiple buttons that execute custom actions when clicked. + * @param {string} infoMessage - The information message to display. + * @param {Array<{label: string, action: NotificationAction}>} actions - An array of objects, each containing a button label and an action to perform when clicked. + * @returns {Promise} - A promise that resolves when the notification is shown and handled. + * @example + * showInfoNotificationWithMultipleActions( + * "Solution available", + * [ + * { label: "View Solution", action: () => openSolution() }, + * { label: "Mute for this session", action: () => disableNotifications() } + * ] + * ); + */ +export async function showInfoNotificationWithMultipleActions( + infoMessage: string, + actions: { label: string; action: NotificationAction }[] +): Promise { + const selectedOption = await vscode.window.showInformationMessage( + infoMessage, + ...actions.map((action) => action.label) + ); + + if (selectedOption) { + const selectedAction = actions.find( + (action) => action.label === selectedOption + ); + if (selectedAction) { + await Promise.resolve(selectedAction.action()); + } + } +} + /** * Shows an error notification with a button that opens a link when clicked. * @param {string} infoMessage - The waning message to display. diff --git a/src/statusBar/index.ts b/src/statusBar/index.ts index 3fb665396..f51909b61 100644 --- a/src/statusBar/index.ts +++ b/src/statusBar/index.ts @@ -25,6 +25,7 @@ import { UIKind, Uri, window, + l10n, } from "vscode"; import { getCurrentIdfSetup } from "../versionSwitcher"; import { readParameter } from "../idfConfiguration"; @@ -235,6 +236,14 @@ export async function createCmdsStatusBarItems(workspaceFolder: Uri) { 89, commandDictionary[CommandKeys.CustomTask].checkboxState ); + statusBarItems["hints"] = createStatusBarItem( + l10n.t("💡 New ESP-IDF Hints!"), + l10n.t("ESP-IDF: Hints available. Click to view."), + "espIdf.errorHints.focus", + 1000, + TreeItemCheckboxState.Unchecked + ); + statusBarItems["hints"].hide(); return statusBarItems; } @@ -256,3 +265,32 @@ export function createStatusBarItem( } return statusBarItem; } + +/** + * Show the hints status bar item with an alert icon if hints are available + * @param {boolean} hasHints - Whether hints are available + */ +export function updateHintsStatusBarItem(hasHints: boolean) { + if (!statusBarItems["hints"]) return; + if (hasHints) { + statusBarItems["hints"].text = l10n.t("💡 New ESP-IDF Hints!"); + statusBarItems["hints"].tooltip = l10n.t( + "ESP-IDF: Hints available. Click to view." + ); + statusBarItems["hints"].backgroundColor = "statusBarItem.warningBackground"; + statusBarItems["hints"].show(); + } else { + statusBarItems["hints"].hide(); + statusBarItems["hints"].color = undefined; + } +} + +/** + * Dispose the hints status bar item + */ +export function disposeHintsStatusBarItem() { + if (statusBarItems["hints"]) { + statusBarItems["hints"].dispose(); + statusBarItems["hints"] = undefined; + } +}