#
# Copyright (C) 2023-2024 Siemens AG
#
# SPDX-License-Identifier: GPL-2.0-only
#
"""Devtool ide-sdk IDE plugin for VSCode and VSCodium"""

import json
import logging
import os
import shutil
from devtool.ide_plugins import BuildTool, IdeBase, GdbCrossConfig, GdbServerModes, get_devtool_deploy_opts

logger = logging.getLogger('devtool')


class GdbCrossConfigVSCode(GdbCrossConfig):
    def __init__(self, image_recipe, modified_recipe, binary,
                 gdbserver_default_mode=GdbServerModes.ONCE):
        super().__init__(image_recipe, modified_recipe, binary,
                         gdbserver_default_mode)

    def target_ssh_gdbserver_start_args(self, gdbserver_mode=None):
        """Get the ssh command arguments to start gdbserver on the target device

        returns something like:
          ['-p', '2222', 'root@target', '"/bin/sh -c \'/usr/bin/gdbserver --once :1234 /usr/bin/cmake-example\'"']
        """
        if gdbserver_mode is None:
            gdbserver_mode = self.gdbserver_default_mode
        return self._target_ssh_gdbserver_args() + [
            self._target_gdbserver_start_cmd(gdbserver_mode)
        ]

    def target_ssh_gdbserver_kill_args(self):
        """Get the ssh command arguments to kill gdbserver on the target device

        returns something like:
          ['-p', '2222', 'root@target', '"kill $(pgrep -o -f \'gdbserver --attach :1234\') 2>/dev/null || true"']
        """
        return self._target_ssh_gdbserver_args() + [
            self._target_gdbserver_kill_cmd()
        ]

    def initialize(self):
        pass


