diff --git a/CHANGELOG.md b/CHANGELOG.md index fd84b8c59..ff143086d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add OCaml onboarding welcome screen. (#1737) - Fix Windows path handling regression introduced in 1.32.1 by using `Uri.fsPath` instead of `Uri.path` in workspace root detection, preventing LSP start failures (`ENOENT -4058`). (#1929) diff --git a/assets/ocaml_survey.png b/assets/ocaml_survey.png new file mode 100644 index 000000000..53e5e007c Binary files /dev/null and b/assets/ocaml_survey.png differ diff --git a/assets/opam_init.jpg b/assets/opam_init.jpg new file mode 100644 index 000000000..7b9a54edd Binary files /dev/null and b/assets/opam_init.jpg differ diff --git a/assets/opam_install.jpg b/assets/opam_install.jpg new file mode 100644 index 000000000..139b6cd5e Binary files /dev/null and b/assets/opam_install.jpg differ diff --git a/assets/opam_switch.jpg b/assets/opam_switch.jpg new file mode 100644 index 000000000..6c9c4a91b Binary files /dev/null and b/assets/opam_switch.jpg differ diff --git a/assets/utop.png b/assets/utop.png new file mode 100644 index 000000000..f877839b3 Binary files /dev/null and b/assets/utop.png differ diff --git a/assets/vscode-ocaml-commands.gif b/assets/vscode-ocaml-commands.gif new file mode 100644 index 000000000..884113c09 Binary files /dev/null and b/assets/vscode-ocaml-commands.gif differ diff --git a/package.json b/package.json index 0c12f86f6..b816aefb7 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,147 @@ "test": "vscode-test" }, "contributes": { + "walkthroughs": [ + { + "id": "dune-onboarding", + "title" : "OCaml: Setup Dune Package Management (manual)", + "description": "A guided, terminal based, installation of Dune Package Management", + "steps": [ + { + "id": "install-dune-linux", + "when": "isLinux", + "title": "Install dune Package Management", + "description": "Dune Package Management is a new package manager for OCaml. Install it using the official script installer.\n\n[Open a terminal and run the dune install script](command:ocaml.install-dune)", + "media": { + "image": "assets/opam_install.jpg", + "altText": "While installing dune, you'll be asked a series of questions: (1) Where should dune be installed? You can press enter and use the default location, unless you wish to install it somehwere else. (2) if your shell config should be updated." + }, + "completionEvents": [ + "onCommand:ocaml.install-dune" + ] + }, + { + "id": "install-dune-mac", + "when": "isMac", + "title": "Install dune for DPM", + "description": "Dune Package Management is a new package manager for OCaml. Install it using the official script installer.\n\n[Open a terminal and run the dune install script](command:ocaml.install-dune)", + "media": { + "image": "assets/opam_install.jpg", + "altText": "While installing dune, you'll be asked a series of questions: (1) Where should dune be installed? You can press enter and use the default location, unless you wish to install it somehwere else. (2) if your shell config should be updated." + }, + "completionEvents": [ + "onCommand:ocaml.install-dune" + ] + } + ] + }, + { + "id": "opam-onboarding", + "title": "OCaml: Setup opam dev environment (manual)", + "description": "A guided, terminal based, installation of OCaml and essential tools with the opam package manager.", + "steps": [ + { + "id": "install-opam-windows", + "when": "isWindows", + "title": "Install opam", + "description": "opam is the package manager for OCaml. Installing opam also installs the OCaml compiler. On Windows 10 or 11, install it using Winget:\n\nRun `winget install Git.Git OCaml.opam` in PowerShell.\n\n[Open a terminal and run winget](command:ocaml.install-opam)", + "media": { + "image": "assets/opam_install.jpg", + "altText": "While installing opam, you'll be asked a series of questions: (1) Where should opam be installed? You can press enter and use the default location, unless you wish to install it somehwere else. (2) Sometimes it may request administrator write access, you should enter your password to authorize the installer." + }, + "completionEvents": [ + "onCommand:ocaml.install-opam" + ] + }, + { + "id": "install-opam-mac", + "when": "isMac", + "title": "Install opam", + "description": "opam is the package manager for OCaml. Installing opam also installs the OCaml compiler. Install it opam's official script installer.\n\n[Open a terminal and run the opam install script](command:ocaml.install-opam)", + "media": { + "image": "assets/opam_install.jpg", + "altText": "While installing opam, you'll be asked a series of questions: (1) Where should opam be installed? You can press enter and use the default location, unless you wish to install it somehwere else. (2) Sometimes it may request administrator write access, you should enter your password to authorize the installer." + }, + "completionEvents": [ + "onCommand:ocaml.install-opam" + ] + }, + { + "id": "install-opam-linux", + "when": "isLinux", + "title": "Install opam", + "description": "opam is the package manager for OCaml. Installing opam also installs the OCaml compiler. Install it opam's official script installer.\n\n[Open a terminal and run the opam install script](command:ocaml.install-opam)", + "media": { + "image": "assets/opam_install.jpg", + "altText": "While installing opam, you'll be asked a series of questions: (1) Where should opam be installed? You can press enter and use the default location, unless you wish to install it somehwere else. (2) Sometimes it may request administrator write access, you should enter your password to authorize the installer." + }, + "completionEvents": [ + "onCommand:ocaml.install-opam" + ] + }, + { + "id": "init-opam", + "title": "Initialize opam", + "description": "After installing opam, we initialise it so as to prepare your system to use it for managing OCaml packages and compilers.\n\n[Open a terminal and run opam init](command:ocaml.init-opam)", + "media": { + "image": "assets/opam_init.jpg", + "altText": "For the different options: (1) → Updates ~/.profile to automatically configure opam for future shell sessions. (2) → Does not update shell configs; requires manual eval $(opam env) after switching. (3) → Allows selecting a different shell (e.g., zsh, fish) for configuration. (4) → Lets you specify a custom config file instead of ~/.profile.5 → No automatic setup; you must manually run eval $(opam env) when needed." + }, + "completionEvents": [ + "onCommand:ocaml.init-opam" + ] + }, + { + "id": "activate-opam-switch", + "title": "Activate the opam switch", + "description": "An opam switch is an isolated OCaml environment (like a Python virtual environment) where you can install different OCaml versions and packages. \n\n[Select a switch and activate it](command:ocaml.select-sandbox)", + "media": { + "image": "assets/opam_switch.jpg", + "altText": "An image of tiny ocaml logos arranged in the shape of a camel" + }, + "completionEvents": [ + "onCommand:ocaml.select-sandbox" + ] + }, + { + "id": "install-platform-tools", + "title": "Install OCaml Platform Tools", + "description": "Now install essential development tools:\n\n- OCaml LSP server enables editor support for OCaml, odoc generates documentation, camlformat automatically formats OCaml code, utop is an interactive REPL for OCaml and dune is the official OCaml build system.\n\n[Install Platform Tools](command:ocaml.install-ocaml-dev)", + "media": { + "image": "assets/vscode-ocaml-commands.gif", + "altText": "A gif of how to access all commands provide by the ocaml vscode plugin which can be accessed by pressing Ctrl+Alt+P and typing ocaml" + }, + "completionEvents": [ + "onCommand:ocaml.install-ocaml-dev" + ] + }, + { + "id": "check-installation", + "title": "Check Installation", + "description": "Verify your OCaml installation by running utop. If everything is set up correctly, you should see a prompt like the image on the right. \n\n[Check Installation](command:ocaml.open-utop)", + "media": { + "image": "assets/utop.png", + "altText": "A screenshot showing the output of running utop in a command line" + }, + "completionEvents": [ + "onCommand:ocaml.open-utop" + ] + }, + { + "id": "finish-onboarding", + "title": "Congratulations", + "description": "You now have OCaml installed and setup on your computer. Please take a few minutes to answer this short survey.\n\n[OCaml Survey](https://docs.google.com/forms/d/e/1FAIpQLSfGGFZBiw4PF7L0yt2DBX8443G5_7aFL5v6wvo6p5MwL-DW8Q/viewform?usp=pp_url&entry.454013858=Link+in+the+VSCode+plugin)", + "media": { + "image": "assets/ocaml_survey.png", + "altText": "A screenshot of some of the various companies that use OCaml including JaneStreet, Bloomberg, ahrefs, Tezos, Facebook, Microsoft, Docker" + }, + "completionEvents": [ + "onLink:https://docs.google.com/forms/d/e/1FAIpQLSfGGFZBiw4PF7L0yt2DBX8443G5_7aFL5v6wvo6p5MwL-DW8Q/viewform?usp=pp_url&entry.454013858=Link+in+the+VSCode+plugin" + ] + } + ] + } + ], "breakpoints": [ { "language": "ocaml" @@ -1372,4 +1513,4 @@ "color": "#f29100", "theme": "light" } -} +} \ No newline at end of file diff --git a/src/command_api.ml b/src/command_api.ml index b8f880ef5..b4571f90e 100644 --- a/src/command_api.ml +++ b/src/command_api.ml @@ -150,6 +150,11 @@ module Internal = struct let augment_selection_type_verbosity = unit_handle "augment-selection-type-verbosity" let install_dune_lsp = unit_handle "install-dune-lsp" let run_dune_pkg_lock = unit_handle "run_dune_pkg_lock" + let install_opam = unit_handle "install-opam" + let init_opam = unit_handle "init-opam" + let install_ocaml_dev = unit_handle "install-ocaml-dev" + let open_utop = unit_handle "open-utop" + let install_dune = unit_handle "install-dune" end module Vscode = struct diff --git a/src/command_api.mli b/src/command_api.mli index ed6ded0f8..b6cb52111 100644 --- a/src/command_api.mli +++ b/src/command_api.mli @@ -51,6 +51,11 @@ module Internal : sig val augment_selection_type_verbosity : (unit, unit) handle val install_dune_lsp : (unit, unit) handle val run_dune_pkg_lock : (unit, unit) handle + val install_opam : (unit, unit) handle + val init_opam : (unit, unit) handle + val install_ocaml_dev : (unit, unit) handle + val open_utop : (unit, unit) handle + val install_dune : (unit, unit) handle end module Vscode : sig diff --git a/src/extension_commands.ml b/src/extension_commands.ml index 16e09cd3e..ed85ab065 100644 --- a/src/extension_commands.ml +++ b/src/extension_commands.ml @@ -247,6 +247,241 @@ let _switch_impl_intf = command Command_api.Internal.switch_impl_intf callback ;; +let walkthrough_terminal_instance = ref None + +let walkthrough_terminal title instance = + match !walkthrough_terminal_instance with + | Some t -> t + | None -> + let t = Terminal_sandbox.create ~name:title (Extension_instance.sandbox instance) in + walkthrough_terminal_instance := Some t; + t +;; + +let _install_dune = + let callback (instance : Extension_instance.t) () = + let process_installation () = + let open Promise.Syntax in + let* dune = Dune.make (Sandbox.workspace_root ()) () in + match dune with + | None -> + let options = + ProgressOptions.create + ~location:(`ProgressLocation Notification) + ~title:"Installing Dune Package Management" + ~cancellable:false + () + in + let task ~progress:_ ~token:_ = + let+ result = + match Platform.t with + | Win32 -> + let _ = + show_message + `Info + "Dune Package Management is not supported on Windows yet!" + in + Ok () |> Promise.return + | Darwin | Linux | Other -> + let open Promise.Result.Syntax in + let+ _ = + let _ = + let terminal = + walkthrough_terminal "Dune Package Management Installer" instance + in + let _ = Terminal_sandbox.show ~preserveFocus:true terminal in + Terminal_sandbox.send + terminal + "bash -c \"sh <(curl -fsSL \ + https://github.com/ocaml-dune/dune-bin-install/releases/download/v2/install.sh)\"" + in + Ok () |> Promise.return + in + () + in + match result with + | Ok () -> Ojs.null + | Error err -> + show_message `Error "An error occured while installing dune %s" err; + Ojs.null + in + let+ _ = Vscode.Window.withProgress (module Ojs) ~options ~task in + () + | Some _ -> + show_message `Info "Dune Package Management is already installed!" + |> Promise.return + in + let (_ : unit Promise.t) = process_installation () in + () + in + command Command_api.Internal.install_dune callback +;; + +let _install_opam = + let callback (instance : Extension_instance.t) () = + let process_installation () = + let open Promise.Syntax in + let* opam = Opam.make () in + match opam with + | None -> + let options = + ProgressOptions.create + ~location:(`ProgressLocation Notification) + ~title:"Installing opam package manager" + ~cancellable:false + () + in + let task ~progress:_ ~token:_ = + let+ result = + match Platform.t with + | Win32 -> + let _ = + let terminal = + Extension_instance.sandbox instance |> Terminal_sandbox.create + in + let _ = Terminal_sandbox.show ~preserveFocus:true terminal in + Terminal_sandbox.send terminal "winget install Git.Git OCaml.opam" + in + Ok () |> Promise.return + | Darwin | Linux | Other -> + let open Promise.Result.Syntax in + let+ _ = + let _ = + let terminal = + walkthrough_terminal "OCaml Platform Walkthrough" instance + in + let _ = Terminal_sandbox.show ~preserveFocus:true terminal in + Terminal_sandbox.send + terminal + "bash -c \"sh <(curl -fsSL https://opam.ocaml.org/install.sh)\"" + in + Ok () |> Promise.return + in + () + in + match result with + | Ok () -> Ojs.null + | Error err -> + show_message `Error "An error occured while installing opam %s" err; + Ojs.null + in + let+ _ = Vscode.Window.withProgress (module Ojs) ~options ~task in + () + | Some _ -> show_message `Info "Opam is already installed!" |> Promise.return + in + let (_ : unit Promise.t) = process_installation () in + () + in + command Command_api.Internal.install_opam callback +;; + +let _init_opam = + let callback (instance : Extension_instance.t) () = + let options = + ProgressOptions.create + ~location:(`ProgressLocation Notification) + ~title:"Initialising opam" + ~cancellable:false + () + in + let task ~progress:_ ~token:_ = + let open Promise.Syntax in + let+ result = + let open Promise.Result.Syntax in + let+ _ = + let _ = + let terminal = walkthrough_terminal "OCaml Platform Walkthrough" instance in + let _ = Terminal_sandbox.show ~preserveFocus:true terminal in + Terminal_sandbox.send terminal "opam init" + in + Ok () |> Promise.return + in + () + in + match result with + | Ok () -> Ojs.null + | Error err -> + show_message `Error "An error occured while initializing opam %s" err; + Ojs.null + in + let _ = Vscode.Window.withProgress (module Ojs) ~options ~task in + () + in + command Command_api.Internal.init_opam callback +;; + +let _install_ocaml_dev = + let callback (instance : Extension_instance.t) () = + let options = + ProgressOptions.create + ~location:(`ProgressLocation Notification) + ~title:"Installing development packages" + ~cancellable:false + () + in + let task ~progress:_ ~token:_ = + let open Promise.Syntax in + let+ result = + let open Promise.Result.Syntax in + let+ _ = + let _ = + let terminal = walkthrough_terminal "OCaml Platform Walkthrough" instance in + let _ = Terminal_sandbox.show ~preserveFocus:true terminal in + Terminal_sandbox.send + terminal + "opam install ocaml-lsp-server odoc ocamlformat utop" + in + Ok () |> Promise.return + in + () + in + match result with + | Ok () -> Ojs.null + | Error err -> + show_message `Error "An error occured while installing packages %s" err; + Ojs.null + in + let _ = Vscode.Window.withProgress (module Ojs) ~options ~task in + () + in + command Command_api.Internal.install_ocaml_dev callback +;; + +let _open_utop = + let callback (instance : Extension_instance.t) () = + let options = + ProgressOptions.create + ~location:(`ProgressLocation Notification) + ~title:"Launching utop" + ~cancellable:false + () + in + let task ~progress:_ ~token:_ = + let open Promise.Syntax in + let+ result = + let open Promise.Result.Syntax in + let+ _ = + let _ = + let terminal = walkthrough_terminal "OCaml Platform Walkthrough" instance in + let _ = Terminal_sandbox.show ~preserveFocus:true terminal in + Terminal_sandbox.send terminal "opam exec -- utop" + in + Ok () |> Promise.return + in + () + in + match result with + | Ok () -> Ojs.null + | Error err -> + show_message `Error "An error occured while opening utop %s" err; + Ojs.null + in + let _ = Vscode.Window.withProgress (module Ojs) ~options ~task in + () + in + command Command_api.Internal.open_utop callback +;; + let _open_current_dune_file = let callback (_ : Extension_instance.t) () = match Vscode.Window.activeTextEditor () with