{"id":16312487,"url":"https://github.com/kiranandcode/gopcaml-mode","last_synced_at":"2025-10-16T10:29:07.264Z","repository":{"id":112325926,"uuid":"345017555","full_name":"kiranandcode/gopcaml-mode","owner":"kiranandcode","description":"[MIRROR] Ultimate Ocaml Editing Mode","archived":false,"fork":false,"pushed_at":"2022-07-12T02:18:19.000Z","size":1183,"stargazers_count":21,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-11T21:48:10.894Z","etag":null,"topics":["code-navigation","editing","emacs","ide","ocaml"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kiranandcode.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-06T05:53:21.000Z","updated_at":"2023-10-19T15:08:50.000Z","dependencies_parsed_at":"2023-05-12T23:00:24.661Z","dependency_job_id":null,"html_url":"https://github.com/kiranandcode/gopcaml-mode","commit_stats":null,"previous_names":["kiranandcode/gopcaml-mode"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiranandcode%2Fgopcaml-mode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiranandcode%2Fgopcaml-mode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiranandcode%2Fgopcaml-mode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiranandcode%2Fgopcaml-mode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kiranandcode","download_url":"https://codeload.github.com/kiranandcode/gopcaml-mode/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221839292,"owners_count":16889592,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-navigation","editing","emacs","ide","ocaml"],"created_at":"2024-10-10T21:48:13.985Z","updated_at":"2025-10-16T10:29:02.224Z","avatar_url":"https://github.com/kiranandcode.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NOTE: THIS IS A MIRROR\nPlease submit issues, feature requests and PRs to https://gitlab.com/gopiandcode/gopcaml-mode\n\n# Gopcaml Ocaml Emacs Major Mode\n\nThe ultimate ocaml editing mode.\n\n## Features\n- AST-based code navigation - `C-M-n, C-M-p, C-M-u, C-M-d, C-M-f, C-M-b`\n\n![ast-code-navigation](https://gitlab.com/gopiandcode/gopcaml-mode/-/raw/master/images/gopcaml_move_expression_example.gif?inline=false)\n\n- AST-based code transformation -`C-M-N, C-M-P, C-M-F, C-M-B`\n\n![ast-code-transform](https://gitlab.com/gopiandcode/gopcaml-mode/-/raw/master/images/gopcaml_move_function_example.gif?inline=false)\n\n- Fixed move-to-defun, move-to-end-defun -`C-M-a, C-M-e`\n\n![move-to-defun](https://gitlab.com/gopiandcode/gopcaml-mode/-/raw/master/images/gopcaml_move_to_defun_example.gif?inline=false)\n\n- Jump to type hole - `TAB`\n\n![jump-to-type-hole](https://gitlab.com/gopiandcode/gopcaml-mode/-/raw/master/images/gopcaml_move_to_type_hole.gif?inline=false)\n\n- Automatic let binding expansion (i.e adds in automatically if defining let inside a let)\n\n![automatic-let-bindings](https://gitlab.com/gopiandcode/gopcaml-mode/-/raw/master/images/gopcaml_auto_let_binding_example.gif?inline=false)\n\n- Mark exp - `C-M-SPC`\n\n![mark-sexp](https://gitlab.com/gopiandcode/gopcaml-mode/-/raw/master/images/gopcaml_mark_sexp.gif?inline=false)\n\n- Move to nearest parameter - `C-c C-p`\n\n![move-to-param](https://gitlab.com/gopiandcode/gopcaml-mode/-/raw/master/images/gopcaml_move_to_parameter.gif?inline=false)\n\n- Move to nearest let def - `C-c C-o`\n\n![move-to-let-def](https://gitlab.com/gopiandcode/gopcaml-mode/-/raw/master/images/gopcaml_move_to_nearest_letdef.gif?inline=false)\n\n- Extract expression into letdef - `C-c C-e`\n\n![extract-expression](https://gitlab.com/gopiandcode/gopcaml-mode/-/raw/master/images/gopcaml_extraction_expressions.gif?inline=false)\n\n\n## Installation\nGopcaml mode is implemented using a mixture of ocaml and elisp.\n\nMake sure your emacs is compiled with dynamic modules support (you may need to build emacs from source with the `--with-modules` option).\n\n*Note:* If you get an error about ELF headers this means that your emacs doesn't support dynamic modules - you'll need to build emacs from source (takes ~5 minutes usually).\n\n- Install the project via opam:\n```sh\nopam install gopcaml-mode\n```\n- install merlin, ocp-indent and tuareg mode\n- load the project in your init.el\n```elisp\n (let ((opam-share (ignore-errors (car (process-lines \"opam\" \"config\" \"var\" \"share\")))))\n\t   (when (and opam-share (file-directory-p opam-share))\n\t     ;; Register Gopcaml mode\n\t     (add-to-list 'load-path (expand-file-name \"emacs/site-lisp\" opam-share))\n         (autoload 'gopcaml-mode \"gopcaml-mode\" nil t nil)\n         (autoload 'tuareg-mode \"tuareg\" nil t nil)\n         (autoload 'merlin-mode \"merlin\" \"Merlin mode\" t)\n\t     ;; Automatically start it in OCaml buffers\n\t     (setq auto-mode-alist\n\t\t   (append '((\"\\\\.ml[ily]?$\" . gopcaml-mode)\n\t\t\t     (\"\\\\.topml$\" . gopcaml-mode))\n\t\t\t   auto-mode-alist))\n\t     ))\n```\n\nEnjoy your ultimate editing experience.\n\n## Extras\n- For some additional features that aren't included in the main release, see the extras folder\n## Development\nIf you want to tinker with this project/extend it/build your own version, see below:\n### Project Structure\nThe core project laid out as follows: \n```\n├── gopcaml.ml\n├── gopcaml_state.ml\n├── ast_zipper.ml\n├── ast_analysis.ml\n├── ast_transformer.ml\n├── gopcaml-mode.el\n├── gopcaml-multiple-cursors.el\n└── gopcaml-smartparens.el\n\n```\nThe purpose of each file is defined as follows (in the order in which you'd probably want to look at them):\n- *gopcaml.ml* \n  - defines the main entrypoint for the module\n  - this is where all the functions bindings to emacs are setup\n- *gopcaml_state.ml*\n  - defines functions to parse and track a copy of the AST for use in other components\n- *ast_zipper.ml* \n  - defines a huet-style scarred zipper for the OCaml AST.\n  - the zipper operates in a lazy fashion - i.e the AST is only\n    expanded into the zipper type when the user expicitly requests it\n- *ast_analysis.ml*\n  - contains functions that perform analysis over the AST (i.e things like finding the free variables in an expression, etc.)\n  - *ast_transformer.ml* should be moved into here at some point\n\n- *gopcaml-mode.el* \n  - main elisp plugin file\n  - takes the functions exported by gopcaml.ml and provides wrappers to make them more robust\n- *gopcaml-\\*.el* \n  - optional features that are loaded in when the required packages are also loaded\n  - allows for better compatibility with other emacs packages (i.e for\n    example, disabling ast-movement when at the start of a parens so\n    smartparens can work )\n\n### Architecture\n- There are two main interesting components to gopcaml-mode\n- *Tracking OCaml Ast* \n  - in order to work, gopcaml mode needs to have a copy of the ocaml\n    ast that (typically*) needs to be up to date with the buffer\n    contents\n  - to achieve this while maintaining a fluid user experience this is achieved\n\tthrough to measures:\n\t  - invalidating on changes:\n\t      - when any change is made to the buffer, the state is invalidiated\n\t\t    (see `gopcaml_state.ml/State/DirtyRegion/update`)\n\t\t  - if the user runs a command that requires the ast and the ast is\n\t\t    invalidated, then we try and rebuild the ast \n\t\t\t(see `gopcaml_state.ml/State/Validated/of_state.ml`).\n\t  - periodic rebuilding:\n\t\t  - when gopcaml-mode is started, an idle timer is setup to\n            periodically check if the AST is out of date and rebuild\n            it when the user doesn't perform any changes for a while\n\t\t  - this just means that we can perform AST reconstruction\n            during idle time, and reduces the cost of moving after\n            changes\n    *sometimes we don't care if the ast is out of date/we're doing analysis\n\t during a time when we know the ast will not be constructable (i.e for \n\t example if implementing a function to check whether we are writing text\n\t inside a letbinding (see `gopcaml_state.ml/inside_let_def`)) - in this\n\t case we can try and retrieve an old copy of the state\n\t (see `gopcaml_state.ml/State/Validated/of_state_immediate`)\n- *Zipper-mode*\n  - zipper-mode is the terminology given to the transient mode that is\n\tentered when the user performs strucutural movement.\n  - when a structural command is run for the first time, we retrieve\n    the ast and create a zipper and store it in a\n    buffer-local-varaible (see `gopcaml_state.ml/build_zipper_enclosing_point`)\n  - all subsequent movement commands retrieve the zipper from this variable and\n    use it to move the emacs cursor and the overlay highlighting the selected item\n  - transformation operations also use the zipper to update the\n    buffer, but have to take extra care to ensure that they also\n    update the state of the zipper to reflect the changes in the ast\n    (as the zipper, unlike the ast isn't periodically updated)\n\t(see `ast_zipper.ml/move_(left|right|up|down)`)\n  - when any command that isn't a structural editing one is pressed,\n    the transient mode ends, and the zipper variable is cleared.\n  - Note: the fact that the zipper is in a separate variable from the\n    ast deliberately means that the zipper may become desynchronized\n    from the ast - for example, if we perform an AST transformation\n    using the zipper, then the original ast will not be up to\n    date. This is mainly just to avoid unnecassary work - rather than\n    writing transformation functions twice for the ast and zipper, we\n    write them once for the zipper (taking sure to ensure that the\n    meta-information stored in the zipper is kept up to date), and\n    then let the automatic rebuilding functionality handle updating\n    the original ast.\n\n### Setting up the development environment\nBeing an emacs plugin, the development environment setup is tailored\nfor emacs.\n\n- Clone the repo from gitlab https://gitlab.com/gopiandcode/gopcaml-mode\n- Build the project with `dune build`\n- in your init.el where gopmacs is loaded, add the following:\n```elisp\n(add-to-list\n\t'command-switch-alist\n\t(cons \"gopdev\"  (lambda (__) nil)))\n\t\n(if (member \"-gopdev\" command-line-args) (setq gopcaml-dev-mode t))\n\n(if (or (not (boundp 'gopcaml-dev-mode)) (not gopcaml-dev-mode))\n   ... ;; run normal gopcaml initialization code (i.e from the install instructions)\n)\n```\n- Now launch emacs passing the flag `-gopdev` and open any file inside\n  the project directory.\n- When prompted press `y` or `!` to setup the development variables for the file.\n- Now this instance of emacs will use your local branch to load\n  gopcaml-mode (It's quite nice developing in this way, as any changes\n  you make will be reflected in your editor, and can quickly be tried\n  out**.\n  \nNote: My typical development setup is to have a command prompt open in\nthe background and execute `dune build \u0026\u0026 emacs -gopdev ./\u003csome-file\u003e.ml`.\nI make some changes, use merlin to ensure there\nare no issues, exit and press up on my terminal to reload the prior\ncommand and press enter.\n\nNote\\*: The reason for the complicated setup is that gopcaml-mode uses\ndynamic modules to call out to ocaml mode from emacs, and dynamic\nmodules can only be loaded into an emacs instance once - thus each\ntime you make a change, you'll need to restart emacs.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkiranandcode%2Fgopcaml-mode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkiranandcode%2Fgopcaml-mode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkiranandcode%2Fgopcaml-mode/lists"}