class IdeVSCode(IdeBase):
    """Manage IDE configurations for VSCode

    Modified recipe mode:
    - cmake: use the cmake-preset generated by devtool ide-sdk
    - meson: meson is called via a wrapper script generated by devtool ide-sdk

    Shared sysroot mode:
    In shared sysroot mode, the cross tool-chain is exported to the user's global configuration.
    A workspace cannot be created because there is no recipe that defines how a workspace could
    be set up.
    - cmake: adds a cmake-kit to .local/share/CMakeTools/cmake-tools-kits.json
             The cmake-kit uses the environment script and the tool-chain file
             generated by meta-ide-support.
    - meson: Meson needs manual workspace configuration.
    """

    @classmethod
    def ide_plugin_priority(cls):
        """If --ide is not passed this is the default plugin"""
        if shutil.which('code'):
            return 100
        return 0

    def setup_shared_sysroots(self, shared_env):
        """Expose the toolchain of the shared sysroots SDK"""
        datadir = shared_env.ide_support.datadir
        deploy_dir_image = shared_env.ide_support.deploy_dir_image
        real_multimach_target_sys = shared_env.ide_support.real_multimach_target_sys
        standalone_sysroot_native = shared_env.build_sysroots.standalone_sysroot_native
        vscode_ws_path = os.path.join(
            os.environ['HOME'], '.local', 'share', 'CMakeTools')
        cmake_kits_path = os.path.join(vscode_ws_path, 'cmake-tools-kits.json')
        oecmake_generator = "Ninja"
        env_script = os.path.join(
            deploy_dir_image, 'environment-setup-' + real_multimach_target_sys)

        if not os.path.isdir(vscode_ws_path):
            os.makedirs(vscode_ws_path)
        cmake_kits_old = []
        if os.path.exists(cmake_kits_path):
            with open(cmake_kits_path, 'r', encoding='utf-8') as cmake_kits_file:
                cmake_kits_old = json.load(cmake_kits_file)
        cmake_kits = cmake_kits_old.copy()

        cmake_kit_new = {
            "name": "OE " + real_multimach_target_sys,
            "environmentSetupScript": env_script,
            "toolchainFile": standalone_sysroot_native + datadir + "/cmake/OEToolchainConfig.cmake",
            "preferredGenerator": {
                "name": oecmake_generator
            }
        }

        def merge_kit(cmake_kits, cmake_kit_new):
            i = 0
            while i < len(cmake_kits):
                if 'environmentSetupScript' in cmake_kits[i] and \
                        cmake_kits[i]['environmentSetupScript'] == cmake_kit_new['environmentSetupScript']:
                    cmake_kits[i] = cmake_kit_new
                    return
                i += 1
            cmake_kits.append(cmake_kit_new)
        merge_kit(cmake_kits, cmake_kit_new)

        if cmake_kits != cmake_kits_old:
            logger.info("Updating: %s" % cmake_kits_path)
            with open(cmake_kits_path, 'w', encoding='utf-8') as cmake_kits_file:
                json.dump(cmake_kits, cmake_kits_file, indent=4)
        else:
            logger.info("Already up to date: %s" % cmake_kits_path)

        cmake_native = os.path.join(
            shared_env.build_sysroots.standalone_sysroot_native, 'usr', 'bin', 'cmake')
        if os.path.isfile(cmake_native):
            logger.info('cmake-kits call cmake by default. If the cmake provided by this SDK should be used, please add the following line to ".vscode/settings.json" file: "cmake.cmakePath": "%s"' % cmake_native)
        else:
            logger.error("Cannot find cmake native at: %s" % cmake_native)

    def dot_code_dir(self, modified_recipe):
        return os.path.join(modified_recipe.srctree, '.vscode')

    def __vscode_settings_meson(self, settings_dict, modified_recipe):
        if modified_recipe.build_tool is not BuildTool.MESON:
            return
        settings_dict["mesonbuild.mesonPath"] = modified_recipe.meson_wrapper

        confopts = modified_recipe.mesonopts.split()
        confopts += modified_recipe.meson_cross_file.split()
        confopts += modified_recipe.extra_oemeson.split()
        settings_dict["mesonbuild.configureOptions"] = confopts
        settings_dict["mesonbuild.buildFolder"] = modified_recipe.b

    def __vscode_settings_cmake(self, settings_dict, modified_recipe):
        """Add cmake specific settings to settings.json.

        Note: most settings are passed to the cmake preset.
        """
        if modified_recipe.build_tool is not BuildTool.CMAKE:
            return
        settings_dict["cmake.configureOnOpen"] = True
        settings_dict["cmake.sourceDirectory"] = modified_recipe.real_srctree

    def __vscode_settings_kernel_module(self, settings_dict, modified_recipe):
        if modified_recipe.build_tool is not BuildTool.KERNEL_MODULE:
            return

        # Define kernel exclude patterns once
        kernel_exclude_patterns = [
            "*.cache/**",
            "*.ko",
            "*.mod.c",
            "*.mod",
            "**/.*.cmd",
            "**/.*.d",
            "**/.*.S",
            "**/.tmp*",
            "**/*.a",
            "**/*.builtin",
            "**/*.map",
            "**/*.modinfo",
            "**/*.o",
            "**/*.order",
            "**/*.orig",
            "**/*.symvers",
            "**/*.tmp"
        ]
        files_excludes_kernel = {pattern: True for pattern in kernel_exclude_patterns}

        settings_dict["files.exclude"].update(files_excludes_kernel)
        settings_dict["files.watcherExclude"].update(files_excludes_kernel)
        settings_dict["python.analysis.exclude"] += kernel_exclude_patterns

        # protect the kernel sources
        settings_dict["files.readonlyInclude"][modified_recipe.staging_kernel_dir + '/**'] = True

        # Export the complete cross-build environment
        settings_dict["terminal.integrated.env.linux"] = modified_recipe.exported_vars

        # and the make configuration
        make_executable = os.path.join(
            modified_recipe.recipe_sysroot_native, 'usr', 'bin', 'make')
        settings_dict["makefile.configurations"] = [
            {
                "name": ' '.join(modified_recipe.make_targets),
                "makePath": make_executable,
                "makeDirectory": modified_recipe.srctree,
                "makefilePath": os.path.join(modified_recipe.srctree, "Makefile"),
                "makeArgs": modified_recipe.extra_oemake + modified_recipe.make_targets
            },
            {
                "name": "clean",
                "makePath": make_executable,
                "makeDirectory": modified_recipe.srctree,
                "makefilePath": os.path.join(modified_recipe.srctree, "Makefile"),
                "makeArgs": modified_recipe.extra_oemake + ["clean"]
            }
        ]

    def vscode_settings(self, modified_recipe, image_recipe):
        files_excludes = {
            "**/.git/**": True,
            "**/oe-logs/**": True,
            "**/oe-workdir/**": True,
            "**/source-date-epoch/**": True
        }
        python_exclude = [
            "**/.git/**",
            "**/oe-logs/**",
            "**/oe-workdir/**",
            "**/source-date-epoch/**"
        ]
        files_readonly = {
            modified_recipe.recipe_sysroot + '/**': True,
            modified_recipe.recipe_sysroot_native + '/**': True,
        }
        if image_recipe.rootfs_dbg is not None:
            files_readonly[image_recipe.rootfs_dbg + '/**'] = True
        settings_dict = {
            "files.watcherExclude": files_excludes,
            "files.exclude": files_excludes,
            "files.readonlyInclude": files_readonly,
            "python.analysis.exclude": python_exclude
        }
        self.__vscode_settings_cmake(settings_dict, modified_recipe)
        self.__vscode_settings_meson(settings_dict, modified_recipe)
        self.__vscode_settings_kernel_module(settings_dict, modified_recipe)

        settings_file = 'settings.json'
        IdeBase.update_json_file(
            self.dot_code_dir(modified_recipe), settings_file, settings_dict)

    def vscode_extensions(self, modified_recipe):
        recommendations = []
        if modified_recipe.build_tool.is_c_cpp_kernel:
            recommendations += [
                "ms-vscode.cpptools",
                "ms-vscode.cpptools-extension-pack",
                "ms-vscode.cpptools-themes"
            ]
        if modified_recipe.build_tool is BuildTool.CMAKE:
            recommendations.append("ms-vscode.cmake-tools")
        if modified_recipe.build_tool is BuildTool.MESON:
            recommendations.append("mesonbuild.mesonbuild")
        if modified_recipe.build_tool is BuildTool.KERNEL_MODULE:
            recommendations.append("ms-vscode.makefile-tools")

        extensions_file = 'extensions.json'
        IdeBase.update_json_file(
            self.dot_code_dir(modified_recipe), extensions_file, {"recommendations": recommendations})

    def vscode_c_cpp_properties(self, modified_recipe):
        properties_dict = {
            "name": modified_recipe.recipe_id_pretty,
        }
        if modified_recipe.build_tool is BuildTool.CMAKE:
            properties_dict["configurationProvider"] = "ms-vscode.cmake-tools"
        elif modified_recipe.build_tool is BuildTool.MESON:
            properties_dict["configurationProvider"] = "mesonbuild.mesonbuild"
            properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.cxx.split()[0])
        elif modified_recipe.build_tool is BuildTool.KERNEL_MODULE:
            # Using e.g. configurationProvider = "ms-vscode.makefile-tools" was not successful
            properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.kernel_cc.split()[0])
            properties_dict["includePath"] = [
                "${workspaceFolder}/**",
                os.path.join(modified_recipe.staging_kernel_dir, "include", "**")
            ]
            # https://www.kernel.org/doc/html/next/process/programming-language.html
            properties_dict["cStandard"] = "gnu11"
        else:  # no C/C++ build
            return

        properties_dicts = {
            "configurations": [
                properties_dict
            ],
            "version": 4
        }
        prop_file = 'c_cpp_properties.json'
        IdeBase.update_json_file(
            self.dot_code_dir(modified_recipe), prop_file, properties_dicts)

    def vscode_launch_bin_dbg(self, gdb_cross_config, gdbserver_mode):
        modified_recipe = gdb_cross_config.modified_recipe

        launch_config = {
            "name": gdb_cross_config.id_pretty_mode(gdbserver_mode),
            "type": "cppdbg",
            "request": "launch",
            "program": gdb_cross_config.binary.binary_host_path,
            "stopAtEntry": True,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": False,
            "MIMode": "gdb",
            "preLaunchTask": gdb_cross_config.id_pretty_mode(gdbserver_mode),
            "miDebuggerPath": modified_recipe.gdb_cross.gdb,
            "miDebuggerServerAddress": "%s:%d" % (modified_recipe.gdb_cross.host, gdb_cross_config.gdbserver_port)
        }

        # Search for header files in recipe-sysroot.
        src_file_map = {
            "/usr/include": os.path.join(modified_recipe.recipe_sysroot, "usr", "include")
        }
        # First of all search for not stripped binaries in the image folder.
        # These binaries are copied (and optionally stripped) by deploy-target
        setup_commands = [
            {
                "description": "sysroot",
                "text": "set sysroot " + modified_recipe.d
            }
        ]

        if gdb_cross_config.image_recipe.rootfs_dbg:
            launch_config['additionalSOLibSearchPath'] = modified_recipe.solib_search_path_str(
                gdb_cross_config.image_recipe)
            # First: Search for sources of this recipe in the workspace folder
            # If compiled with DEBUG_PREFIX_MAP = "", no reverse map is is needed. The binaries
            # contain the full path to the source files. But by default there is a reverse map.
            for target_path, host_path in modified_recipe.reverse_debug_prefix_map.items():
                if host_path.startswith(modified_recipe.real_srctree):
                    src_file_map[target_path] = "${workspaceFolder}" + host_path[len(modified_recipe.real_srctree):]
                else:
                    src_file_map[target_path] = host_path

            # Second: Search for sources of other recipes in the rootfs-dbg
            # We expect that /usr/src/debug/${PN}/${PV} or no mapping is used for the recipes
            # own sources and we can use the key "/usr/src/debug" for the rootfs-dbg.
            if "/usr/src/debug" in src_file_map:
                logger.error(
                    'Key "/usr/src/debug" already exists in src_file_map. '
                    'Something with DEBUG_PREFIX_MAP looks unexpected and finding '
                    'sources in the rootfs-dbg will not work as expected.'
                )
            else:
                src_file_map["/usr/src/debug"] = os.path.join(
                    gdb_cross_config.image_recipe.rootfs_dbg, "usr", "src", "debug")
        else:
            logger.warning(
                "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.")

        # Enable pretty-printing for gdb for resolving STL types with help of python scripts
        pretty_printing_cmd = modified_recipe.gdb_pretty_print_scripts
        if pretty_printing_cmd:
            setup_commands += [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "python " +";".join(pretty_printing_cmd)
                },
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing"
                }
            ]

        launch_config['sourceFileMap'] = src_file_map
        launch_config['setupCommands'] = setup_commands

        # Add postDebugTask for attach mode to clean up gdbserver
        if gdbserver_mode == GdbServerModes.ATTACH:
            kill_task_label = "kill_gdbserver_" + gdb_cross_config.id_pretty_mode(gdbserver_mode)
            launch_config["postDebugTask"] = kill_task_label

        return launch_config

    def vscode_launch(self, args, modified_recipe):
        """GDB launch configurations for user-space binaries.

        Kernel modules are not debugged via gdbserver and have no launch entry.
        Their deployment workflow is driven entirely by tasks.json:
          install && deploy-target  ->  reload module (rmmod + insmod)  ->  verify module
        These tasks can be triggered from the Tasks menu (Ctrl+Shift+P > Run Task)
        or via the default build task (Ctrl+Shift+B for install && deploy-target).
        """

        configurations = []
        for gdb_cross_config in self.gdb_cross_configs:
            if gdb_cross_config.modified_recipe is modified_recipe:
                for gdbserver_mode in gdb_cross_config.gdbserver_modes():
                    configurations.append(self.vscode_launch_bin_dbg(gdb_cross_config, gdbserver_mode))
        launch_dict = {
            "version": "0.2.0",
            "configurations": configurations
        }
        launch_file = 'launch.json'
        IdeBase.update_json_file(
            self.dot_code_dir(modified_recipe), launch_file, launch_dict)

    def vscode_tasks_cpp(self, args, modified_recipe):
        run_install_deploy = modified_recipe.gen_install_deploy_script(args)
        install_task_name = "install && deploy-target %s" % modified_recipe.recipe_id_pretty
        tasks_dict = {
            "version": "2.0.0",
            "tasks": [
                {
                    "label": install_task_name,
                    "type": "shell",
                    "command": run_install_deploy,
                    "args": [
                        "--target",
                        args.target
                    ],
                    "problemMatcher": []
                }
            ]
        }
        for gdb_cross_config in self.gdb_cross_configs:
            if gdb_cross_config.modified_recipe is not modified_recipe:
                continue
            for gdbserver_mode in gdb_cross_config.gdbserver_modes():
                new_task = {
                    "label": gdb_cross_config.id_pretty_mode(gdbserver_mode),
                    "type": "shell",
                    "isBackground": True,
                    "command": gdb_cross_config.gdb_cross.target_device.ssh_sshexec,
                    "args": gdb_cross_config.target_ssh_gdbserver_start_args(gdbserver_mode),
                    "problemMatcher": [
                        {
                            "pattern": [
                                {
                                    "regexp": ".",
                                    "file": 1,
                                    "location": 2,
                                    "message": 3
                                }
                            ],
                            "background": {
                                "activeOnStart": True,
                                "beginsPattern": ".",
                                "endsPattern": ".",
                            }
                        }
                    ]
                }
                # Deploy the artifacts to the target before starting gdbserver if not already running
                if gdbserver_mode != GdbServerModes.ATTACH:
                    new_task['dependsOn'] = [
                        install_task_name
                    ]

                tasks_dict['tasks'].append(new_task)

                # For attach mode, add a kill task to stop a previously running gdbserver
                # This is a known issue with gdbserver --attach that it does not terminate
                # after detaching. With this helper task, it is possible to:
                # 1. Start debugging in attach mode
                # 2. Add breakpoints, step, continue, etc.
                # 3. Press the Continue button
                # 4. Press the Stop button which detaches gdbserver from the debugged process
                # 5. Start debugging again in attach mode
                # Without this kill task, step 5 would fail because gdbserver is still running
                if gdbserver_mode == GdbServerModes.ATTACH:
                    new_task_kill_label = "kill_gdbserver_"+ gdb_cross_config.id_pretty_mode(gdbserver_mode)
                    new_task_kill = {
                        "label": new_task_kill_label,
                        "type": "shell",
                        "command": gdb_cross_config.gdb_cross.target_device.ssh_sshexec,
                        "args": gdb_cross_config.target_ssh_gdbserver_kill_args(),
                        "presentation": {
                            "close": True
                        },
                        "problemMatcher": []
                    }
                    tasks_dict['tasks'].append(new_task_kill)

        tasks_file = 'tasks.json'
        IdeBase.update_json_file(
            self.dot_code_dir(modified_recipe), tasks_file, tasks_dict)

    @staticmethod
    def _ssh_args(target_device, remote_cmd):
        """Build a VS Code task 'args' list for an ssh command to the target."""
        args = list(target_device.extraoptions)
        if target_device.ssh_port:
            args += list(target_device.ssh_port)
        args += [target_device.target, remote_cmd]
        return args

    def vscode_tasks_kernel_module(self, args, modified_recipe):
        """Generate tasks.json for kernel module recipes.

        Three tasks are generated and chained in sequence:
          1. install && deploy-target  - run bitbake do_install and push the
             freshly built .ko to the target via devtool deploy-target.
          2. reload module  - single SSH call: rmmod (errors ignored) then insmod
             using the path reported by find so depmod is not required.
          3. verify module  - SSH call: lsmod | grep <module> to confirm the new
             module is loaded; output is visible in the task terminal.

        The tasks are linked via dependsOn / dependsOrder: sequence so that
        running the verify task automatically executes the full chain.  The
        launch.json 'reload kernel module' entry uses preLaunchTask: verify,
        providing a single F5 / click action for the complete reload cycle.
        """
        td = modified_recipe.gdb_cross.target_device
        ko_name = modified_recipe.bpn + '.ko'
        # rmmod / lsmod use the kernel module name (- replaced by _ per kernel convention)
        mod_name = modified_recipe.bpn.replace('-', '_')
        install_task_name = "install && deploy-target %s" % modified_recipe.recipe_id_pretty
        reload_task_name = "reload module %s" % modified_recipe.recipe_id_pretty
        verify_task_name = "verify module %s" % modified_recipe.recipe_id_pretty
        run_install_deploy = modified_recipe.gen_install_deploy_script(args)
        tasks_dict = {
            "version": "2.0.0",
            "tasks": [
                {
                    "label": install_task_name,
                    "type": "shell",
                    "command": run_install_deploy,
                    "args": [
                        "--target",
                        args.target
                    ],
                    "problemMatcher": []
                },
                {
                    "label": reload_task_name,
                    "type": "shell",
                    "command": td.ssh_sshexec,
                    "args": self._ssh_args(
                        td,
                        "rmmod %(mod)s 2>/dev/null; insmod $(find /lib/modules -name '%(ko)s' | head -n 1)"
                        % {"mod": mod_name, "ko": ko_name}),
                    "dependsOn": [install_task_name],
                    "dependsOrder": "sequence",
                    "problemMatcher": []
                },
                {
                    "label": verify_task_name,
                    "type": "shell",
                    "command": td.ssh_sshexec,
                    "args": self._ssh_args(td, "lsmod | grep %s" % mod_name),
                    "dependsOn": [reload_task_name],
                    "dependsOrder": "sequence",
                    "problemMatcher": []
                }
            ]
        }
        tasks_file = 'tasks.json'
        IdeBase.update_json_file(
            self.dot_code_dir(modified_recipe), tasks_file, tasks_dict)

    def vscode_tasks_fallback(self, args, modified_recipe):
        oe_init_dir = modified_recipe.oe_init_dir
        oe_init = ". %s %s > /dev/null && " % (modified_recipe.oe_init_build_env, modified_recipe.topdir)
        dt_build = "devtool build "
        dt_build_label = dt_build + modified_recipe.recipe_id_pretty
        dt_build_cmd = dt_build + modified_recipe.bpn
        clean_opt = " --clean"
        dt_build_clean_label = dt_build + modified_recipe.recipe_id_pretty + clean_opt
        dt_build_clean_cmd = dt_build + modified_recipe.bpn + clean_opt
        dt_deploy = "devtool deploy-target "
        dt_deploy_label = dt_deploy + modified_recipe.recipe_id_pretty
        dt_deploy_cmd = dt_deploy + modified_recipe.bpn
        dt_build_deploy_label = "devtool build & deploy-target %s" % modified_recipe.recipe_id_pretty
        deploy_opts = ' '.join(get_devtool_deploy_opts(args))
        tasks_dict = {
            "version": "2.0.0",
            "tasks": [
                {
                    "label": dt_build_label,
                    "type": "shell",
                    "command": "bash",
                    "linux": {
                        "options": {
                            "cwd": oe_init_dir
                        }
                    },
                    "args": [
                        "--login",
                        "-c",
                        "%s%s" % (oe_init, dt_build_cmd)
                    ],
                    "problemMatcher": []
                },
                {
                    "label": dt_deploy_label,
                    "type": "shell",
                    "command": "bash",
                    "linux": {
                        "options": {
                            "cwd": oe_init_dir
                        }
                    },
                    "args": [
                        "--login",
                        "-c",
                        "%s%s %s" % (
                            oe_init, dt_deploy_cmd, deploy_opts)
                    ],
                    "problemMatcher": []
                },
                {
                    "label": dt_build_deploy_label,
                    "dependsOrder": "sequence",
                    "dependsOn": [
                        dt_build_label,
                        dt_deploy_label
                    ],
                    "problemMatcher": [],
                    "group": {
                        "kind": "build",
                        "isDefault": True
                    }
                },
                {
                    "label": dt_build_clean_label,
                    "type": "shell",
                    "command": "bash",
                    "linux": {
                        "options": {
                            "cwd": oe_init_dir
                        }
                    },
                    "args": [
                        "--login",
                        "-c",
                        "%s%s" % (oe_init, dt_build_clean_cmd)
                    ],
                    "problemMatcher": []
                }
            ]
        }
        if modified_recipe.gdb_cross:
            for gdb_cross_config in self.gdb_cross_configs:
                if gdb_cross_config.modified_recipe is not modified_recipe:
                    continue
                for gdbserver_mode in gdb_cross_config.gdbserver_modes():
                    new_task = {
                        "label": gdb_cross_config.id_pretty(gdbserver_mode),
                        "type": "shell",
                        "isBackground": True,
                        "command": gdb_cross_config.gdb_cross.target_device.ssh_sshexec,
                        "args": gdb_cross_config.target_ssh_gdbserver_start_args(gdbserver_mode),
                        "problemMatcher": [
                            {
                                "pattern": [
                                    {
                                        "regexp": ".",
                                        "file": 1,
                                        "location": 2,
                                        "message": 3
                                    }
                                ],
                                "background": {
                                    "activeOnStart": True,
                                    "beginsPattern": ".",
                                    "endsPattern": ".",
                                }
                            }
                        ]
                    }
                    if gdbserver_mode != GdbServerModes.ATTACH:
                        new_task['dependsOn'] = [
                            dt_build_deploy_label
                        ]
                    tasks_dict['tasks'].append(new_task)
        tasks_file = 'tasks.json'
        IdeBase.update_json_file(
            self.dot_code_dir(modified_recipe), tasks_file, tasks_dict)

    def vscode_tasks(self, args, modified_recipe):
        if modified_recipe.build_tool.is_c_ccp:
            self.vscode_tasks_cpp(args, modified_recipe)
        elif modified_recipe.build_tool == BuildTool.KERNEL_MODULE:
            self.vscode_tasks_kernel_module(args, modified_recipe)
        else:
            self.vscode_tasks_fallback(args, modified_recipe)

    def setup_modified_recipe(self, args, image_recipe, modified_recipe):
        self.vscode_settings(modified_recipe, image_recipe)
        self.vscode_extensions(modified_recipe)
        self.vscode_c_cpp_properties(modified_recipe)
        if args.target:
            self.initialize_gdb_cross_configs(
                image_recipe, modified_recipe, GdbCrossConfigVSCode)
            self.vscode_launch(args, modified_recipe)
            self.vscode_tasks(args, modified_recipe)


def register_ide_plugin(ide_plugins):
    ide_plugins['code'] = IdeVSCode
