diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml
index 0845774fb3..9397d78e2b 100644
--- a/.github/workflows/package.yml
+++ b/.github/workflows/package.yml
@@ -103,6 +103,7 @@ jobs:
name: package.${{ matrix.runtime }}
path: |
build/sourcegit-*.AppImage
+ build/sourcegit-*.AppImage.zsync
build/sourcegit_*.deb
build/sourcegit-*.rpm
- name: Delete temp artifacts
diff --git a/.gitignore b/.gitignore
index a0576b9fc6..5ca0482f9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
*.user
*.suo
*.code-workspace
+*.tss
.DS_Store
.DocumentRevisions-V100
@@ -43,3 +44,4 @@ build/*.AppImage
SourceGit.app/
build.command
src/Properties/launchSettings.json
+*.lscache
diff --git a/README.md b/README.md
index e26b52c72c..2ac5ba0723 100644
--- a/README.md
+++ b/README.md
@@ -72,11 +72,11 @@ You can download the latest stable from [Releases](https://github.com/sourcegit-
This software creates a folder, which is platform-dependent, to store user settings, downloaded avatars and crash logs.
-| OS | PATH |
-|---------|-------------------------------------------|
-| Windows | `%APPDATA%\SourceGit` |
-| Linux | `~/.sourcegit` |
-| macOS | `~/Library/Application Support/SourceGit` |
+| OS | PATH |
+|---------|--------------------------------------------------------------------------------------------------|
+| Windows | `%APPDATA%\SourceGit` |
+| Linux | `${XDG_CONFIG_HOME}/SourceGit` (preference.json) `${XDG_CACHE_HOME}/SourceGit` (avatars & logs) |
+| macOS | `~/Library/Application Support/SourceGit` |
> [!TIP]
> * You can open this data storage directory from the main menu `Open Data Storage Directory`.
@@ -163,6 +163,14 @@ For **Linux** users:
sudo apt install sourcegit
```
+* `AOSC OS`: In addition to AMD64 and ARM64, support for LoongArch and RISC-V has been added.
+ ```shell
+ sudo oma install sourcegit
+ ```
+
+> [!NOTE]
+> RISC-V support is untested.
+
* `AppImage` files can be found on [AppImage hub](https://appimage.github.io/SourceGit/), `xdg-open` (`xdg-utils`) must be installed to support open native file manager.
* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) or [git-credential-libsecret](https://pkgs.org/search/?q=git-credential-libsecret) is installed on your Linux.
* Maybe you need to set environment variable `AVALONIA_SCREEN_SCALE_FACTORS`. See https://github.com/AvaloniaUI/Avalonia/wiki/Configuring-X11-per-monitor-DPI.
@@ -186,6 +194,31 @@ For `OpenAI`:
* `Server` must be `https://api.openai.com/v1`
+For `OrcaRouter`:
+
+* `Server` should be `https://api.orcarouter.ai/v1`
+* `API Key` should be an OrcaRouter API key (prefixed with `sk-orca-`)
+* A good starting `Model` is `orcarouter/auto`, which routes each request to the best available model
+
+For `Cheaper Inference`:
+
+* `Server` should be `https://api.cheaperinference.com/v1`
+* `API Key` should be a Cheaper Inference API key (prefixed with `ci_live_`)
+* A good starting `Model` is `gpt-5.4-mini`
+
+For `Opper`:
+
+* `Server` should be `https://api.opper.ai/v3/compat`
+* `API Key` should be an Opper API key, created at https://platform.opper.ai
+* A good starting `Model` is `gpt-5.4-mini`
+
+For `API Route`:
+
+* `Server` should be `https://global.api-route.com/v1`
+* `API Key` should be your API Route API key
+* `Model` can be `gpt-6.1-sol` if it is available to your key; otherwise select an available chat model from the fetched catalog
+* See the [API documentation](https://github.com/DennyHo0917/api-route/blob/main/API.md) for authentication and model discovery
+
For other AI service:
* The `Server` should fill in a URL equivalent to OpenAI's `https://api.openai.com/v1`. For example, when using `Ollama`, it should be `http://localhost:11434/v1` instead of `http://localhost:11434/api/generate`
@@ -207,7 +240,7 @@ This app supports open repository in external tools listed in the table below.
> [!NOTE]
> This app will try to find those tools based on some pre-defined or expected locations automatically. If you are using one portable version of these tools, it will not be detected by this app.
-> To solve this problem you can add a file named `external_editors.json` in app data storage directory and provide the path directly.
+> To solve this problem you can add a file named `external_editors.json` in app data storage directory (config dir for Linux) and provide the path directly.
> User can also exclude some editors by using `external_editors.json`.
The format of `external_editors.json`:
@@ -251,6 +284,9 @@ You can define your own conventional commit types (per-repository) by following
Everyone is welcome to submit a PR. Please make sure your PR is based on the latest `develop` branch and the target branch of PR is `develop`.
+This project has a submodule in `depends/AvaloniaEdit` which is a custom fork of [Official AvaloniaEdit](https://github.com/AvaloniaUI/AvaloniaEdit).
+Please make sure it is initialized - enable `--recurse-submodules` option while cloning or run `git submodule update --init` after cloned.
+
In short, here are the commands to get started once [.NET tools are installed](https://dotnet.microsoft.com/en-us/download):
```sh
diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md
index f5e0b54005..4be26e4a12 100644
--- a/THIRD-PARTY-LICENSES.md
+++ b/THIRD-PARTY-LICENSES.md
@@ -7,25 +7,25 @@ The project uses the following third-party libraries or assets
### AvaloniaUI
- **Source**: https://github.com/AvaloniaUI/Avalonia
-- **Version**: 11.3.13
+- **Version**: 12.0.5
- **License**: MIT License
- **License Link**: https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md
+### Avalonia.Controls.DataGrid
+
+- **Source**: https://github.com/AvaloniaUI/Avalonia.Controls.DataGrid
+- **Version**: 12.0.1
+- **License**: MIT License
+- **License Link**: https://github.com/AvaloniaUI/Avalonia.Controls.DataGrid/blob/master/licence.md
+
### AvaloniaEdit
- **Official Source**: https://github.com/AvaloniaUI/AvaloniaEdit
- **Fork (Modified)**: https://github.com/love-linger/AvaloniaEdit
-- **Version**: 11.4.1
+- **Version**: Based on 12.0.x
- **License**: MIT License
- **License Link**: https://github.com/AvaloniaUI/AvaloniaEdit/blob/master/LICENSE
-### LiveChartsCore.SkiaSharpView.Avalonia
-
-- **Source**: https://github.com/beto-rodriguez/LiveCharts2
-- **Version**: 2.0.0
-- **License**: MIT License
-- **License Link**: https://github.com/beto-rodriguez/LiveCharts2/blob/master/LICENSE
-
### TextMateSharp
- **Source**: https://github.com/danipen/TextMateSharp
@@ -61,6 +61,12 @@ The project uses the following third-party libraries or assets
- **License**: MIT License
- **License Link**: https://github.com/nickbabcock/Pfim/blob/master/LICENSE.txt
+### StbImageSharp
+
+- **Source**: https://github.com/StbSharp/StbImageSharp
+- **Version**: 2.30.15
+- **License**: Public Domain
+
## Fonts
### JetBrainsMono
@@ -106,3 +112,38 @@ The project uses the following third-party libraries or assets
- **Commit**: 68d98dc57f8486c2946ae28dc86bf8e91d45da4d
- **License**: MIT License
- **License Link**: https://github.com/vuejs/language-tools/blob/68d98dc57f8486c2946ae28dc86bf8e91d45da4d/LICENSE
+
+### OCaml
+
+- **Source**: https://github.com/shentao/vue-multiselect/blob/master/docs/shiki/languages/ocaml.tmLanguage.json
+- **Commit**: 8092917041f0ae02cbe4c6982deddc931c6adecd
+- **License**: MIT License
+- **License Link**: https://github.com/shentao/vue-multiselect/blob/master/LICENSE
+
+### OCamllex
+
+- **Source**: https://github.com/textmate/ocaml.tmbundle/blob/master/Syntaxes/OCamllex.tmLanguage
+- **Commit**: 1eff26848b39368414cb4214183a2cba22f12d0e
+- **License**: Permission to copy, use, modify, sell and distribute this software is granted. This software is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.
+- **License Link**: https://github.com/textmate/ocaml.tmbundle/tree/master#license
+
+### OCamlyacc
+
+- **Source**: https://github.com/textmate/ocaml.tmbundle/blob/master/Syntaxes/OCamlyacc.tmLanguage
+- **Commit**: 08a892aef6a4b285b1902d9ed1427653fce53e31
+- **License**: Permission to copy, use, modify, sell and distribute this software is granted. This software is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.
+- **License Link**: https://github.com/textmate/ocaml.tmbundle/tree/master#license
+
+### Erlang
+
+- **Source**: https://github.com/shentao/vue-multiselect/blob/master/docs/shiki/languages/erlang.tmLanguage.json
+- **Commit**: 8092917041f0ae02cbe4c6982deddc931c6adecd
+- **License**: MIT License
+- **License Link**: https://github.com/shentao/vue-multiselect/blob/master/LICENSE
+
+### Swift
+
+- **Source**: https://github.com/jtbandes/swift-tmlanguage/blob/main/Swift.tmLanguage.json
+- **Commit**: abda279b567a0a7f35f81642c1b830f437f718d9
+- **License**: MIT License
+- **License Link**: https://github.com/jtbandes/swift-tmlanguage/blob/main/LICENSE.md
\ No newline at end of file
diff --git a/TRANSLATION.md b/TRANSLATION.md
index 1263c1103d..0e33d6f6a4 100644
--- a/TRANSLATION.md
+++ b/TRANSLATION.md
@@ -6,413 +6,551 @@ This document shows the translation status of each locale file in the repository
### 
-### 
+### 
Missing keys in de_DE.axaml
-- Text.About.GitSourceRevision
-- Text.AIAssistant.Use
-- Text.App.HideOthers
-- Text.Apply.3Way
-- Text.BranchCM.CompareWithSpecial
-- Text.CheckoutBranchFromStash
-- Text.CheckoutBranchFromStash.Branch
-- Text.CheckoutBranchFromStash.Stash
-- Text.Clone.Bookmark
-- Text.Clone.Group
-- Text.CommandPalette.Branches
-- Text.CommandPalette.BranchesAndTags
-- Text.CommandPalette.RepositoryActions
-- Text.CommandPalette.RevisionFiles
-- Text.CommitDetail.CollapseToBottom
-- Text.CommitMessageTextBox.Column
-- Text.Compare.Changes
-- Text.Compare.Commits
-- Text.Compare.Commits.LeftOnly
-- Text.Compare.Commits.RightOnly
-- Text.Compare.Commits.Tips
-- Text.ConfigureCustomActionControls.StringFormatter
-- Text.ConfigureCustomActionControls.StringFormatter.Tip
-- Text.ConfigureCustomActionControls.UseFriendlyName
-- Text.ConfirmEmptyCommit.StageSelectedThenCommit
-- Text.Diff.Submodule.UncommittedChanges
-- Text.Discard.IncludeModified
-- Text.GotoRevisionSelector
-- Text.Histories.HighlightsInGraph
-- Text.Histories.HighlightsInGraph.All
-- Text.Histories.HighlightsInGraph.CurrentBranchOnly
-- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
-- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
-- Text.HistoriesDetailsStandalone
-- Text.HistoriesDetailsStandalone.CommitDetail
-- Text.HistoriesDetailsStandalone.RevisionCompare
-- Text.Hotkeys.Global.OpenLocalRepository
-- Text.Hotkeys.Repo.CreateBranch
-- Text.Hotkeys.Repo.GoToChild
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
-- Text.Init.CommandTip
-- Text.Init.ErrorMessageTip
-- Text.InteractiveRebase.NoVerify
-- Text.OpenLocalRepository
-- Text.OpenLocalRepository.Bookmark
-- Text.OpenLocalRepository.Group
-- Text.OpenLocalRepository.Path
-- Text.Preferences.AI.AdditionalPrompt
-- Text.Preferences.AI.Model
-- Text.Preferences.AI.Model.AutoFetchAvailableModels
-- Text.Preferences.General.ShowRelativeTimeInGraph
-- Text.Preferences.General.Use24Hours
-- Text.Preferences.Git.UseStashAndReapplyByDefault
-- Text.Rebase.NoVerify
-- Text.RemoteCM.EnableAutoFetch
-- Text.StashCM.ApplyFileChanges
-- Text.StashCM.Branch
-- Text.SubmoduleRevisionCompare
-- Text.SubmoduleRevisionCompare.OpenDetails
-- Text.Worktree.Branch
-- Text.Worktree.Head
-- Text.Worktree.Path
-
-
-
-### 
-
-
-Missing keys in es_ES.axaml
-
-- Text.CommitDetail.CollapseToBottom
-- Text.Compare.Changes
-- Text.Compare.Commits
-- Text.Compare.Commits.LeftOnly
-- Text.Compare.Commits.RightOnly
-- Text.Compare.Commits.Tips
-- Text.Histories.HighlightsInGraph
-- Text.Histories.HighlightsInGraph.All
-- Text.Histories.HighlightsInGraph.CurrentBranchOnly
-- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
-- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
-- Text.HistoriesDetailsStandalone
-- Text.HistoriesDetailsStandalone.CommitDetail
-- Text.HistoriesDetailsStandalone.RevisionCompare
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
-- Text.InteractiveRebase.NoVerify
-- Text.Preferences.AI.Model
-- Text.Preferences.AI.Model.AutoFetchAvailableModels
-- Text.Preferences.General.ShowRelativeTimeInGraph
-- Text.Rebase.NoVerify
-
-
-
-### 
-
-
-Missing keys in fr_FR.axaml
-
-- Text.About.GitSourceRevision
-- Text.About.ReleaseDate
-- Text.AIAssistant.Use
-- Text.App.HideOthers
-- Text.Apply.3Way
-- Text.Blame.IgnoreWhitespace
-- Text.BranchCM.CompareTwo
-- Text.BranchCM.CompareWith
-- Text.BranchCM.CompareWithHead
-- Text.BranchCM.CompareWithSpecial
-- Text.BranchCM.EditDescription
-- Text.ChangeCM.Merge
-- Text.ChangeCM.MergeExternal
-- Text.ChangeCM.ResetFileTo
-- Text.Checkout.WarnUpdatingSubmodules
-- Text.CheckoutBranchFromStash
-- Text.CheckoutBranchFromStash.Branch
-- Text.CheckoutBranchFromStash.Stash
-- Text.Clone.Bookmark
-- Text.Clone.Group
-- Text.CommandPalette.Branches
-- Text.CommandPalette.BranchesAndTags
-- Text.CommandPalette.RepositoryActions
-- Text.CommandPalette.RevisionFiles
-- Text.CommitDetail.CollapseToBottom
-- Text.CommitMessageTextBox.Column
-- Text.CommitMessageTextBox.Placeholder
-- Text.Compare.Changes
-- Text.Compare.Commits
-- Text.Compare.Commits.LeftOnly
-- Text.Compare.Commits.RightOnly
-- Text.Compare.Commits.Tips
-- Text.Configure.Git.AskBeforeAutoUpdatingSubmodules
-- Text.ConfigureCustomActionControls.StringFormatter
-- Text.ConfigureCustomActionControls.StringFormatter.Tip
-- Text.ConfigureCustomActionControls.UseFriendlyName
-- Text.ConfirmEmptyCommit.StageSelectedThenCommit
-- Text.Diff.Submodule.UncommittedChanges
-- Text.Discard.IncludeModified
-- Text.EditBranchDescription
-- Text.EditBranchDescription.Target
-- Text.FileCM.CustomAction
-- Text.GotoRevisionSelector
-- Text.Histories.Header.DateTime
-- Text.Histories.HighlightsInGraph
-- Text.Histories.HighlightsInGraph.All
-- Text.Histories.HighlightsInGraph.CurrentBranchOnly
-- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
-- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
-- Text.Histories.ShowColumns
-- Text.HistoriesDetailsStandalone
-- Text.HistoriesDetailsStandalone.CommitDetail
-- Text.HistoriesDetailsStandalone.RevisionCompare
-- Text.Hotkeys.Global.OpenLocalRepository
-- Text.Hotkeys.Global.ShowWorkspaceDropdownMenu
-- Text.Hotkeys.Global.Zoom
-- Text.Hotkeys.Repo.CreateBranch
-- Text.Hotkeys.Repo.GoToChild
-- Text.Hotkeys.Repo.GoToParent
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
-- Text.Init.CommandTip
-- Text.Init.ErrorMessageTip
-- Text.InteractiveRebase.NoVerify
-- Text.MergeConflictEditor.AcceptBoth.MineFirst
-- Text.MergeConflictEditor.AcceptBoth.TheirsFirst
-- Text.MergeConflictEditor.UseBoth
-- Text.MergeConflictEditor.AllResolved
-- Text.MergeConflictEditor.ConflictsRemaining
-- Text.MergeConflictEditor.Mine
-- Text.MergeConflictEditor.NextConflict
-- Text.MergeConflictEditor.PrevConflict
-- Text.MergeConflictEditor.Result
-- Text.MergeConflictEditor.SaveAndStage
-- Text.MergeConflictEditor.Theirs
-- Text.MergeConflictEditor.Title
-- Text.MergeConflictEditor.UnsavedChanges
-- Text.MergeConflictEditor.UseMine
-- Text.MergeConflictEditor.UseTheirs
-- Text.MergeConflictEditor.Undo
-- Text.No
-- Text.OpenFile
-- Text.OpenLocalRepository
-- Text.OpenLocalRepository.Bookmark
-- Text.OpenLocalRepository.Group
-- Text.OpenLocalRepository.Path
-- Text.PageTabBar.Tab.MoveToWorkspace
-- Text.PageTabBar.Tab.Refresh
-- Text.Preferences.AI.AdditionalPrompt
-- Text.Preferences.AI.Model
-- Text.Preferences.AI.Model.AutoFetchAvailableModels
-- Text.Preferences.DiffMerge.DiffArgs
-- Text.Preferences.DiffMerge.DiffArgs.Tip
-- Text.Preferences.DiffMerge.MergeArgs
-- Text.Preferences.DiffMerge.MergeArgs.Tip
-- Text.Preferences.General.ShowRelativeTimeInGraph
-- Text.Preferences.General.Use24Hours
-- Text.Preferences.Git.UseStashAndReapplyByDefault
-- Text.Preferences.Shell.Args
-- Text.Preferences.Shell.Args.Tip
-- Text.Rebase.NoVerify
-- Text.RemoteCM.EnableAutoFetch
-- Text.Repository.OpenAsFolder
-- Text.Repository.Resolve
-- Text.SelfUpdate.CurrentVersion
-- Text.SelfUpdate.ReleaseDate
-- Text.StashCM.ApplyFileChanges
-- Text.StashCM.Branch
-- Text.SubmoduleRevisionCompare
-- Text.SubmoduleRevisionCompare.OpenDetails
-- Text.TagCM.CompareTwo
-- Text.TagCM.CompareWith
-- Text.TagCM.CompareWithHead
-- Text.WorkingCopy.Conflicts.Merge
-- Text.WorkingCopy.Conflicts.MergeExternal
-- Text.Worktree.Branch
-- Text.Worktree.Head
-- Text.Worktree.Path
-- Text.Yes
-
-
-
-### 
-
-
-Missing keys in id_ID.axaml
-
-- Text.About.GitSourceRevision
-- Text.About.ReleaseDate
-- Text.About.ReleaseNotes
-- Text.AIAssistant.Use
-- Text.App.HideOthers
-- Text.Apply.3Way
-- Text.Blame.BlameOnPreviousRevision
-- Text.Blame.IgnoreWhitespace
-- Text.BranchCM.CompareTwo
-- Text.BranchCM.CompareWith
-- Text.BranchCM.CompareWithHead
-- Text.BranchCM.CompareWithSpecial
-- Text.BranchCM.CreatePR
-- Text.BranchCM.CreatePRForUpstream
-- Text.BranchCM.EditDescription
-- Text.ChangeCM.Merge
-- Text.ChangeCM.MergeExternal
-- Text.ChangeCM.ResetFileTo
-- Text.Checkout.WarnUpdatingSubmodules
-- Text.CheckoutBranchFromStash
-- Text.CheckoutBranchFromStash.Branch
-- Text.CheckoutBranchFromStash.Stash
-- Text.Clone.Bookmark
-- Text.Clone.Group
-- Text.CommandPalette.Branches
-- Text.CommandPalette.BranchesAndTags
-- Text.CommandPalette.RepositoryActions
-- Text.CommandPalette.RevisionFiles
-- Text.CommitDetail.CollapseToBottom
-- Text.CommitMessageTextBox.Column
-- Text.CommitMessageTextBox.Placeholder
-- Text.Compare.Changes
-- Text.Compare.Commits
-- Text.Compare.Commits.LeftOnly
-- Text.Compare.Commits.RightOnly
-- Text.Compare.Commits.Tips
-- Text.Configure.CommitMessageTemplate.BuiltinVars
-- Text.Configure.Git.AskBeforeAutoUpdatingSubmodules
-- Text.Configure.Git.ConventionalTypesOverride
-- Text.ConfigureCustomActionControls.StringFormatter
-- Text.ConfigureCustomActionControls.StringFormatter.Tip
-- Text.ConfigureCustomActionControls.StringValue.Tip
-- Text.ConfigureCustomActionControls.UseFriendlyName
-- Text.ConfirmEmptyCommit.StageSelectedThenCommit
-- Text.DealWithLocalChanges.DoNothing
-- Text.Diff.Submodule.UncommittedChanges
-- Text.Discard.IncludeModified
-- Text.EditBranchDescription
-- Text.EditBranchDescription.Target
-- Text.FileCM.CustomAction
-- Text.GitLFS.Locks.UnlockAllMyLocks
-- Text.GitLFS.Locks.UnlockAllMyLocks.Confirm
-- Text.GotoRevisionSelector
-- Text.Histories.Header.DateTime
-- Text.Histories.HighlightsInGraph
-- Text.Histories.HighlightsInGraph.All
-- Text.Histories.HighlightsInGraph.CurrentBranchOnly
-- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
-- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
-- Text.Histories.ShowColumns
-- Text.HistoriesDetailsStandalone
-- Text.HistoriesDetailsStandalone.CommitDetail
-- Text.HistoriesDetailsStandalone.RevisionCompare
-- Text.Hotkeys.Global.OpenLocalRepository
-- Text.Hotkeys.Global.ShowWorkspaceDropdownMenu
-- Text.Hotkeys.Global.Zoom
-- Text.Hotkeys.Repo.CreateBranch
-- Text.Hotkeys.Repo.GoToChild
-- Text.Hotkeys.Repo.GoToParent
-- Text.Hotkeys.Repo.OpenCommandPalette
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
-- Text.Init.CommandTip
-- Text.Init.ErrorMessageTip
-- Text.InteractiveRebase.NoVerify
-- Text.Launcher.Commands
-- Text.Launcher.OpenRepository
-- Text.MergeConflictEditor.AcceptBoth.MineFirst
-- Text.MergeConflictEditor.AcceptBoth.TheirsFirst
-- Text.MergeConflictEditor.UseBoth
-- Text.MergeConflictEditor.AllResolved
-- Text.MergeConflictEditor.ConflictsRemaining
-- Text.MergeConflictEditor.Mine
-- Text.MergeConflictEditor.NextConflict
-- Text.MergeConflictEditor.PrevConflict
-- Text.MergeConflictEditor.Result
-- Text.MergeConflictEditor.SaveAndStage
-- Text.MergeConflictEditor.Theirs
-- Text.MergeConflictEditor.Title
-- Text.MergeConflictEditor.UnsavedChanges
-- Text.MergeConflictEditor.UseMine
-- Text.MergeConflictEditor.UseTheirs
-- Text.MergeConflictEditor.Undo
-- Text.No
-- Text.Open
-- Text.Open.SystemDefaultEditor
-- Text.OpenFile
-- Text.OpenLocalRepository
-- Text.OpenLocalRepository.Bookmark
-- Text.OpenLocalRepository.Group
-- Text.OpenLocalRepository.Path
-- Text.PageTabBar.Tab.MoveToWorkspace
-- Text.PageTabBar.Tab.Refresh
-- Text.Preferences.AI.AdditionalPrompt
-- Text.Preferences.AI.Model
-- Text.Preferences.AI.Model.AutoFetchAvailableModels
-- Text.Preferences.DiffMerge.DiffArgs
-- Text.Preferences.DiffMerge.DiffArgs.Tip
-- Text.Preferences.DiffMerge.MergeArgs
-- Text.Preferences.DiffMerge.MergeArgs.Tip
-- Text.Preferences.General.ShowRelativeTimeInGraph
-- Text.Preferences.General.Use24Hours
-- Text.Preferences.Git.UseStashAndReapplyByDefault
-- Text.Preferences.Shell.Args
-- Text.Preferences.Shell.Args.Tip
-- Text.PushToNewBranch
-- Text.PushToNewBranch.Title
-- Text.Rebase.NoVerify
-- Text.RemoteCM.EnableAutoFetch
-- Text.Repository.OpenAsFolder
-- Text.Repository.Resolve
-- Text.SelfUpdate.CurrentVersion
-- Text.SelfUpdate.ReleaseDate
-- Text.StashCM.ApplyFileChanges
-- Text.StashCM.Branch
-- Text.SubmoduleRevisionCompare
-- Text.SubmoduleRevisionCompare.OpenDetails
-- Text.TagCM.CompareTwo
-- Text.TagCM.CompareWith
-- Text.TagCM.CompareWithHead
-- Text.WorkingCopy.Conflicts.Merge
-- Text.WorkingCopy.Conflicts.MergeExternal
-- Text.Worktree.Branch
-- Text.Worktree.Head
-- Text.Worktree.Path
-- Text.Yes
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
+- Text.BranchTree.PushURL
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
+- Text.CommitDetail.Files.Tips
+- Text.Dashboard
+- Text.Diff.Binary.ViewContentOfNew
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
+- Text.Histories.AdvancedOptions
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
+- Text.Preferences.AI.ExtraHeaders
+- Text.Preferences.AI.ReasoningEffortLevel
+- Text.Remote.ResetPushURL
+- Text.RemoteCM.SetPushURL
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.WorkingCopy.CollapseSidebar
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+
+
+
+### 
+
+
+Missing keys in el_GR.axaml
+
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
+- Text.BranchTree.PushURL
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
+- Text.CommitDetail.Files.Tips
+- Text.Dashboard
+- Text.Diff.Binary.ViewContentOfNew
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
+- Text.Histories.AdvancedOptions
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
+- Text.Launcher.NewVersion
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
+- Text.Preferences.AI.ExtraHeaders
+- Text.Preferences.AI.ReasoningEffortLevel
+- Text.Remote.ResetPushURL
+- Text.RemoteCM.SetPushURL
+- Text.Repository.FilterCommits.Collapse
+- Text.Repository.FilterCommits.Expand
+- Text.Repository.FilterCommits.Summary
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.WorkingCopy.CollapseSidebar
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+- Text.WorkingCopy.FilterChanges
+
+
+
+### 
+
+### 
+
+
+Missing keys in fr_FR.axaml
+
+- Text.Apply.Source
+- Text.Apply.Source.File
+- Text.Apply.Source.Clipboard
+- Text.Bisect.WaitingForCheckoutAnother
+- Text.Bisect.WaitingForFirstBad
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
+- Text.BranchTree.PushURL
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
+- Text.CheckoutDetached
+- Text.CheckoutDetached.Target
+- Text.CheckoutDetached.Warning
+- Text.CommitCM.CopyAuthorTime
+- Text.CommitCM.CopyCommitterTime
+- Text.CommitDetail.Files.Tips
+- Text.Configure.Git.EnableRecursiveWhenAutoUpdatingSubmodules
+- Text.CopyAsPatch
+- Text.Dashboard
+- Text.DeleteBranch.Force
+- Text.Diff.Binary.ViewContentOfNew
+- Text.Diff.EmptyFile
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
+- Text.FileModeChange
+- Text.FileModeChange.Deleted
+- Text.FileModeChange.Directory
+- Text.FileModeChange.Executable
+- Text.FileModeChange.New
+- Text.FileModeChange.Normal
+- Text.FileModeChange.Submodule
+- Text.FileModeChange.Symlink
+- Text.FileModeChange.Unknown
+- Text.GitFlow.Finish
+- Text.GitFlow.FinishWithRebase
+- Text.GitFlow.StartAt
+- Text.GitFlow.StartName
+- Text.Histories.AdvancedOptions
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
+- Text.Launcher.NewVersion
+- Text.Merge.Test
+- Text.Merge.Test.NoConflicts
+- Text.Merge.Test.UnknownError
+- Text.Merge.Test.WillCauseConflicts
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
+- Text.Preferences.AI.ExtraHeaders
+- Text.Preferences.AI.ReasoningEffortLevel
+- Text.Preferences.General.UseCompactBranchNames
+- Text.Rebase.Test
+- Text.Rebase.Test.OK
+- Text.Rebase.Test.UnknownError
+- Text.Rebase.Test.WillCauseConflicts
+- Text.Remote.ResetPushURL
+- Text.RemoteCM.SetPushURL
+- Text.Repository.FilterCommits.Collapse
+- Text.Repository.FilterCommits.Expand
+- Text.Repository.FilterCommits.Summary
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
+- Text.TagCM.Checkout
+- Text.TagCM.Merge
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.UpdateSubmodules.Recursive
+- Text.WorkingCopy.AddToGitIgnore.UntrackedInSameFolder
+- Text.WorkingCopy.CollapseSidebar
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+- Text.WorkingCopy.FilterChanges
+
+
+
+### 
+
+
+Missing keys in he_IL.axaml
+
+- Text.Apply.Source
+- Text.Apply.Source.File
+- Text.Apply.Source.Clipboard
+- Text.Bisect.WaitingForCheckoutAnother
+- Text.Bisect.WaitingForFirstBad
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
+- Text.BranchTree.PushURL
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
+- Text.CheckoutDetached
+- Text.CheckoutDetached.Target
+- Text.CheckoutDetached.Warning
+- Text.CommitCM.CopyAuthorTime
+- Text.CommitCM.CopyCommitterTime
+- Text.CommitDetail.Files.Tips
+- Text.Configure.Git.EnableRecursiveWhenAutoUpdatingSubmodules
+- Text.CopyAsPatch
+- Text.Dashboard
+- Text.DeleteBranch.Force
+- Text.Diff.Binary.ViewContentOfNew
+- Text.Diff.EmptyFile
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
+- Text.FileModeChange
+- Text.FileModeChange.Deleted
+- Text.FileModeChange.Directory
+- Text.FileModeChange.Executable
+- Text.FileModeChange.New
+- Text.FileModeChange.Normal
+- Text.FileModeChange.Submodule
+- Text.FileModeChange.Symlink
+- Text.FileModeChange.Unknown
+- Text.GitFlow.Finish
+- Text.GitFlow.FinishWithRebase
+- Text.GitFlow.StartAt
+- Text.GitFlow.StartName
+- Text.Histories.AdvancedOptions
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
+- Text.Launcher.NewVersion
+- Text.Merge.Test
+- Text.Merge.Test.NoConflicts
+- Text.Merge.Test.UnknownError
+- Text.Merge.Test.WillCauseConflicts
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
+- Text.Preferences.AI.ExtraHeaders
+- Text.Preferences.AI.ReasoningEffortLevel
+- Text.Preferences.General.UseCompactBranchNames
+- Text.Rebase.Test
+- Text.Rebase.Test.OK
+- Text.Rebase.Test.UnknownError
+- Text.Rebase.Test.WillCauseConflicts
+- Text.Remote.ResetPushURL
+- Text.RemoteCM.SetPushURL
+- Text.Repository.FilterCommits.Collapse
+- Text.Repository.FilterCommits.Expand
+- Text.Repository.FilterCommits.Summary
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
+- Text.TagCM.Checkout
+- Text.TagCM.Merge
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.UpdateSubmodules.Recursive
+- Text.WorkingCopy.AddToGitIgnore.UntrackedInSameFolder
+- Text.WorkingCopy.CollapseSidebar
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+- Text.WorkingCopy.FilterChanges
+
+
+
+### 
+
+
+Missing keys in id_ID.axaml
+
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
+- Text.BranchTree.PushURL
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
+- Text.CommitDetail.Files.Tips
+- Text.Dashboard
+- Text.Diff.Binary.ViewContentOfNew
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
+- Text.Histories.AdvancedOptions
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
+- Text.Preferences.AI.ReasoningEffortLevel
+- Text.Remote.ResetPushURL
+- Text.RemoteCM.SetPushURL
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.WorkingCopy.CollapseSidebar
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+- Text.WorkingCopy.FilterChanges
-### 
+### 
Missing keys in it_IT.axaml
-- Text.About.GitSourceRevision
- Text.AIAssistant.Use
- Text.App.HideOthers
- Text.Apply.3Way
+- Text.Apply.Source
+- Text.Apply.Source.File
+- Text.Apply.Source.Clipboard
+- Text.Bisect.WaitingForCheckoutAnother
+- Text.Bisect.WaitingForFirstBad
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
- Text.BranchCM.CompareWithSpecial
+- Text.BranchTree.PushURL
- Text.ChangeCM.ResetFileTo
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
+- Text.CheckoutDetached
+- Text.CheckoutDetached.Target
+- Text.CheckoutDetached.Warning
- Text.Clone.Bookmark
- Text.Clone.Group
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
- Text.CommandPalette.RepositoryActions
- Text.CommandPalette.RevisionFiles
+- Text.CommitCM.CopyAuthorTime
+- Text.CommitCM.CopyCommitterTime
- Text.CommitDetail.CollapseToBottom
-- Text.CommitMessageTextBox.Column
+- Text.CommitDetail.Files.Tips
- Text.Compare.Changes
- Text.Compare.Commits
- Text.Compare.Commits.LeftOnly
- Text.Compare.Commits.RightOnly
- Text.Compare.Commits.Tips
+- Text.Configure.Git.EnableRecursiveWhenAutoUpdatingSubmodules
- Text.ConfigureCustomActionControls.StringFormatter
- Text.ConfigureCustomActionControls.StringFormatter.Tip
- Text.ConfigureCustomActionControls.UseFriendlyName
- Text.ConfirmEmptyCommit.StageSelectedThenCommit
+- Text.CopyAsPatch
+- Text.Dashboard
+- Text.DeleteBranch.Force
+- Text.Diff.Binary.ViewContentOfNew
+- Text.Diff.EmptyFile
- Text.Diff.Submodule.UncommittedChanges
- Text.Discard.IncludeModified
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
+- Text.FileModeChange
+- Text.FileModeChange.Deleted
+- Text.FileModeChange.Directory
+- Text.FileModeChange.Executable
+- Text.FileModeChange.New
+- Text.FileModeChange.Normal
+- Text.FileModeChange.Submodule
+- Text.FileModeChange.Symlink
+- Text.FileModeChange.Unknown
+- Text.GitFlow.Finish
+- Text.GitFlow.FinishWithRebase
+- Text.GitFlow.StartAt
+- Text.GitFlow.StartName
- Text.GotoRevisionSelector
-- Text.Histories.Header.DateTime
+- Text.Histories.AdvancedOptions
- Text.Histories.HighlightsInGraph
- Text.Histories.HighlightsInGraph.All
- Text.Histories.HighlightsInGraph.CurrentBranchOnly
- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
- Text.Histories.ShowColumns
- Text.HistoriesDetailsStandalone
- Text.HistoriesDetailsStandalone.CommitDetail
@@ -421,259 +559,251 @@ This document shows the translation status of each locale file in the repository
- Text.Hotkeys.Repo.CreateBranch
- Text.Hotkeys.Repo.GoToChild
- Text.Hotkeys.Repo.GoToParent
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
- Text.Init.CommandTip
- Text.Init.ErrorMessageTip
- Text.InteractiveRebase.NoVerify
+- Text.Launcher.NewVersion
+- Text.Merge.Test
+- Text.Merge.Test.NoConflicts
+- Text.Merge.Test.UnknownError
+- Text.Merge.Test.WillCauseConflicts
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
- Text.OpenLocalRepository
- Text.OpenLocalRepository.Bookmark
- Text.OpenLocalRepository.Group
- Text.OpenLocalRepository.Path
- Text.Preferences.AI.AdditionalPrompt
+- Text.Preferences.AI.ExtraHeaders
- Text.Preferences.AI.Model
- Text.Preferences.AI.Model.AutoFetchAvailableModels
+- Text.Preferences.AI.ReasoningEffortLevel
- Text.Preferences.General.ShowRelativeTimeInGraph
- Text.Preferences.General.Use24Hours
+- Text.Preferences.General.UseCompactBranchNames
- Text.Preferences.Git.UseStashAndReapplyByDefault
- Text.Rebase.NoVerify
+- Text.Rebase.Test
+- Text.Rebase.Test.OK
+- Text.Rebase.Test.UnknownError
+- Text.Rebase.Test.WillCauseConflicts
+- Text.Remote.ResetPushURL
- Text.RemoteCM.EnableAutoFetch
+- Text.RemoteCM.SetPushURL
+- Text.Repository.FilterCommits.Collapse
+- Text.Repository.FilterCommits.Expand
+- Text.Repository.FilterCommits.Summary
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
- Text.SelfUpdate.CurrentVersion
- Text.SelfUpdate.ReleaseDate
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
- Text.SubmoduleRevisionCompare
- Text.SubmoduleRevisionCompare.OpenDetails
+- Text.TagCM.Checkout
+- Text.TagCM.Merge
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.UpdateSubmodules.Recursive
+- Text.WorkingCopy.AddToGitIgnore.UntrackedInSameFolder
+- Text.WorkingCopy.CollapseSidebar
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+- Text.WorkingCopy.FilterChanges
- Text.Worktree.Branch
- Text.Worktree.Head
- Text.Worktree.Path
-### 
+### 
Missing keys in ja_JP.axaml
-- Text.About.GitSourceRevision
-- Text.AIAssistant.Use
-- Text.App.HideOthers
-- Text.Apply.3Way
-- Text.BranchCM.CompareWithSpecial
-- Text.CheckoutBranchFromStash
-- Text.CheckoutBranchFromStash.Branch
-- Text.CheckoutBranchFromStash.Stash
-- Text.Clone.Bookmark
-- Text.Clone.Group
-- Text.CommandPalette.Branches
-- Text.CommandPalette.BranchesAndTags
-- Text.CommandPalette.RepositoryActions
-- Text.CommandPalette.RevisionFiles
-- Text.CommitDetail.CollapseToBottom
-- Text.Compare.Changes
-- Text.Compare.Commits
-- Text.Compare.Commits.LeftOnly
-- Text.Compare.Commits.RightOnly
-- Text.Compare.Commits.Tips
-- Text.ConfigureCustomActionControls.StringFormatter
-- Text.ConfigureCustomActionControls.StringFormatter.Tip
-- Text.ConfigureCustomActionControls.UseFriendlyName
-- Text.ConfirmEmptyCommit.StageSelectedThenCommit
-- Text.DealWithLocalChanges.DoNothing
-- Text.Diff.Submodule.UncommittedChanges
-- Text.Discard.IncludeModified
-- Text.Histories.HighlightsInGraph
-- Text.Histories.HighlightsInGraph.All
-- Text.Histories.HighlightsInGraph.CurrentBranchOnly
-- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
-- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
-- Text.HistoriesDetailsStandalone
-- Text.HistoriesDetailsStandalone.CommitDetail
-- Text.HistoriesDetailsStandalone.RevisionCompare
-- Text.Hotkeys.Global.OpenLocalRepository
-- Text.Hotkeys.Repo.CreateBranch
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
-- Text.Init.CommandTip
-- Text.Init.ErrorMessageTip
-- Text.InteractiveRebase.NoVerify
-- Text.OpenLocalRepository
-- Text.OpenLocalRepository.Bookmark
-- Text.OpenLocalRepository.Group
-- Text.OpenLocalRepository.Path
-- Text.Preferences.AI.AdditionalPrompt
-- Text.Preferences.AI.Model
-- Text.Preferences.AI.Model.AutoFetchAvailableModels
-- Text.Preferences.General.ShowRelativeTimeInGraph
-- Text.Preferences.General.Use24Hours
-- Text.Preferences.Git.UseStashAndReapplyByDefault
-- Text.Rebase.NoVerify
-- Text.RemoteCM.EnableAutoFetch
-- Text.StashCM.Branch
-- Text.SubmoduleRevisionCompare
-- Text.SubmoduleRevisionCompare.OpenDetails
-- Text.Worktree.Branch
-- Text.Worktree.Head
-- Text.Worktree.Path
+- Text.BranchCM.CheckoutAsWorktree
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
+- Text.CommitDetail.Files.Tips
+- Text.Statistics.Commits
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.WorkingCopy.CollapseSidebar
+- Text.WorkingCopy.ExpandSidebar
-### 
+### 
Missing keys in ko_KR.axaml
-- Text.About.GitSourceRevision
-- Text.About.ReleaseDate
-- Text.AIAssistant.Use
-- Text.App.HideOthers
-- Text.Apply.3Way
-- Text.Blame.BlameOnPreviousRevision
-- Text.Blame.IgnoreWhitespace
-- Text.Blame.TypeNotSupported
-- Text.BranchCM.CompareTwo
-- Text.BranchCM.CompareWith
-- Text.BranchCM.CompareWithHead
-- Text.BranchCM.CompareWithSpecial
-- Text.BranchCM.CreatePR
-- Text.BranchCM.CreatePRForUpstream
-- Text.BranchCM.EditDescription
-- Text.ChangeCM.Merge
-- Text.ChangeCM.MergeExternal
-- Text.ChangeCM.ResetFileTo
-- Text.Checkout.WarnUpdatingSubmodules
-- Text.CheckoutBranchFromStash
-- Text.CheckoutBranchFromStash.Branch
-- Text.CheckoutBranchFromStash.Stash
-- Text.Clone.Bookmark
-- Text.Clone.Group
-- Text.CommandPalette.Branches
-- Text.CommandPalette.BranchesAndTags
-- Text.CommandPalette.RepositoryActions
-- Text.CommandPalette.RevisionFiles
-- Text.CommitDetail.CollapseToBottom
-- Text.CommitMessageTextBox.Column
-- Text.CommitMessageTextBox.Placeholder
-- Text.Compare.Changes
-- Text.Compare.Commits
-- Text.Compare.Commits.LeftOnly
-- Text.Compare.Commits.RightOnly
-- Text.Compare.Commits.Tips
-- Text.Configure.Git.AskBeforeAutoUpdatingSubmodules
-- Text.Configure.Git.ConventionalTypesOverride
-- Text.ConfigureCustomActionControls.StringFormatter
-- Text.ConfigureCustomActionControls.StringFormatter.Tip
-- Text.ConfigureCustomActionControls.StringValue.Tip
-- Text.ConfigureCustomActionControls.UseFriendlyName
-- Text.ConfirmEmptyCommit.StageSelectedThenCommit
-- Text.DealWithLocalChanges.DoNothing
-- Text.Diff.Submodule.UncommittedChanges
-- Text.Discard.IncludeModified
-- Text.EditBranchDescription
-- Text.EditBranchDescription.Target
-- Text.FileCM.CustomAction
-- Text.GitLFS.Locks.UnlockAllMyLocks
-- Text.GitLFS.Locks.UnlockAllMyLocks.Confirm
-- Text.GotoRevisionSelector
-- Text.Histories.Header.DateTime
-- Text.Histories.HighlightsInGraph
-- Text.Histories.HighlightsInGraph.All
-- Text.Histories.HighlightsInGraph.CurrentBranchOnly
-- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
-- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
-- Text.Histories.ShowColumns
-- Text.HistoriesDetailsStandalone
-- Text.HistoriesDetailsStandalone.CommitDetail
-- Text.HistoriesDetailsStandalone.RevisionCompare
-- Text.Hotkeys.Global.OpenLocalRepository
-- Text.Hotkeys.Global.ShowWorkspaceDropdownMenu
-- Text.Hotkeys.Global.Zoom
-- Text.Hotkeys.Repo.CreateBranch
-- Text.Hotkeys.Repo.GoToChild
-- Text.Hotkeys.Repo.GoToParent
-- Text.Hotkeys.Repo.OpenCommandPalette
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
-- Text.Init.CommandTip
-- Text.Init.ErrorMessageTip
-- Text.InteractiveRebase.NoVerify
-- Text.Launcher.Commands
-- Text.Launcher.OpenRepository
-- Text.MergeConflictEditor.AcceptBoth.MineFirst
-- Text.MergeConflictEditor.AcceptBoth.TheirsFirst
-- Text.MergeConflictEditor.UseBoth
-- Text.MergeConflictEditor.AllResolved
-- Text.MergeConflictEditor.ConflictsRemaining
-- Text.MergeConflictEditor.Mine
-- Text.MergeConflictEditor.NextConflict
-- Text.MergeConflictEditor.PrevConflict
-- Text.MergeConflictEditor.Result
-- Text.MergeConflictEditor.SaveAndStage
-- Text.MergeConflictEditor.Theirs
-- Text.MergeConflictEditor.Title
-- Text.MergeConflictEditor.UnsavedChanges
-- Text.MergeConflictEditor.UseMine
-- Text.MergeConflictEditor.UseTheirs
-- Text.MergeConflictEditor.Undo
-- Text.No
-- Text.Open
-- Text.Open.SystemDefaultEditor
-- Text.OpenFile
-- Text.OpenLocalRepository
-- Text.OpenLocalRepository.Bookmark
-- Text.OpenLocalRepository.Group
-- Text.OpenLocalRepository.Path
-- Text.PageTabBar.Tab.MoveToWorkspace
-- Text.PageTabBar.Tab.Refresh
-- Text.Preferences.AI.AdditionalPrompt
-- Text.Preferences.AI.Model
-- Text.Preferences.AI.Model.AutoFetchAvailableModels
-- Text.Preferences.Appearance.UseFixedTabWidth
-- Text.Preferences.DiffMerge.DiffArgs
-- Text.Preferences.DiffMerge.DiffArgs.Tip
-- Text.Preferences.DiffMerge.MergeArgs
-- Text.Preferences.DiffMerge.MergeArgs.Tip
-- Text.Preferences.General.ShowRelativeTimeInGraph
-- Text.Preferences.General.Use24Hours
-- Text.Preferences.Git.UseStashAndReapplyByDefault
-- Text.Preferences.Shell.Args
-- Text.Preferences.Shell.Args.Tip
-- Text.PushToNewBranch
-- Text.PushToNewBranch.Title
-- Text.Rebase.NoVerify
-- Text.RemoteCM.EnableAutoFetch
-- Text.Repository.OpenAsFolder
-- Text.Repository.Resolve
-- Text.SelfUpdate.CurrentVersion
-- Text.SelfUpdate.ReleaseDate
-- Text.StashCM.ApplyFileChanges
-- Text.StashCM.Branch
-- Text.Submodule.Status.Unmerged
-- Text.SubmoduleRevisionCompare
-- Text.SubmoduleRevisionCompare.OpenDetails
-- Text.TagCM.CompareTwo
-- Text.TagCM.CompareWith
-- Text.TagCM.CompareWithHead
-- Text.WorkingCopy.Conflicts.Merge
-- Text.WorkingCopy.Conflicts.MergeExternal
-- Text.Worktree.Branch
-- Text.Worktree.Head
-- Text.Worktree.Path
-- Text.Yes
+- Text.Bisect.WaitingForCheckoutAnother
+- Text.Bisect.WaitingForFirstBad
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
+- Text.BranchTree.PushURL
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
+- Text.CheckoutDetached
+- Text.CheckoutDetached.Target
+- Text.CheckoutDetached.Warning
+- Text.CommitDetail.Files.Tips
+- Text.Configure.Git.EnableRecursiveWhenAutoUpdatingSubmodules
+- Text.Dashboard
+- Text.DeleteBranch.Force
+- Text.Diff.Binary.ViewContentOfNew
+- Text.Diff.EmptyFile
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
+- Text.FileModeChange
+- Text.FileModeChange.Deleted
+- Text.FileModeChange.Directory
+- Text.FileModeChange.Executable
+- Text.FileModeChange.New
+- Text.FileModeChange.Normal
+- Text.FileModeChange.Submodule
+- Text.FileModeChange.Symlink
+- Text.FileModeChange.Unknown
+- Text.GitFlow.Finish
+- Text.GitFlow.FinishWithRebase
+- Text.GitFlow.StartAt
+- Text.GitFlow.StartName
+- Text.Histories.AdvancedOptions
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
+- Text.Launcher.NewVersion
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
+- Text.Preferences.AI.ExtraHeaders
+- Text.Preferences.AI.ReasoningEffortLevel
+- Text.Remote.ResetPushURL
+- Text.RemoteCM.SetPushURL
+- Text.Repository.FilterCommits.Collapse
+- Text.Repository.FilterCommits.Expand
+- Text.Repository.FilterCommits.Summary
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
+- Text.TagCM.Checkout
+- Text.TagCM.Merge
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.UpdateSubmodules.Recursive
+- Text.WorkingCopy.AddToGitIgnore.UntrackedInSameFolder
+- Text.WorkingCopy.CollapseSidebar
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+- Text.WorkingCopy.FilterChanges
-### 
+### 
Missing keys in pt_BR.axaml
-- Text.About.GitSourceRevision
- Text.AIAssistant.Use
- Text.App.HideOthers
- Text.Apply.3Way
+- Text.Apply.Source
+- Text.Apply.Source.File
+- Text.Apply.Source.Clipboard
+- Text.Bisect.WaitingForCheckoutAnother
+- Text.Bisect.WaitingForFirstBad
- Text.Blame.BlameOnPreviousRevision
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
- Text.BranchCM.CompareWithSpecial
- Text.BranchCM.InteractiveRebase.Manually
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
+- Text.BranchTree.PushURL
- Text.BranchTree.Tracking
- Text.BranchTree.URL
- Text.BranchTree.Worktree
@@ -687,9 +817,20 @@ This document shows the translation status of each locale file in the repository
- Text.Checkout.WarnUpdatingSubmodules
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
+- Text.CheckoutDetached
+- Text.CheckoutDetached.Target
+- Text.CheckoutDetached.Warning
- Text.Clone.Bookmark
- Text.Clone.Group
- Text.Clone.RecurseSubmodules
@@ -698,8 +839,10 @@ This document shows the translation status of each locale file in the repository
- Text.CommandPalette.RepositoryActions
- Text.CommandPalette.RevisionFiles
- Text.CommitCM.CopyAuthor
+- Text.CommitCM.CopyAuthorTime
- Text.CommitCM.CopyCommitMessage
- Text.CommitCM.CopyCommitter
+- Text.CommitCM.CopyCommitterTime
- Text.CommitCM.CopySubject
- Text.CommitCM.InteractiveRebase
- Text.CommitCM.InteractiveRebase.Drop
@@ -708,7 +851,6 @@ This document shows the translation status of each locale file in the repository
- Text.CommitCM.InteractiveRebase.Manually
- Text.CommitCM.InteractiveRebase.Reword
- Text.CommitCM.InteractiveRebase.Squash
-- Text.CommitCM.Merge
- Text.CommitCM.MergeMultiple
- Text.CommitCM.PushRevision
- Text.CommitCM.Rebase
@@ -716,13 +858,12 @@ This document shows the translation status of each locale file in the repository
- Text.CommitDetail.Changes.Count
- Text.CommitDetail.CollapseToBottom
- Text.CommitDetail.Files.Search
-- Text.CommitDetail.Info.Children
+- Text.CommitDetail.Files.Tips
- Text.CommitDetail.Info.CopyEmail
- Text.CommitDetail.Info.CopyName
- Text.CommitDetail.Info.CopyNameAndEmail
- Text.CommitDetail.Info.Key
- Text.CommitDetail.Info.Signer
-- Text.CommitMessageTextBox.Column
- Text.CommitMessageTextBox.Placeholder
- Text.CommitMessageTextBox.SubjectCount
- Text.Compare.Changes
@@ -741,6 +882,7 @@ This document shows the translation status of each locale file in the repository
- Text.Configure.CustomAction.WaitForExit
- Text.Configure.Git.AskBeforeAutoUpdatingSubmodules
- Text.Configure.Git.ConventionalTypesOverride
+- Text.Configure.Git.EnableRecursiveWhenAutoUpdatingSubmodules
- Text.Configure.Git.PreferredMergeMode
- Text.Configure.IssueTracker.AddSampleGerritChangeIdCommit
- Text.Configure.IssueTracker.AddSampleGiteeIssue
@@ -767,17 +909,22 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.WithLocalChanges
- Text.ConfirmRestart.Title
- Text.ConfirmRestart.Message
+- Text.CopyAsPatch
- Text.CopyFullPath
- Text.CreateBranch.OverwriteExisting
+- Text.Dashboard
- Text.DeinitSubmodule
- Text.DeinitSubmodule.Force
- Text.DeinitSubmodule.Path
+- Text.DeleteBranch.Force
- Text.DeleteMultiTags
- Text.DeleteMultiTags.DeleteFromRemotes
- Text.DeleteMultiTags.Tip
- Text.DeleteRepositoryNode.Path
- Text.DeleteRepositoryNode.TipForGroup
- Text.DeleteRepositoryNode.TipForRepository
+- Text.Diff.Binary.ViewContentOfNew
+- Text.Diff.EmptyFile
- Text.Diff.First
- Text.Diff.Image.Blend
- Text.Diff.Image.Difference
@@ -799,18 +946,35 @@ This document shows the translation status of each locale file in the repository
- Text.ExecuteCustomAction.Target
- Text.ExecuteCustomAction.Repository
- Text.Fetch.Force
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
- Text.FileCM.CustomAction
- Text.FileCM.ResolveUsing
+- Text.FileModeChange
+- Text.FileModeChange.Deleted
+- Text.FileModeChange.Directory
+- Text.FileModeChange.Executable
+- Text.FileModeChange.New
+- Text.FileModeChange.Normal
+- Text.FileModeChange.Submodule
+- Text.FileModeChange.Symlink
+- Text.FileModeChange.Unknown
+- Text.GitFlow.Finish
+- Text.GitFlow.FinishWithRebase
- Text.GitFlow.FinishWithSquash
+- Text.GitFlow.StartAt
+- Text.GitFlow.StartName
- Text.GitLFS.Locks.UnlockAllMyLocks
- Text.GitLFS.Locks.UnlockAllMyLocks.Confirm
- Text.GotoRevisionSelector
-- Text.Histories.Header.DateTime
+- Text.Histories.AdvancedOptions
- Text.Histories.HighlightsInGraph
- Text.Histories.HighlightsInGraph.All
- Text.Histories.HighlightsInGraph.CurrentBranchOnly
- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
- Text.Histories.ShowColumns
- Text.HistoriesDetailsStandalone
- Text.HistoriesDetailsStandalone.CommitDetail
@@ -824,7 +988,9 @@ This document shows the translation status of each locale file in the repository
- Text.Hotkeys.Repo.GoToChild
- Text.Hotkeys.Repo.GoToParent
- Text.Hotkeys.Repo.OpenCommandPalette
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
- Text.Init.CommandTip
- Text.Init.ErrorMessageTip
- Text.InProgress.CherryPick.Head
@@ -834,11 +1000,16 @@ This document shows the translation status of each locale file in the repository
- Text.InteractiveRebase.NoVerify
- Text.InteractiveRebase.ReorderTip
- Text.Launcher.Commands
+- Text.Launcher.NewVersion
- Text.Launcher.OpenRepository
- Text.Launcher.Pages
- Text.Launcher.Workspaces
- Text.Merge.Edit
- Text.Merge.Source
+- Text.Merge.Test
+- Text.Merge.Test.NoConflicts
+- Text.Merge.Test.UnknownError
+- Text.Merge.Test.WillCauseConflicts
- Text.MergeConflictEditor.AcceptBoth.MineFirst
- Text.MergeConflictEditor.AcceptBoth.TheirsFirst
- Text.MergeConflictEditor.UseBoth
@@ -865,6 +1036,8 @@ This document shows the translation status of each locale file in the repository
- Text.No
- Text.Open
- Text.Open.SystemDefaultEditor
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
- Text.OpenFile
- Text.OpenLocalRepository
- Text.OpenLocalRepository.Bookmark
@@ -873,9 +1046,11 @@ This document shows the translation status of each locale file in the repository
- Text.PageTabBar.Tab.MoveToWorkspace
- Text.PageTabBar.Tab.Refresh
- Text.Preferences.AI.AdditionalPrompt
+- Text.Preferences.AI.ExtraHeaders
- Text.Preferences.AI.Model
- Text.Preferences.AI.Model.AutoFetchAvailableModels
- Text.Preferences.AI.ReadApiKeyFromEnv
+- Text.Preferences.AI.ReasoningEffortLevel
- Text.Preferences.Appearance.EditorTabWidth
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.DiffMerge.DiffArgs
@@ -886,10 +1061,10 @@ This document shows the translation status of each locale file in the repository
- Text.Preferences.General.EnableCompactFolders
- Text.Preferences.General.ShowChangesPageByDefault
- Text.Preferences.General.ShowChangesTabInCommitDetailByDefault
-- Text.Preferences.General.ShowChildren
- Text.Preferences.General.ShowRelativeTimeInGraph
- Text.Preferences.General.ShowTagsInGraph
- Text.Preferences.General.Use24Hours
+- Text.Preferences.General.UseCompactBranchNames
- Text.Preferences.General.UseGitHubStyleAvatar
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
- Text.Preferences.Git.SSLVerify
@@ -903,24 +1078,33 @@ This document shows the translation status of each locale file in the repository
- Text.PushToNewBranch
- Text.PushToNewBranch.Title
- Text.Rebase.NoVerify
+- Text.Rebase.Test
+- Text.Rebase.Test.OK
+- Text.Rebase.Test.UnknownError
+- Text.Rebase.Test.WillCauseConflicts
+- Text.Remote.ResetPushURL
- Text.RemoteCM.CustomAction
- Text.RemoteCM.EnableAutoFetch
+- Text.RemoteCM.SetPushURL
- Text.Repository.BranchSort
- Text.Repository.BranchSort.ByCommitterDate
- Text.Repository.BranchSort.ByName
- Text.Repository.ClearStashes
-- Text.Repository.Dashboard
- Text.Repository.FilterCommits
+- Text.Repository.FilterCommits.Collapse
+- Text.Repository.FilterCommits.Expand
+- Text.Repository.FilterCommits.Summary
- Text.Repository.HistoriesLayout
- Text.Repository.HistoriesLayout.Horizontal
- Text.Repository.HistoriesLayout.Vertical
- Text.Repository.HistoriesOrder
-- Text.Repository.MoreOptions
- Text.Repository.Notifications.Clear
- Text.Repository.OpenAsFolder
- Text.Repository.Resolve
- Text.Repository.Search.ByContent
- Text.Repository.Search.ByPath
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
- Text.Repository.ShowDecoratedCommitsOnly
- Text.Repository.ShowFirstParentOnly
- Text.Repository.ShowFlags
@@ -938,6 +1122,10 @@ This document shows the translation status of each locale file in the repository
- Text.ScanRepositories.UseCustomDir
- Text.SelfUpdate.CurrentVersion
- Text.SelfUpdate.ReleaseDate
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
- Text.SetSubmoduleBranch
- Text.SetSubmoduleBranch.Submodule
- Text.SetSubmoduleBranch.Current
@@ -948,11 +1136,33 @@ This document shows the translation status of each locale file in the repository
- Text.SetUpstream.Unset
- Text.SetUpstream.Upstream
- Text.SHALinkCM.NavigateTo
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
- Text.Stash.Mode
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.StashCM.CopyMessage
- Text.StashCM.SaveAsPatch
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
- Text.Submodule.Branch
- Text.Submodule.CopyBranch
- Text.Submodule.Deinit
@@ -971,6 +1181,7 @@ This document shows the translation status of each locale file in the repository
- Text.SubmoduleRevisionCompare.OpenDetails
- Text.Tag.Tagger
- Text.Tag.Time
+- Text.TagCM.Checkout
- Text.TagCM.CompareTwo
- Text.TagCM.CompareWith
- Text.TagCM.CompareWithHead
@@ -980,14 +1191,24 @@ This document shows the translation status of each locale file in the repository
- Text.TagCM.CopyName
- Text.TagCM.CustomAction
- Text.TagCM.DeleteMultiple
+- Text.TagCM.Merge
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.UpdateSubmodules.Recursive
- Text.UpdateSubmodules.UpdateToRemoteTrackingBranch
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
- Text.ViewLogs.Delete
- Text.WorkingCopy.AddToGitIgnore.InFolder
+- Text.WorkingCopy.AddToGitIgnore.UntrackedInSameFolder
- Text.WorkingCopy.ClearCommitHistories
- Text.WorkingCopy.ClearCommitHistories.Confirm
+- Text.WorkingCopy.CollapseSidebar
- Text.WorkingCopy.CommitToEdit
- Text.WorkingCopy.ConfirmCommitWithDetachedHead
- Text.WorkingCopy.ConfirmCommitWithFilter
@@ -996,6 +1217,9 @@ This document shows the translation status of each locale file in the repository
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
- Text.WorkingCopy.Conflicts.UseMine
- Text.WorkingCopy.Conflicts.UseTheirs
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+- Text.WorkingCopy.FilterChanges
- Text.WorkingCopy.NoVerify
- Text.WorkingCopy.ResetAuthor
- Text.WorkingCopy.SignOff
@@ -1007,39 +1231,13 @@ This document shows the translation status of each locale file in the repository
-### 
-
-
-Missing keys in ru_RU.axaml
-
-- Text.Compare.Changes
-- Text.Compare.Commits
-- Text.Compare.Commits.LeftOnly
-- Text.Compare.Commits.RightOnly
-- Text.Compare.Commits.Tips
-- Text.Histories.HighlightsInGraph
-- Text.Histories.HighlightsInGraph.All
-- Text.Histories.HighlightsInGraph.CurrentBranchOnly
-- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
-- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
-- Text.HistoriesDetailsStandalone
-- Text.HistoriesDetailsStandalone.CommitDetail
-- Text.HistoriesDetailsStandalone.RevisionCompare
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
-- Text.InteractiveRebase.NoVerify
-- Text.Preferences.AI.Model
-- Text.Preferences.AI.Model.AutoFetchAvailableModels
-- Text.Preferences.General.ShowRelativeTimeInGraph
-- Text.Rebase.NoVerify
-
-
+### 
-### 
+### 
Missing keys in ta_IN.axaml
-- Text.About.GitSourceRevision
- Text.About.ReleaseDate
- Text.About.ReleaseNotes
- Text.AddToIgnore
@@ -1050,17 +1248,24 @@ This document shows the translation status of each locale file in the repository
- Text.App.HideOthers
- Text.App.ShowAll
- Text.Apply.3Way
+- Text.Apply.Source
+- Text.Apply.Source.File
+- Text.Apply.Source.Clipboard
- Text.Askpass.Passphrase
- Text.Avatar.Load
- Text.Bisect
- Text.Bisect.Abort
- Text.Bisect.Bad
-- Text.Bisect.Detecting
- Text.Bisect.Good
- Text.Bisect.Skip
-- Text.Bisect.WaitingForRange
+- Text.Bisect.WaitingForCheckoutAnother
+- Text.Bisect.WaitingForFirstBad
+- Text.Bisect.WaitingForFirstGood
+- Text.Bisect.WaitingForMark
- Text.Blame.BlameOnPreviousRevision
- Text.Blame.IgnoreWhitespace
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
- Text.BranchCM.CompareTwo
- Text.BranchCM.CompareWith
- Text.BranchCM.CompareWithHead
@@ -1075,6 +1280,7 @@ This document shows the translation status of each locale file in the repository
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
- Text.BranchTree.InvalidUpstream
+- Text.BranchTree.PushURL
- Text.BranchTree.Remote
- Text.BranchTree.Status
- Text.BranchTree.Tracking
@@ -1090,9 +1296,20 @@ This document shows the translation status of each locale file in the repository
- Text.Checkout.WarnUpdatingSubmodules
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
+- Text.CheckoutDetached
+- Text.CheckoutDetached.Target
+- Text.CheckoutDetached.Warning
- Text.Clone.Bookmark
- Text.Clone.Group
- Text.CommandPalette.Branches
@@ -1100,8 +1317,10 @@ This document shows the translation status of each locale file in the repository
- Text.CommandPalette.RepositoryActions
- Text.CommandPalette.RevisionFiles
- Text.CommitCM.CopyAuthor
+- Text.CommitCM.CopyAuthorTime
- Text.CommitCM.CopyCommitMessage
- Text.CommitCM.CopyCommitter
+- Text.CommitCM.CopyCommitterTime
- Text.CommitCM.CopySubject
- Text.CommitCM.InteractiveRebase
- Text.CommitCM.InteractiveRebase.Drop
@@ -1115,12 +1334,12 @@ This document shows the translation status of each locale file in the repository
- Text.CommitCM.Reset
- Text.CommitDetail.Changes.Count
- Text.CommitDetail.CollapseToBottom
+- Text.CommitDetail.Files.Tips
- Text.CommitDetail.Info.CopyEmail
- Text.CommitDetail.Info.CopyName
- Text.CommitDetail.Info.CopyNameAndEmail
- Text.CommitDetail.Info.Key
- Text.CommitDetail.Info.Signer
-- Text.CommitMessageTextBox.Column
- Text.CommitMessageTextBox.Placeholder
- Text.CommitMessageTextBox.SubjectCount
- Text.Compare.Changes
@@ -1137,6 +1356,7 @@ This document shows the translation status of each locale file in the repository
- Text.Configure.CustomAction.Scope.Tag
- Text.Configure.Git.AskBeforeAutoUpdatingSubmodules
- Text.Configure.Git.ConventionalTypesOverride
+- Text.Configure.Git.EnableRecursiveWhenAutoUpdatingSubmodules
- Text.Configure.Git.PreferredMergeMode
- Text.Configure.IssueTracker.AddSampleGerritChangeIdCommit
- Text.Configure.IssueTracker.Share
@@ -1161,14 +1381,19 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.WithLocalChanges
- Text.ConfirmRestart.Title
- Text.ConfirmRestart.Message
+- Text.CopyAsPatch
- Text.CreateBranch.OverwriteExisting
+- Text.Dashboard
- Text.DealWithLocalChanges.DoNothing
- Text.DeinitSubmodule
- Text.DeinitSubmodule.Force
- Text.DeinitSubmodule.Path
+- Text.DeleteBranch.Force
- Text.DeleteMultiTags
- Text.DeleteMultiTags.DeleteFromRemotes
- Text.DeleteMultiTags.Tip
+- Text.Diff.Binary.ViewContentOfNew
+- Text.Diff.EmptyFile
- Text.Diff.Image.Blend
- Text.Diff.Image.Difference
- Text.Diff.Image.SideBySide
@@ -1187,17 +1412,34 @@ This document shows the translation status of each locale file in the repository
- Text.EditBranchDescription.Target
- Text.ExecuteCustomAction.Target
- Text.ExecuteCustomAction.Repository
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
- Text.FileCM.CustomAction
+- Text.FileModeChange
+- Text.FileModeChange.Deleted
+- Text.FileModeChange.Directory
+- Text.FileModeChange.Executable
+- Text.FileModeChange.New
+- Text.FileModeChange.Normal
+- Text.FileModeChange.Submodule
+- Text.FileModeChange.Symlink
+- Text.FileModeChange.Unknown
+- Text.GitFlow.Finish
+- Text.GitFlow.FinishWithRebase
- Text.GitFlow.FinishWithSquash
+- Text.GitFlow.StartAt
+- Text.GitFlow.StartName
- Text.GitLFS.Locks.UnlockAllMyLocks
- Text.GitLFS.Locks.UnlockAllMyLocks.Confirm
- Text.GotoRevisionSelector
-- Text.Histories.Header.DateTime
+- Text.Histories.AdvancedOptions
- Text.Histories.HighlightsInGraph
- Text.Histories.HighlightsInGraph.All
- Text.Histories.HighlightsInGraph.CurrentBranchOnly
- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
- Text.Histories.ShowColumns
- Text.HistoriesDetailsStandalone
- Text.HistoriesDetailsStandalone.CommitDetail
@@ -1210,16 +1452,23 @@ This document shows the translation status of each locale file in the repository
- Text.Hotkeys.Repo.GoToChild
- Text.Hotkeys.Repo.GoToParent
- Text.Hotkeys.Repo.OpenCommandPalette
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
- Text.Init.CommandTip
- Text.Init.ErrorMessageTip
- Text.InteractiveRebase.NoVerify
- Text.InteractiveRebase.ReorderTip
- Text.Launcher.Commands
+- Text.Launcher.NewVersion
- Text.Launcher.OpenRepository
- Text.Launcher.Pages
- Text.Launcher.Workspaces
- Text.Merge.Edit
+- Text.Merge.Test
+- Text.Merge.Test.NoConflicts
+- Text.Merge.Test.UnknownError
+- Text.Merge.Test.WillCauseConflicts
- Text.MergeConflictEditor.AcceptBoth.MineFirst
- Text.MergeConflictEditor.AcceptBoth.TheirsFirst
- Text.MergeConflictEditor.UseBoth
@@ -1242,6 +1491,8 @@ This document shows the translation status of each locale file in the repository
- Text.No
- Text.Open
- Text.Open.SystemDefaultEditor
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
- Text.OpenFile
- Text.OpenLocalRepository
- Text.OpenLocalRepository.Bookmark
@@ -1250,9 +1501,11 @@ This document shows the translation status of each locale file in the repository
- Text.PageTabBar.Tab.MoveToWorkspace
- Text.PageTabBar.Tab.Refresh
- Text.Preferences.AI.AdditionalPrompt
+- Text.Preferences.AI.ExtraHeaders
- Text.Preferences.AI.Model
- Text.Preferences.AI.Model.AutoFetchAvailableModels
- Text.Preferences.AI.ReadApiKeyFromEnv
+- Text.Preferences.AI.ReasoningEffortLevel
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.DiffMerge.DiffArgs
- Text.Preferences.DiffMerge.DiffArgs.Tip
@@ -1263,6 +1516,7 @@ This document shows the translation status of each locale file in the repository
- Text.Preferences.General.ShowChangesTabInCommitDetailByDefault
- Text.Preferences.General.ShowRelativeTimeInGraph
- Text.Preferences.General.Use24Hours
+- Text.Preferences.General.UseCompactBranchNames
- Text.Preferences.General.UseGitHubStyleAvatar
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
- Text.Preferences.Git.UseLibsecret
@@ -1275,18 +1529,27 @@ This document shows the translation status of each locale file in the repository
- Text.PushToNewBranch
- Text.PushToNewBranch.Title
- Text.Rebase.NoVerify
+- Text.Rebase.Test
+- Text.Rebase.Test.OK
+- Text.Rebase.Test.UnknownError
+- Text.Rebase.Test.WillCauseConflicts
+- Text.Remote.ResetPushURL
- Text.RemoteCM.CustomAction
- Text.RemoteCM.EnableAutoFetch
+- Text.RemoteCM.SetPushURL
- Text.Repository.BranchSort
- Text.Repository.BranchSort.ByCommitterDate
- Text.Repository.BranchSort.ByName
- Text.Repository.ClearStashes
-- Text.Repository.Dashboard
-- Text.Repository.MoreOptions
+- Text.Repository.FilterCommits.Collapse
+- Text.Repository.FilterCommits.Expand
+- Text.Repository.FilterCommits.Summary
- Text.Repository.OpenAsFolder
- Text.Repository.Resolve
- Text.Repository.Search.ByContent
- Text.Repository.Search.ByPath
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
- Text.Repository.ShowDecoratedCommitsOnly
- Text.Repository.ShowFirstParentOnly
- Text.Repository.ShowFlags
@@ -1300,15 +1563,41 @@ This document shows the translation status of each locale file in the repository
- Text.ScanRepositories.UseCustomDir
- Text.SelfUpdate.CurrentVersion
- Text.SelfUpdate.ReleaseDate
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
- Text.SetSubmoduleBranch
- Text.SetSubmoduleBranch.Submodule
- Text.SetSubmoduleBranch.Current
- Text.SetSubmoduleBranch.New
- Text.SetSubmoduleBranch.New.Tip
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
- Text.Stash.Mode
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.StashCM.CopyMessage
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
- Text.Submodule.Branch
- Text.Submodule.CopyBranch
- Text.Submodule.Deinit
@@ -1327,6 +1616,7 @@ This document shows the translation status of each locale file in the repository
- Text.SubmoduleRevisionCompare.OpenDetails
- Text.Tag.Tagger
- Text.Tag.Time
+- Text.TagCM.Checkout
- Text.TagCM.CompareTwo
- Text.TagCM.CompareWith
- Text.TagCM.CompareWithHead
@@ -1336,21 +1626,34 @@ This document shows the translation status of each locale file in the repository
- Text.TagCM.CopyName
- Text.TagCM.CustomAction
- Text.TagCM.DeleteMultiple
+- Text.TagCM.Merge
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
- Text.UpdateSubmodules.Target
+- Text.UpdateSubmodules.Recursive
- Text.UpdateSubmodules.UpdateToRemoteTrackingBranch
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
- Text.ViewLogs.Delete
- Text.WorkingCopy.AddToGitIgnore.InFolder
+- Text.WorkingCopy.AddToGitIgnore.UntrackedInSameFolder
- Text.WorkingCopy.ClearCommitHistories
- Text.WorkingCopy.ClearCommitHistories.Confirm
+- Text.WorkingCopy.CollapseSidebar
- Text.WorkingCopy.ConfirmCommitWithDetachedHead
- Text.WorkingCopy.Conflicts.Merge
- Text.WorkingCopy.Conflicts.MergeExternal
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
- Text.WorkingCopy.Conflicts.UseMine
- Text.WorkingCopy.Conflicts.UseTheirs
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+- Text.WorkingCopy.FilterChanges
- Text.WorkingCopy.NoVerify
- Text.WorkingCopy.ResetAuthor
- Text.Worktree.Branch
@@ -1361,12 +1664,11 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in uk_UA.axaml
-- Text.About.GitSourceRevision
- Text.About.ReleaseDate
- Text.About.ReleaseNotes
- Text.AddToIgnore
@@ -1377,17 +1679,24 @@ This document shows the translation status of each locale file in the repository
- Text.App.HideOthers
- Text.App.ShowAll
- Text.Apply.3Way
+- Text.Apply.Source
+- Text.Apply.Source.File
+- Text.Apply.Source.Clipboard
- Text.Askpass.Passphrase
- Text.Avatar.Load
- Text.Bisect
- Text.Bisect.Abort
- Text.Bisect.Bad
-- Text.Bisect.Detecting
- Text.Bisect.Good
- Text.Bisect.Skip
-- Text.Bisect.WaitingForRange
+- Text.Bisect.WaitingForCheckoutAnother
+- Text.Bisect.WaitingForFirstBad
+- Text.Bisect.WaitingForFirstGood
+- Text.Bisect.WaitingForMark
- Text.Blame.BlameOnPreviousRevision
- Text.Blame.IgnoreWhitespace
+- Text.Blame.Tips
+- Text.BranchCM.CheckoutAsWorktree
- Text.BranchCM.CompareTwo
- Text.BranchCM.CompareWith
- Text.BranchCM.CompareWithHead
@@ -1402,6 +1711,7 @@ This document shows the translation status of each locale file in the repository
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
- Text.BranchTree.InvalidUpstream
+- Text.BranchTree.PushURL
- Text.BranchTree.Remote
- Text.BranchTree.Status
- Text.BranchTree.Tracking
@@ -1417,9 +1727,20 @@ This document shows the translation status of each locale file in the repository
- Text.Checkout.WarnUpdatingSubmodules
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
+- Text.CheckoutAsWorktree
+- Text.CheckoutAsWorktree.LocalBranch
+- Text.CheckoutAsWorktree.NewBranchName
+- Text.CheckoutAsWorktree.NewBranchName.Placeholder
+- Text.CheckoutAsWorktree.RemoteBranch
+- Text.CheckoutAsWorktree.Tracking
+- Text.CheckoutAsWorktree.WorktreePath
+- Text.CheckoutAsWorktree.WorktreePath.Placeholder
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
+- Text.CheckoutDetached
+- Text.CheckoutDetached.Target
+- Text.CheckoutDetached.Warning
- Text.Clone.Bookmark
- Text.Clone.Group
- Text.CommandPalette.Branches
@@ -1427,8 +1748,10 @@ This document shows the translation status of each locale file in the repository
- Text.CommandPalette.RepositoryActions
- Text.CommandPalette.RevisionFiles
- Text.CommitCM.CopyAuthor
+- Text.CommitCM.CopyAuthorTime
- Text.CommitCM.CopyCommitMessage
- Text.CommitCM.CopyCommitter
+- Text.CommitCM.CopyCommitterTime
- Text.CommitCM.CopySubject
- Text.CommitCM.InteractiveRebase
- Text.CommitCM.InteractiveRebase.Drop
@@ -1442,12 +1765,12 @@ This document shows the translation status of each locale file in the repository
- Text.CommitCM.Reset
- Text.CommitDetail.Changes.Count
- Text.CommitDetail.CollapseToBottom
+- Text.CommitDetail.Files.Tips
- Text.CommitDetail.Info.CopyEmail
- Text.CommitDetail.Info.CopyName
- Text.CommitDetail.Info.CopyNameAndEmail
- Text.CommitDetail.Info.Key
- Text.CommitDetail.Info.Signer
-- Text.CommitMessageTextBox.Column
- Text.CommitMessageTextBox.Placeholder
- Text.CommitMessageTextBox.SubjectCount
- Text.Compare.Changes
@@ -1464,6 +1787,7 @@ This document shows the translation status of each locale file in the repository
- Text.Configure.CustomAction.Scope.Tag
- Text.Configure.Git.AskBeforeAutoUpdatingSubmodules
- Text.Configure.Git.ConventionalTypesOverride
+- Text.Configure.Git.EnableRecursiveWhenAutoUpdatingSubmodules
- Text.Configure.IssueTracker.AddSampleGerritChangeIdCommit
- Text.Configure.IssueTracker.Share
- Text.ConfigureCustomActionControls
@@ -1484,14 +1808,19 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.StageSelectedThenCommit
- Text.ConfirmRestart.Title
- Text.ConfirmRestart.Message
+- Text.CopyAsPatch
- Text.CreateBranch.OverwriteExisting
+- Text.Dashboard
- Text.DealWithLocalChanges.DoNothing
- Text.DeinitSubmodule
- Text.DeinitSubmodule.Force
- Text.DeinitSubmodule.Path
+- Text.DeleteBranch.Force
- Text.DeleteMultiTags
- Text.DeleteMultiTags.DeleteFromRemotes
- Text.DeleteMultiTags.Tip
+- Text.Diff.Binary.ViewContentOfNew
+- Text.Diff.EmptyFile
- Text.Diff.Image.Blend
- Text.Diff.Image.Difference
- Text.Diff.Image.SideBySide
@@ -1510,17 +1839,34 @@ This document shows the translation status of each locale file in the repository
- Text.EditBranchDescription.Target
- Text.ExecuteCustomAction.Target
- Text.ExecuteCustomAction.Repository
+- Text.File.Binary
+- Text.File.HexViewer
+- Text.File.HexViewer.GotoAddress
- Text.FileCM.CustomAction
+- Text.FileModeChange
+- Text.FileModeChange.Deleted
+- Text.FileModeChange.Directory
+- Text.FileModeChange.Executable
+- Text.FileModeChange.New
+- Text.FileModeChange.Normal
+- Text.FileModeChange.Submodule
+- Text.FileModeChange.Symlink
+- Text.FileModeChange.Unknown
+- Text.GitFlow.Finish
+- Text.GitFlow.FinishWithRebase
- Text.GitFlow.FinishWithSquash
+- Text.GitFlow.StartAt
+- Text.GitFlow.StartName
- Text.GitLFS.Locks.UnlockAllMyLocks
- Text.GitLFS.Locks.UnlockAllMyLocks.Confirm
- Text.GotoRevisionSelector
-- Text.Histories.Header.DateTime
+- Text.Histories.AdvancedOptions
- Text.Histories.HighlightsInGraph
- Text.Histories.HighlightsInGraph.All
- Text.Histories.HighlightsInGraph.CurrentBranchOnly
- Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits
- Text.Histories.HighlightsInGraph.SelectedCommitsOnly
+- Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent
- Text.Histories.ShowColumns
- Text.HistoriesDetailsStandalone
- Text.HistoriesDetailsStandalone.CommitDetail
@@ -1533,16 +1879,23 @@ This document shows the translation status of each locale file in the repository
- Text.Hotkeys.Repo.GoToChild
- Text.Hotkeys.Repo.GoToParent
- Text.Hotkeys.Repo.OpenCommandPalette
-- Text.Hotkeys.Repo.ToggleHistoriesDetailPanel
+- Text.Hotkeys.Repo.ToggleCommitDetailPanel
+- Text.Hotkeys.Repo.ToggleSearchCommits
+- Text.Hotkeys.Repo.ViewDashboard
- Text.Init.CommandTip
- Text.Init.ErrorMessageTip
- Text.InteractiveRebase.NoVerify
- Text.InteractiveRebase.ReorderTip
- Text.Launcher.Commands
+- Text.Launcher.NewVersion
- Text.Launcher.OpenRepository
- Text.Launcher.Pages
- Text.Launcher.Workspaces
- Text.Merge.Edit
+- Text.Merge.Test
+- Text.Merge.Test.NoConflicts
+- Text.Merge.Test.UnknownError
+- Text.Merge.Test.WillCauseConflicts
- Text.MergeConflictEditor.AcceptBoth.MineFirst
- Text.MergeConflictEditor.AcceptBoth.TheirsFirst
- Text.MergeConflictEditor.UseBoth
@@ -1565,6 +1918,8 @@ This document shows the translation status of each locale file in the repository
- Text.No
- Text.Open
- Text.Open.SystemDefaultEditor
+- Text.OpenAppDataDir.Cache
+- Text.OpenAppDataDir.Config
- Text.OpenFile
- Text.OpenLocalRepository
- Text.OpenLocalRepository.Bookmark
@@ -1573,9 +1928,11 @@ This document shows the translation status of each locale file in the repository
- Text.PageTabBar.Tab.MoveToWorkspace
- Text.PageTabBar.Tab.Refresh
- Text.Preferences.AI.AdditionalPrompt
+- Text.Preferences.AI.ExtraHeaders
- Text.Preferences.AI.Model
- Text.Preferences.AI.Model.AutoFetchAvailableModels
- Text.Preferences.AI.ReadApiKeyFromEnv
+- Text.Preferences.AI.ReasoningEffortLevel
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.DiffMerge.DiffArgs
- Text.Preferences.DiffMerge.DiffArgs.Tip
@@ -1586,6 +1943,7 @@ This document shows the translation status of each locale file in the repository
- Text.Preferences.General.ShowChangesTabInCommitDetailByDefault
- Text.Preferences.General.ShowRelativeTimeInGraph
- Text.Preferences.General.Use24Hours
+- Text.Preferences.General.UseCompactBranchNames
- Text.Preferences.General.UseGitHubStyleAvatar
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
- Text.Preferences.Git.UseLibsecret
@@ -1598,18 +1956,27 @@ This document shows the translation status of each locale file in the repository
- Text.PushToNewBranch
- Text.PushToNewBranch.Title
- Text.Rebase.NoVerify
+- Text.Rebase.Test
+- Text.Rebase.Test.OK
+- Text.Rebase.Test.UnknownError
+- Text.Rebase.Test.WillCauseConflicts
+- Text.Remote.ResetPushURL
- Text.RemoteCM.CustomAction
- Text.RemoteCM.EnableAutoFetch
+- Text.RemoteCM.SetPushURL
- Text.Repository.BranchSort
- Text.Repository.BranchSort.ByCommitterDate
- Text.Repository.BranchSort.ByName
- Text.Repository.ClearStashes
-- Text.Repository.Dashboard
-- Text.Repository.MoreOptions
+- Text.Repository.FilterCommits.Collapse
+- Text.Repository.FilterCommits.Expand
+- Text.Repository.FilterCommits.Summary
- Text.Repository.OpenAsFolder
- Text.Repository.Resolve
- Text.Repository.Search.ByContent
- Text.Repository.Search.ByPath
+- Text.Repository.Search.Method
+- Text.Repository.Search.ToolTip
- Text.Repository.ShowDecoratedCommitsOnly
- Text.Repository.ShowFirstParentOnly
- Text.Repository.ShowFlags
@@ -1623,15 +1990,41 @@ This document shows the translation status of each locale file in the repository
- Text.ScanRepositories.UseCustomDir
- Text.SelfUpdate.CurrentVersion
- Text.SelfUpdate.ReleaseDate
+- Text.SetPushUrl
+- Text.SetPushUrl.PushURL
+- Text.SetPushUrl.Remote
+- Text.SetPushUrl.URL
- Text.SetSubmoduleBranch
- Text.SetSubmoduleBranch.Submodule
- Text.SetSubmoduleBranch.Current
- Text.SetSubmoduleBranch.New
- Text.SetSubmoduleBranch.New.Tip
+- Text.SSHKeyHelper
+- Text.SSHKeyHelper.ConfirmDeletion
+- Text.SSHKeyHelper.Fingerprint
+- Text.SSHKeyHelper.Generator
+- Text.SSHKeyHelper.Generator.ConfirmPassphrase
+- Text.SSHKeyHelper.Generator.Email
+- Text.SSHKeyHelper.Generator.Email.Placeholder
+- Text.SSHKeyHelper.Generator.Name
+- Text.SSHKeyHelper.Generator.Name.Placeholder
+- Text.SSHKeyHelper.Generator.Passphrase
+- Text.SSHKeyHelper.Generator.Start
+- Text.SSHKeyHelper.Generator.Type
+- Text.SSHKeyHelper.Generator.UsePassphrase
+- Text.SSHKeyHelper.Path
+- Text.SSHKeyHelper.PublicKey
+- Text.SSHKeyHelper.Title
- Text.Stash.Mode
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.StashCM.CopyMessage
+- Text.Statistics.Commits
+- Text.Statistics.Tips
+- Text.Statistics.TopAuthors
+- Text.Statistics.TopAuthors.Count
+- Text.Statistics.TopAuthors.Percentage
+- Text.Statistics.TopAuthors.Tip
- Text.Submodule.Branch
- Text.Submodule.CopyBranch
- Text.Submodule.Deinit
@@ -1650,6 +2043,7 @@ This document shows the translation status of each locale file in the repository
- Text.SubmoduleRevisionCompare.OpenDetails
- Text.Tag.Tagger
- Text.Tag.Time
+- Text.TagCM.Checkout
- Text.TagCM.CompareTwo
- Text.TagCM.CompareWith
- Text.TagCM.CompareWithHead
@@ -1659,17 +2053,30 @@ This document shows the translation status of each locale file in the repository
- Text.TagCM.CopyName
- Text.TagCM.CustomAction
- Text.TagCM.DeleteMultiple
+- Text.TagCM.Merge
+- Text.Terminate
+- Text.TrustRepository
+- Text.TrustRepository.CommandTip
+- Text.TrustRepository.Description
+- Text.TrustRepository.Path
+- Text.TrustRepository.ScanSkipped
+- Text.UpdateSubmodules.Recursive
- Text.UpdateSubmodules.UpdateToRemoteTrackingBranch
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
- Text.ViewLogs.Delete
- Text.WorkingCopy.AddToGitIgnore.InFolder
+- Text.WorkingCopy.AddToGitIgnore.UntrackedInSameFolder
- Text.WorkingCopy.ClearCommitHistories
- Text.WorkingCopy.ClearCommitHistories.Confirm
+- Text.WorkingCopy.CollapseSidebar
- Text.WorkingCopy.ConfirmCommitWithDetachedHead
- Text.WorkingCopy.Conflicts.Merge
- Text.WorkingCopy.Conflicts.MergeExternal
+- Text.WorkingCopy.DiscardAll
+- Text.WorkingCopy.ExpandSidebar
+- Text.WorkingCopy.FilterChanges
- Text.WorkingCopy.NoVerify
- Text.WorkingCopy.ResetAuthor
- Text.Worktree.Branch
diff --git a/VERSION b/VERSION
index b3d745254f..bb71f1638b 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2026.11
\ No newline at end of file
+2026.22
\ No newline at end of file
diff --git a/build/resources/_common/applications/sourcegit.desktop b/build/resources/_common/applications/sourcegit.desktop
index bcf9c813ca..966b1d65f3 100644
--- a/build/resources/_common/applications/sourcegit.desktop
+++ b/build/resources/_common/applications/sourcegit.desktop
@@ -1,7 +1,7 @@
[Desktop Entry]
Name=SourceGit
Comment=Open-source & Free Git GUI Client
-Exec=/opt/sourcegit/sourcegit
+Exec=/opt/sourcegit/sourcegit %u
Icon=/usr/share/icons/sourcegit.png
Terminal=false
Type=Application
diff --git a/build/resources/app/App.plist b/build/resources/app/App.plist
index ba6f40a2bb..d20efa5e29 100644
--- a/build/resources/app/App.plist
+++ b/build/resources/app/App.plist
@@ -20,6 +20,21 @@
APPL
CFBundleShortVersionString
SOURCE_GIT_VERSION
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeName
+ Supported Folder
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Alternate
+ LSItemContentTypes
+
+ public.folder
+
+
+
NSHighResolutionCapable
diff --git a/build/resources/flatpak/sourcegit.desktop b/build/resources/flatpak/sourcegit.desktop
index f1213e6f6d..9cf9665be9 100644
--- a/build/resources/flatpak/sourcegit.desktop
+++ b/build/resources/flatpak/sourcegit.desktop
@@ -1,7 +1,7 @@
[Desktop Entry]
Name=SourceGit
Comment=Open-source & Free Git GUI Client
-Exec=sourcegit.sh
+Exec=sourcegit
Icon=io.github.sourcegit_scm.sourcegit
Terminal=false
Type=Application
diff --git a/build/scripts/package.linux.sh b/build/scripts/package.linux.sh
index 262f141ae8..e4645b7213 100755
--- a/build/scripts/package.linux.sh
+++ b/build/scripts/package.linux.sh
@@ -51,7 +51,9 @@ ln -rsf SourceGit.AppDir/opt/sourcegit/sourcegit SourceGit.AppDir/AppRun
ln -rsf SourceGit.AppDir/usr/share/applications/com.sourcegit_scm.SourceGit.desktop SourceGit.AppDir
cp resources/appimage/sourcegit.appdata.xml SourceGit.AppDir/usr/share/metainfo/com.sourcegit_scm.SourceGit.appdata.xml
-ARCH="$appimage_arch" ./appimagetool -v SourceGit.AppDir "sourcegit-$VERSION.linux.$arch.AppImage"
+ARCH="$appimage_arch" ./appimagetool -v \
+ -u "gh-releases-zsync|sourcegit-scm|sourcegit|latest|sourcegit-*.linux.$arch.AppImage.zsync" \
+ SourceGit.AppDir "sourcegit-$VERSION.linux.$arch.AppImage"
mkdir -p resources/deb/opt/sourcegit/
mkdir -p resources/deb/usr/bin
diff --git a/build/scripts/package.osx-app.sh b/build/scripts/package.osx-app.sh
index 8f3ddc77c8..78c31114f4 100755
--- a/build/scripts/package.osx-app.sh
+++ b/build/scripts/package.osx-app.sh
@@ -14,4 +14,14 @@ sed "s/SOURCE_GIT_VERSION/$VERSION/g" resources/app/App.plist > SourceGit.app/Co
rm -rf SourceGit.app/Contents/MacOS/SourceGit.dsym
rm -f SourceGit.app/Contents/MacOS/*.pdb
+case "$RUNTIME" in
+ osx-arm64) ARCH=arm64 ;;
+ osx-x64) ARCH=x86_64 ;;
+ *) echo "Unsupported RUNTIME: $RUNTIME" >&2; exit 1 ;;
+esac
+
+clang -arch "$ARCH" ../tools/setsid-macos/setsid.c -o SourceGit.app/Contents/MacOS/setsid -mmacosx-version-min=13.0
+codesign --force --deep --sign - SourceGit.app
+codesign --verify --deep --strict --verbose=2 SourceGit.app
+
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit.app
diff --git a/depends/AvaloniaEdit b/depends/AvaloniaEdit
index 1e6f595ff0..ddf00a91dc 160000
--- a/depends/AvaloniaEdit
+++ b/depends/AvaloniaEdit
@@ -1 +1 @@
-Subproject commit 1e6f595ff0da0dc20637322cbab9531892c12cbc
+Subproject commit ddf00a91dc0da29b85c79b85c9583860213a99ff
diff --git a/screenshots/theme_dark.png b/screenshots/theme_dark.png
index 6e8af07b38..bd8fcf339e 100644
Binary files a/screenshots/theme_dark.png and b/screenshots/theme_dark.png differ
diff --git a/screenshots/theme_light.png b/screenshots/theme_light.png
index 10988efdb3..baa54ef722 100644
Binary files a/screenshots/theme_light.png and b/screenshots/theme_light.png differ
diff --git a/src/AI/Agent.cs b/src/AI/Agent.cs
index fd857ef58e..178683ec68 100644
--- a/src/AI/Agent.cs
+++ b/src/AI/Agent.cs
@@ -14,13 +14,18 @@ public Agent(Service service)
_service = service;
}
- public async Task GenerateCommitMessageAsync(string repo, string changeList, Action onUpdate, CancellationToken cancellation)
+ public async Task GenerateCommitMessageAsync(string repo, string currentBranch, string changeList, string amendParent, Action onUpdate, CancellationToken cancellation)
{
var chatClient = _service.GetChatClient();
if (chatClient == null)
throw new Exception("Failed to fetch available models from this service. Please check your configuration and try again.");
var options = new ChatCompletionOptions() { Tools = { ChatTools.GetDetailChangesInFile } };
+#pragma warning disable OPENAI001
+ if (!_service.ReasoningEffortLevel.Equals(Options.IgnoredReasoningEffortLevel, StringComparison.OrdinalIgnoreCase))
+ options.ReasoningEffortLevel = new ChatReasoningEffortLevel(_service.ReasoningEffortLevel);
+#pragma warning restore OPENAI001
+
var userMessageBuilder = new StringBuilder();
userMessageBuilder
.AppendLine("Generate a commit message (follow the rule of conventional commit message) for given git repository.")
@@ -28,6 +33,7 @@ public async Task GenerateCommitMessageAsync(string repo, string changeList, Act
.AppendLine("- Output the conventional commit message (with detail changes in list) directly. Do not explain your output nor introduce your answer.")
.AppendLine(_service.AdditionalPrompt)
.Append("Repository path: ").AppendLine(repo.Quoted())
+ .Append("Current branch: ").AppendLine(currentBranch.Quoted())
.AppendLine("Changed files ('A' means added, 'M' means modified, 'D' means deleted, 'T' means type changed, 'R' means renamed, 'C' means copied): ")
.Append(changeList);
@@ -50,10 +56,11 @@ public async Task GenerateCommitMessageAsync(string repo, string changeList, Act
var text = completion.Content[0].Text.ReplaceLineEndings("\n").Trim();
var start = 0;
var len = text.Length;
- if (text.StartsWith("```\n", StringComparison.Ordinal))
+ if (text.StartsWith("```", StringComparison.Ordinal))
{
- start += 4;
- len -= 4;
+ var idx = text.IndexOf('\n') + 1;
+ start += idx;
+ len -= idx;
}
if (text.EndsWith("\n```", StringComparison.Ordinal))
@@ -82,13 +89,18 @@ public async Task GenerateCommitMessageAsync(string repo, string changeList, Act
#pragma warning disable SCME0001
var hasReasoningContent = completion.Patch.TryGetValue("$.choices[0].message.reasoning_content"u8, out string reasoning);
if (hasReasoningContent)
- message.Patch.Set("$.reasoning_content"u8, reasoning);
+ {
+ if (string.IsNullOrEmpty(reasoning))
+ message.Patch.Set("$.reasoning_content"u8, BinaryData.FromString("\"\""));
+ else
+ message.Patch.Set("$.reasoning_content"u8, reasoning);
+ }
#pragma warning restore SCME0001
messages.Add(message);
foreach (var call in completion.ToolCalls)
{
- var result = await ChatTools.ProcessAsync(call, onUpdate);
+ var result = await ChatTools.ProcessAsync(call, repo, amendParent, onUpdate);
messages.Add(result);
}
diff --git a/src/AI/ChatTools.cs b/src/AI/ChatTools.cs
index b9e2f80c6c..57a8e14595 100644
--- a/src/AI/ChatTools.cs
+++ b/src/AI/ChatTools.cs
@@ -15,10 +15,6 @@ public static class ChatTools
{
"type": "object",
"properties": {
- "repo": {
- "type": "string",
- "description": "The path to the repository."
- },
"file": {
"type": "string",
"description": "The path to the file."
@@ -28,28 +24,25 @@ public static class ChatTools
"description": "The path to the original file when it has been renamed or copied."
}
},
- "required": ["repo", "file"]
+ "required": ["file"]
}
""")), false);
- public static async Task ProcessAsync(ChatToolCall call, Action output)
+ public static async Task ProcessAsync(ChatToolCall call, string repo, string amendParent, Action output)
{
using var doc = JsonDocument.Parse(call.FunctionArguments);
if (call.FunctionName.Equals(GetDetailChangesInFile.FunctionName))
{
- var hasRepo = doc.RootElement.TryGetProperty("repo", out var repoPath);
var hasFile = doc.RootElement.TryGetProperty("file", out var filePath);
var hasOriginalFile = doc.RootElement.TryGetProperty("originalFile", out var originalFilePath);
- if (!hasRepo)
- throw new ArgumentException("repo", "The repo argument is required");
if (!hasFile)
throw new ArgumentException("file", "The file argument is required");
output?.Invoke($"Read changes in file: {filePath.GetString()}");
var orgFilePath = hasOriginalFile ? originalFilePath.GetString() : string.Empty;
- var rs = await new Commands.GetFileChangeForAI(repoPath.GetString(), filePath.GetString(), orgFilePath).ReadAsync();
+ var rs = await new Commands.GetFileChangeForAI(repo, filePath.GetString(), orgFilePath, amendParent).ReadAsync();
var message = rs.IsSuccess ? rs.StdOut : string.Empty;
return new ToolChatMessage(call.Id, message);
}
diff --git a/src/AI/ExtraHeadersPolicy.cs b/src/AI/ExtraHeadersPolicy.cs
new file mode 100644
index 0000000000..a14924ba64
--- /dev/null
+++ b/src/AI/ExtraHeadersPolicy.cs
@@ -0,0 +1,48 @@
+using System;
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace SourceGit.AI
+{
+ public class ExtraHeadersPolicy : PipelinePolicy
+ {
+ public ExtraHeadersPolicy(string headers)
+ {
+ _headers = headers;
+ }
+
+ public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ {
+ Apply(message);
+ ProcessNext(message, pipeline, currentIndex);
+ }
+
+ public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ {
+ Apply(message);
+ await ProcessNextAsync(message, pipeline, currentIndex);
+ }
+
+ private void Apply(PipelineMessage message)
+ {
+ var lines = _headers.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var line in lines)
+ {
+ var idx = line.IndexOf(':');
+ if (idx < 0)
+ continue;
+
+ var key = line.Substring(0, idx).Trim();
+ var value = line.Substring(idx + 1).Trim();
+ if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value))
+ continue;
+
+ message.Request.Headers.Add(key, value);
+ }
+ }
+
+ private readonly string _headers;
+ }
+}
diff --git a/src/AI/Options.cs b/src/AI/Options.cs
new file mode 100644
index 0000000000..e83b72caad
--- /dev/null
+++ b/src/AI/Options.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace SourceGit.AI
+{
+ public static class Options
+ {
+ public static readonly string IgnoredReasoningEffortLevel = "unspecified";
+ public static readonly List ReasoningEffortLevels = [
+ IgnoredReasoningEffortLevel,
+ "none",
+ "minimal",
+ "low",
+ "medium",
+ "high",
+ ];
+ }
+}
diff --git a/src/AI/Service.cs b/src/AI/Service.cs
index 7d311c6c04..abc1c63a12 100644
--- a/src/AI/Service.cs
+++ b/src/AI/Service.cs
@@ -1,7 +1,9 @@
using System;
using System.ClientModel;
+using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Text.Json.Serialization;
+
using Azure.AI.OpenAI;
using CommunityToolkit.Mvvm.ComponentModel;
using OpenAI;
@@ -54,12 +56,24 @@ public bool AutoFetchAvailableModels
set => SetProperty(ref _autoFetchAvailableModels, value);
}
+ public string ReasoningEffortLevel
+ {
+ get => _reasoningEffortLevel;
+ set => SetProperty(ref _reasoningEffortLevel, value);
+ }
+
public string AdditionalPrompt
{
get;
set;
} = string.Empty;
+ public string ExtraHeaders
+ {
+ get;
+ set;
+ } = string.Empty;
+
public void FetchAvailableModels()
{
if (!_autoFetchAvailableModels)
@@ -86,13 +100,26 @@ public ChatClient GetChatClient()
private OpenAIClient GetOpenAIClient()
{
var credential = new ApiKeyCredential(ReadApiKeyFromEnv ? Environment.GetEnvironmentVariable(ApiKey) : ApiKey);
- return Server.Contains("openai.azure.com/", StringComparison.Ordinal)
- ? new AzureOpenAIClient(new Uri(Server), credential)
- : new OpenAIClient(credential, new() { Endpoint = new Uri(Server) });
+
+ if (Server.Contains("openai.azure.com/", StringComparison.Ordinal))
+ {
+ if (string.IsNullOrEmpty(ExtraHeaders))
+ return new AzureOpenAIClient(new Uri(Server), credential);
+
+ var azureOptions = new AzureOpenAIClientOptions();
+ azureOptions.AddPolicy(new ExtraHeadersPolicy(ExtraHeaders), PipelinePosition.PerCall);
+ return new AzureOpenAIClient(new Uri(Server), credential, azureOptions);
+ }
+
+ var options = new OpenAIClientOptions() { Endpoint = new Uri(Server) };
+ if (!string.IsNullOrEmpty(ExtraHeaders))
+ options.AddPolicy(new ExtraHeadersPolicy(ExtraHeaders), PipelinePosition.PerCall);
+ return new OpenAIClient(credential, options);
}
private string _name = string.Empty;
private string _model = string.Empty;
+ private string _reasoningEffortLevel = Options.IgnoredReasoningEffortLevel;
private bool _autoFetchAvailableModels = true;
}
}
diff --git a/src/App.Commands.cs b/src/App.Commands.cs
index daa930b763..ad92a5755f 100644
--- a/src/App.Commands.cs
+++ b/src/App.Commands.cs
@@ -55,9 +55,14 @@ public static bool IsCheckForUpdateCommandVisible
}
});
+ public static readonly Command OpenAppConfigDirCommand = new Command(_ =>
+ {
+ Native.OS.OpenInFileManager(Native.OS.BasicDirectories.ConfigDir);
+ });
+
public static readonly Command OpenAppDataDirCommand = new Command(_ =>
{
- Native.OS.OpenInFileManager(Native.OS.DataDir);
+ Native.OS.OpenInFileManager(Native.OS.BasicDirectories.CacheDir);
});
public static readonly Command OpenAboutCommand = new Command(async _ =>
@@ -81,17 +86,30 @@ public static bool IsCheckForUpdateCommandVisible
public static readonly Command HideAppCommand = new Command(_ =>
{
- Native.OS.HideSelf();
+ if (OperatingSystem.IsMacOS())
+ Native.MacOSUtilities.HideSelf();
});
public static readonly Command HideOtherApplicationsCommand = new Command(_ =>
{
- Native.OS.HideOtherApplications();
+ if (OperatingSystem.IsMacOS())
+ Native.MacOSUtilities.HideOtherApplications();
});
public static readonly Command ShowAllApplicationsCommand = new Command(_ =>
{
- Native.OS.ShowAllApplications();
+ if (OperatingSystem.IsMacOS())
+ Native.MacOSUtilities.ShowAllApplications();
+ });
+
+ public static readonly Command OpenSSHKeyHelperCommand = new Command(async _ =>
+ {
+ if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
+ {
+ var vm = new ViewModels.SSHKeyHelper();
+ var dialog = new Views.SSHKeyHelper() { DataContext = vm };
+ await dialog.ShowDialog(owner);
+ }
});
}
}
diff --git a/src/App.Extensions.cs b/src/App.Extensions.cs
index 7c2d0195a0..08c1b54783 100644
--- a/src/App.Extensions.cs
+++ b/src/App.Extensions.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
+using System.Threading;
using Avalonia.Media;
namespace SourceGit
@@ -18,6 +19,18 @@ public static string Escaped(this string value)
return value.Replace("\"", "\\\"", StringComparison.Ordinal);
}
+ public static string EscapeForBRE(this string value)
+ {
+ return value
+ .Replace("\\", "\\\\", StringComparison.Ordinal)
+ .Replace(".", "\\.", StringComparison.Ordinal)
+ .Replace("[", "\\[", StringComparison.Ordinal)
+ .Replace("*", "\\*", StringComparison.Ordinal)
+ .Replace("^", "\\^", StringComparison.Ordinal)
+ .Replace("$", "\\$", StringComparison.Ordinal)
+ .Replace("{", "\\{", StringComparison.Ordinal);
+ }
+
public static string FormatFontNames(string input)
{
if (string.IsNullOrEmpty(input))
@@ -67,6 +80,12 @@ public static T Use(this T cmd, Models.ICommandLog log) where T : Commands.Co
cmd.Log = log;
return cmd;
}
+
+ public static T WithCancellation(this T cmd, CancellationToken token) where T : Commands.Command
+ {
+ cmd.CancellationToken = token;
+ return cmd;
+ }
}
public static class DirectoryInfoExtension
diff --git a/src/App.JsonCodeGen.cs b/src/App.JsonCodeGen.cs
index 43dcdde148..8eef67ae72 100644
--- a/src/App.JsonCodeGen.cs
+++ b/src/App.JsonCodeGen.cs
@@ -51,20 +51,6 @@ public override void Write(Utf8JsonWriter writer, GridLength value, JsonSerializ
}
}
- public class DataGridLengthConverter : JsonConverter
- {
- public override DataGridLength Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- var size = reader.GetDouble();
- return new DataGridLength(size, DataGridLengthUnitType.Pixel, 0, size);
- }
-
- public override void Write(Utf8JsonWriter writer, DataGridLength value, JsonSerializerOptions options)
- {
- writer.WriteNumberValue(value.DisplayValue);
- }
- }
-
[JsonSourceGenerationOptions(
WriteIndented = true,
IgnoreReadOnlyFields = true,
@@ -73,7 +59,6 @@ public override void Write(Utf8JsonWriter writer, DataGridLength value, JsonSeri
typeof(DateTimeConverter),
typeof(ColorConverter),
typeof(GridLengthConverter),
- typeof(DataGridLengthConverter),
]
)]
[JsonSerializable(typeof(Models.ExternalToolCustomization))]
@@ -87,5 +72,6 @@ public override void Write(Utf8JsonWriter writer, DataGridLength value, JsonSeri
[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(ViewModels.Preferences))]
+ [JsonSerializable(typeof(ViewModels.RepositoryNodeMinimalInfo))]
internal partial class JsonCodeGen : JsonSerializerContext { }
}
diff --git a/src/App.axaml b/src/App.axaml
index 2fa982635c..2663e44ad4 100644
--- a/src/App.axaml
+++ b/src/App.axaml
@@ -8,12 +8,15 @@
+
+
+
@@ -42,6 +45,7 @@
+
diff --git a/src/App.axaml.cs b/src/App.axaml.cs
index 7e7dc8312f..66882b4869 100644
--- a/src/App.axaml.cs
+++ b/src/App.axaml.cs
@@ -11,6 +11,7 @@
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Fonts;
+using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
@@ -22,7 +23,7 @@ public partial class App : Application
[STAThread]
public static void Main(string[] args)
{
- Native.OS.SetupDataDir();
+ Native.OS.SetupBasicDirectories();
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
{
@@ -101,8 +102,11 @@ public static async Task AskConfirmAsync(string message, Models.ConfirmBut
public static void SetLocale(string localeKey)
{
+ var locale = Models.Locale.Supported.Find(x => x.Key.Equals(localeKey, StringComparison.OrdinalIgnoreCase));
+ var finalLocaleKey = locale?.Key ?? "en_US";
+
if (Current is not App app ||
- app.Resources[localeKey] is not ResourceDictionary targetLocale ||
+ app.Resources[finalLocaleKey] is not ResourceDictionary targetLocale ||
targetLocale == app._activeLocale)
return;
@@ -187,7 +191,7 @@ public static void SetFonts(string defaultFont, string monospaceFont)
{
if (!string.IsNullOrEmpty(defaultFont))
{
- monospaceFont = $"fonts:SourceGit#JetBrains Mono,{defaultFont}";
+ monospaceFont = $"fonts:SourceGit#JetBrains Mono NL,{defaultFont}";
resDic.Add("Fonts.Monospace", FontFamily.Parse(monospaceFont));
}
}
@@ -282,7 +286,7 @@ private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode)
if (args.Length <= 1 || !args[0].Equals("--rebase-todo-editor", StringComparison.Ordinal))
return false;
- var file = args[1];
+ var file = args[1].Replace('\\', '/').Trim('\"').Trim();
var filename = Path.GetFileName(file);
if (!filename.Equals("git-rebase-todo", StringComparison.OrdinalIgnoreCase))
return true;
@@ -311,7 +315,7 @@ private static bool TryLaunchAsRebaseMessageEditor(string[] args, out int exitCo
exitCode = 0;
- var file = args[1];
+ var file = args[1].Replace('\\', '/').Trim('\"').Trim();
var filename = Path.GetFileName(file);
if (!filename.Equals("COMMIT_EDITMSG", StringComparison.OrdinalIgnoreCase))
return true;
@@ -340,13 +344,13 @@ private bool TryLaunchAsFileHistoryViewer(IClassicDesktopStyleApplicationLifetim
if (args is not { Length: > 1 } || !args[0].Equals("--history", StringComparison.Ordinal))
return false;
- var fullPath = Path.GetFullPath(args[1]);
+ var fullPath = Path.GetFullPath(args[1].Replace('\\', '/').Trim('\"').Trim());
var dir = Path.GetDirectoryName(fullPath);
var test = new Commands.QueryRepositoryRootPath(dir).GetResult();
if (!test.IsSuccess || string.IsNullOrEmpty(test.StdOut))
{
- Console.Out.WriteLine($"'{args[1]}' is not in a valid git repository");
+ Console.Out.WriteLine(string.IsNullOrWhiteSpace(test.StdErr) ? $"'{args[1]}' is not in a valid git repository" : test.StdErr.Trim());
desktop.Shutdown(-1);
return true;
}
@@ -355,7 +359,7 @@ private bool TryLaunchAsFileHistoryViewer(IClassicDesktopStyleApplicationLifetim
Models.AvatarManager.Instance.Start();
var repo = test.StdOut.Trim();
- var relativePath = Path.GetRelativePath(repo, fullPath);
+ var relativePath = Path.GetRelativePath(repo, fullPath).Replace('\\', '/');
if (File.Exists(fullPath))
{
desktop.MainWindow = new Views.FileHistories()
@@ -367,7 +371,7 @@ private bool TryLaunchAsFileHistoryViewer(IClassicDesktopStyleApplicationLifetim
{
desktop.MainWindow = new Views.DirHistories()
{
- DataContext = new ViewModels.DirHistories(repo, relativePath.TrimEnd('/', '\\'))
+ DataContext = new ViewModels.DirHistories(repo, relativePath.TrimEnd('/'))
};
}
else
@@ -385,13 +389,13 @@ private bool TryLaunchAsBlameViewer(IClassicDesktopStyleApplicationLifetime desk
if (args is not { Length: > 1 } || !args[0].Equals("--blame", StringComparison.Ordinal))
return false;
- var file = Path.GetFullPath(args[1]);
+ var file = Path.GetFullPath(args[1].Replace('\\', '/').Trim('\"').Trim());
var dir = Path.GetDirectoryName(file);
var test = new Commands.QueryRepositoryRootPath(dir).GetResult();
if (!test.IsSuccess || string.IsNullOrEmpty(test.StdOut))
{
- Console.Out.WriteLine($"'{args[1]}' is not in a valid git repository");
+ Console.Out.WriteLine(string.IsNullOrWhiteSpace(test.StdErr) ? $"'{args[1]}' is not in a valid git repository" : test.StdErr.Trim());
desktop.Shutdown(-1);
return true;
}
@@ -420,7 +424,7 @@ private bool TryLaunchAsCoreEditor(IClassicDesktopStyleApplicationLifetime deskt
if (args is not { Length: > 1 } || !args[0].Equals("--core-editor", StringComparison.Ordinal))
return false;
- var file = args[1];
+ var file = args[1].Replace('\\', '/').Trim('\"').Trim();
if (!File.Exists(file))
{
desktop.Shutdown(-1);
@@ -456,12 +460,10 @@ private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
_ipcChannel = new Models.IpcChannel();
if (!_ipcChannel.IsFirstInstance)
{
- var arg = desktop.Args is { Length: > 0 } ? desktop.Args[0].Trim() : string.Empty;
+ var arg = desktop.Args is { Length: > 0 } ? desktop.Args[0] : string.Empty;
if (!string.IsNullOrEmpty(arg))
{
- if (arg.StartsWith('"') && arg.EndsWith('"'))
- arg = arg.Substring(1, arg.Length - 2).Trim();
-
+ arg = arg.Replace('\\', '/').TrimEnd('/').Trim('\"').Trim();
if (arg.Length > 0 && !Path.IsPathFullyQualified(arg))
arg = Path.GetFullPath(arg);
}
@@ -474,9 +476,22 @@ private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
Native.OS.SetupExternalTools();
Models.AvatarManager.Instance.Start();
+ if (this.TryGetFeature() is { } activatable)
+ {
+ activatable.Activated += (_, e) =>
+ {
+ if (e is FileActivatedEventArgs { Files: { Count: > 0 } } fileArgs)
+ _launcher?.TryOpenRepositoryFromPath(fileArgs.Files[0].Path.LocalPath);
+ };
+ }
+
string startupRepo = null;
- if (desktop.Args is { Length: 1 } && Directory.Exists(desktop.Args[0]))
- startupRepo = desktop.Args[0];
+ if (desktop.Args is { Length: 1 })
+ {
+ var arg = desktop.Args[0].Replace('\\', '/').TrimEnd('/').Trim('\"').Trim();
+ if (Directory.Exists(arg))
+ startupRepo = arg;
+ }
var pref = ViewModels.Preferences.Instance;
pref.SetCanModify();
@@ -522,6 +537,9 @@ private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
#region Check for Updates
private void Check4Update(bool manually = false)
{
+ if (_launcher != null)
+ _launcher.NewVersion = null;
+
Task.Run(async () =>
{
try
@@ -535,23 +553,20 @@ private void Check4Update(bool manually = false)
if (ver == null)
return;
- // Check if already up-to-date.
- if (!ver.IsNewVersion)
+ if (manually)
{
- if (manually)
+ if (ver.IsNewVersion)
+ ShowSelfUpdateResult(ver);
+ else
ShowSelfUpdateResult(new Models.AlreadyUpToDate());
- return;
}
-
- // Should not check ignored tag if this is called manually.
- if (!manually)
+ else if (_launcher != null)
{
- var pref = ViewModels.Preferences.Instance;
- if (ver.TagName == pref.IgnoreUpdateTag)
+ if (!ver.IsNewVersion || ver.TagName == ViewModels.Preferences.Instance.IgnoreUpdateTag)
return;
- }
- ShowSelfUpdateResult(ver);
+ _launcher.NewVersion = ver;
+ }
}
catch (Exception e)
{
diff --git a/src/Commands/AddSafeDirectory.cs b/src/Commands/AddSafeDirectory.cs
new file mode 100644
index 0000000000..52e190db69
--- /dev/null
+++ b/src/Commands/AddSafeDirectory.cs
@@ -0,0 +1,11 @@
+namespace SourceGit.Commands
+{
+ public class AddSafeDirectory : Command
+ {
+ public AddSafeDirectory(string ctx, string value)
+ {
+ Context = ctx;
+ Args = $"config --global --add safe.directory {value.Quoted()}";
+ }
+ }
+}
diff --git a/src/Commands/Blame.cs b/src/Commands/Blame.cs
index 0f247d736f..a0bc26ee4c 100644
--- a/src/Commands/Blame.cs
+++ b/src/Commands/Blame.cs
@@ -37,7 +37,7 @@ public Blame(string repo, string file, string revision, bool ignoreWhitespace)
ParseLine(line);
if (_result.IsBinary)
- break;
+ return _result;
}
if (_needUnifyCommitSHA)
@@ -49,6 +49,9 @@ public Blame(string repo, string file, string revision, bool ignoreWhitespace)
}
}
+ if (_content.Length > 0)
+ _content.Length--; // Remove the last newline character
+
_result.Content = _content.ToString();
return _result;
}
@@ -66,7 +69,8 @@ private void ParseLine(string line)
if (!match.Success)
return;
- _content.AppendLine(match.Groups[5].Value);
+ // Force using `\n` as line-ending
+ _content.Append(match.Groups[5].Value).Append('\n');
var commit = match.Groups[1].Value;
var file = match.Groups[2].Value.Trim();
diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs
index efb325d066..6ed017841e 100644
--- a/src/Commands/Branch.cs
+++ b/src/Commands/Branch.cs
@@ -42,15 +42,15 @@ public async Task SetUpstreamAsync(Models.Branch tracking)
return await ExecAsync().ConfigureAwait(false);
}
- public async Task DeleteLocalAsync()
+ public async Task DeleteLocalAsync(bool force)
{
- Args = $"branch -D {_name}";
+ Args = $"branch {(force ? "-D" : "-d")} {_name}";
return await ExecAsync().ConfigureAwait(false);
}
- public async Task DeleteRemoteAsync(string remote)
+ public async Task DeleteRemoteAsync(string remote, bool force)
{
- Args = $"branch -D -r {remote}/{_name}";
+ Args = $"branch {(force ? "-D" : "-d")} -r {remote}/{_name}";
return await ExecAsync().ConfigureAwait(false);
}
diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs
index 2c453b9a1f..14430805c2 100644
--- a/src/Commands/Command.cs
+++ b/src/Commands/Command.cs
@@ -62,7 +62,7 @@ public async Task ExecAsync()
lock (capturedLock)
{
if (captured is { Process: { HasExited: false } })
- captured.Process.Kill();
+ Native.OS.TerminateProcess(captured.Process);
}
});
}
@@ -158,8 +158,36 @@ protected async Task ReadToEndAsync()
protected ProcessStartInfo CreateGitStartInfo(bool redirect)
{
+ var useSetSid = CancellationToken.CanBeCanceled && Native.OS.SupportSetSid();
+ var selfExecFile = Environment.ProcessPath;
+ var builder = new StringBuilder(2048);
+
+ if (useSetSid)
+ builder.Append(Native.OS.GitExecutable.Quoted()).Append(' ');
+
+ builder
+ .Append("--no-pager -c core.quotepath=off -c credential.helper=")
+ .Append(Native.OS.CredentialHelper)
+ .Append(' ');
+
+ switch (Editor)
+ {
+ case EditorType.CoreEditor:
+ builder.Append($"""-c core.editor="\"{selfExecFile}\" --core-editor" """);
+ break;
+ case EditorType.RebaseEditor:
+ builder.Append($"""-c core.editor="\"{selfExecFile}\" --rebase-message-editor" -c sequence.editor="\"{selfExecFile}\" --rebase-todo-editor" -c rebase.abbreviateCommands=true """);
+ break;
+ default:
+ builder.Append("-c core.editor=true ");
+ break;
+ }
+
+ builder.Append(Args);
+
var start = new ProcessStartInfo();
- start.FileName = Native.OS.GitExecutable;
+ start.FileName = useSetSid ? Native.OS.GetSetSidExecutable() : Native.OS.GitExecutable;
+ start.Arguments = builder.ToString();
start.UseShellExecute = false;
start.CreateNoWindow = true;
@@ -172,7 +200,6 @@ protected ProcessStartInfo CreateGitStartInfo(bool redirect)
}
// Force using this app as SSH askpass program
- var selfExecFile = Process.GetCurrentProcess().MainModule!.FileName;
start.Environment.Add("SSH_ASKPASS", selfExecFile); // Can not use parameter here, because it invoked by SSH with `exec`
start.Environment.Add("SSH_ASKPASS_REQUIRE", "prefer");
start.Environment.Add("SOURCEGIT_LAUNCH_AS_ASKPASS", "TRUE");
@@ -181,37 +208,15 @@ protected ProcessStartInfo CreateGitStartInfo(bool redirect)
// If an SSH private key was provided, sets the environment.
if (!start.Environment.ContainsKey("GIT_SSH_COMMAND") && !string.IsNullOrEmpty(SSHKey))
- start.Environment.Add("GIT_SSH_COMMAND", $"ssh -i '{SSHKey}' -F '/dev/null'");
+ start.Environment.Add("GIT_SSH_COMMAND", $"ssh -i '{SSHKey}' -o AddKeysToAgent=yes");
- // Force using en_US.UTF-8 locale
- if (OperatingSystem.IsLinux())
+ // Force the C locale on Unix, so that git's output (including fatal errors) can always be parsed.
+ if (!OperatingSystem.IsWindows())
{
start.Environment.Add("LANG", "C");
start.Environment.Add("LC_ALL", "C");
}
- var builder = new StringBuilder(2048);
- builder
- .Append("--no-pager -c core.quotepath=off -c credential.helper=")
- .Append(Native.OS.CredentialHelper)
- .Append(' ');
-
- switch (Editor)
- {
- case EditorType.CoreEditor:
- builder.Append($"""-c core.editor="\"{selfExecFile}\" --core-editor" """);
- break;
- case EditorType.RebaseEditor:
- builder.Append($"""-c core.editor="\"{selfExecFile}\" --rebase-message-editor" -c sequence.editor="\"{selfExecFile}\" --rebase-todo-editor" -c rebase.abbreviateCommands=true """);
- break;
- default:
- builder.Append("-c core.editor=true ");
- break;
- }
-
- builder.Append(Args);
- start.Arguments = builder.ToString();
-
// Working directory
if (!string.IsNullOrEmpty(WorkingDirectory))
start.WorkingDirectory = WorkingDirectory;
diff --git a/src/Commands/CompareRevisions.cs b/src/Commands/CompareRevisions.cs
index 2e1062cfb4..c7ee58915a 100644
--- a/src/Commands/CompareRevisions.cs
+++ b/src/Commands/CompareRevisions.cs
@@ -7,7 +7,7 @@ namespace SourceGit.Commands
{
public partial class CompareRevisions : Command
{
- [GeneratedRegex(@"^([MAD])\s+(.+)$")]
+ [GeneratedRegex(@"^([MADT])\s+(.+)$")]
private static partial Regex REG_FORMAT();
[GeneratedRegex(@"^([CR])[0-9]{0,4}\s+(.+)$")]
private static partial Regex REG_RENAME_FORMAT();
@@ -73,6 +73,10 @@ public CompareRevisions(string repo, string start, string end, string path)
change.Set(Models.ChangeState.Deleted);
changes.Add(change);
break;
+ case 'T':
+ change.Set(Models.ChangeState.TypeChanged);
+ changes.Add(change);
+ break;
}
}
diff --git a/src/Commands/Config.cs b/src/Commands/Config.cs
index 75f4d738a2..78baee3cdf 100644
--- a/src/Commands/Config.cs
+++ b/src/Commands/Config.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Threading.Tasks;
namespace SourceGit.Commands
@@ -46,7 +45,7 @@ public async Task> ReadAllAsync()
Args = "config -l";
var output = await ReadToEndAsync().ConfigureAwait(false);
- var rs = new Dictionary();
+ var rs = new Dictionary(StringComparer.OrdinalIgnoreCase);
if (output.IsSuccess)
{
var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
@@ -55,7 +54,7 @@ public async Task> ReadAllAsync()
var parts = line.Split('=', 2);
if (parts.Length == 2)
{
- var key = parts[0].ToLower(CultureInfo.CurrentCulture); // Always use lower case for key
+ var key = parts[0];
var value = parts[1];
rs[key] = value;
}
diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs
index 4840d14e69..fb008aeee9 100644
--- a/src/Commands/Diff.cs
+++ b/src/Commands/Diff.cs
@@ -16,11 +16,29 @@ public partial class Diff : Command
[GeneratedRegex(@"^index\s([0-9a-f]{6,64})\.\.([0-9a-f]{6,64})(\s[1-9]{6})?")]
private static partial Regex REG_HASH_CHANGE();
- private const string PREFIX_LFS_NEW = "+version https://git-lfs.github.com/spec/";
- private const string PREFIX_LFS_DEL = "-version https://git-lfs.github.com/spec/";
- private const string PREFIX_LFS_MODIFY = " version https://git-lfs.github.com/spec/";
+ private const char PREFIX_CONTEXT = ' ';
+ private const char PREFIX_DELETED = '-';
+ private const char PREFIX_ADDED = '+';
+ private const char PREFIX_COMMAND = '\\';
- public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespace)
+ private const string FILE_MODE_OLD = "old mode ";
+ private const string FILE_MODE_NEW = "new mode ";
+ private const string FILE_MODE_DELETED = "deleted file mode ";
+ private const string FILE_MODE_ADDED = "new file mode ";
+
+ private const string LFS_SPECIFIER = "version https://git-lfs.github.com/spec/";
+ private const string LFS_OID_PREFIX = "oid sha256:";
+ private const string LFS_SIZE_PREFIX = "size ";
+
+ private const string SPECIAL_DIFF_START = "diff ";
+ private const string SPECIAL_BINARY = "Binary files ";
+ private const string SPECIAL_NO_NEWLINE = " No newline at end of file";
+ private const string SPECIAL_SUBMODULE = "Subproject commit ";
+
+ private const int MAX_INLINE_CONTENT_LENGTH = 1024;
+ private const int MAX_INLINE_CHUNKS_PER_LINE = 16;
+
+ public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignoreWhitespace, bool ignoreCRAtEOL)
{
_result.TextDiff = new Models.TextDiff();
@@ -28,12 +46,12 @@ public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespa
Context = repo;
var builder = new StringBuilder(256);
- builder.Append("diff --no-color --no-ext-diff --patch ");
- if (Models.DiffOption.IgnoreCRAtEOL)
- builder.Append("--ignore-cr-at-eol ");
+ builder.Append("diff --no-color --no-ext-diff --full-index --patch ");
if (ignoreWhitespace)
- builder.Append("--ignore-space-change ");
- builder.Append("--unified=").Append(unified).Append(' ');
+ builder.Append("--ignore-space-change --ignore-blank-lines ");
+ if (ignoreCRAtEOL)
+ builder.Append("--ignore-cr-at-eol ");
+ builder.Append("--unified=").Append(numContextLines).Append(' ');
builder.Append(opt.ToString());
Args = builder.ToString();
@@ -50,22 +68,25 @@ public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespa
using var ms = new MemoryStream();
await proc.StandardOutput.BaseStream.CopyToAsync(ms, CancellationToken).ConfigureAwait(false);
- var bytes = ms.ToArray();
- var start = 0;
- while (start < bytes.Length)
+ if (ms.TryGetBuffer(out var buffer))
{
- var end = Array.IndexOf(bytes, (byte)'\n', start);
- if (end < 0)
+ var start = buffer.Offset;
+ var end = buffer.Offset + buffer.Count;
+ while (start < end)
{
- ParseLine(bytes[start..]);
- break;
- }
+ var lineEnd = Array.IndexOf(buffer.Array, (byte)'\n', start);
+ if (lineEnd < 0)
+ {
+ ParseLine(buffer[start..]);
+ break;
+ }
- ParseLine(bytes[start..end]);
- if (_result.IsBinary)
- break;
+ ParseLine(buffer[start..lineEnd]);
+ if (_result.IsBinary)
+ break;
- start = end + 1;
+ start = lineEnd + 1;
+ }
}
await proc.WaitForExitAsync(CancellationToken).ConfigureAwait(false);
@@ -75,171 +96,229 @@ public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespa
// Ignore exceptions.
}
- if (_result.IsBinary || _result.IsLFS || _result.TextDiff.Lines.Count == 0)
+ if (_isLFS || _result.IsBinary || _result.TextDiff.Lines.Count == 0)
{
_result.TextDiff = null;
}
else
{
- ProcessInlineHighlights();
+ if (_isInChunk)
+ {
+ ProcessInlineHighlights();
+ _isInChunk = false;
+ }
+
+ if (_result.TextDiff.Lines.Count < 4)
+ {
+ var isSubmoduleChange = true;
+
+ for (int i = 1; i < _result.TextDiff.Lines.Count; i++)
+ {
+ var line = _result.TextDiff.Lines[i];
+ if (!line.Content.StartsWith(SPECIAL_SUBMODULE, StringComparison.Ordinal))
+ {
+ isSubmoduleChange = false;
+ break;
+ }
+ }
+
+ if (isSubmoduleChange)
+ {
+ _result.IsSubmoduleChange = true;
+ _result.TextDiff = null;
+ return _result;
+ }
+ }
+
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
+ _result.TextDiff.OldMode = _result.OldMode;
+ _result.TextDiff.NewMode = _result.NewMode;
+ _result.TextDiff.OldHash = _result.OldHash;
+ _result.TextDiff.NewHash = _result.NewHash;
}
return _result;
}
- private void ParseLine(byte[] lineBytes)
+ private void ParseLine(ArraySegment lineBytes)
{
- var line = Encoding.UTF8.GetString(lineBytes);
- if (ParseFileModeChange(line))
- return;
-
- if (ParseLFSChange(line))
+ // Decode line bytes to UTF-8 string
+ var line = Encoding.UTF8.GetString(lineBytes.Array, lineBytes.Offset, lineBytes.Count);
+ if (line.Length == 0)
return;
- if (_result.TextDiff.Lines.Count == 0)
+ // If we are reading a chunk-body, try to read the current line as chunk-body first (because
+ // there are usually more chunk-body lines than chunk-indicator lines).
+ if (_isInChunk)
{
- if (line.StartsWith("Binary", StringComparison.Ordinal))
- {
- _result.IsBinary = true;
+ if (ParseChunkBodyLine(line, lineBytes))
return;
- }
- if (string.IsNullOrEmpty(_result.OldHash))
- {
- var match = REG_HASH_CHANGE().Match(line);
- if (!match.Success)
- return;
+ ProcessInlineHighlights();
+ _isInChunk = false;
+ }
- _result.OldHash = match.Groups[1].Value;
- _result.NewHash = match.Groups[2].Value;
- }
- else
- {
- var match = REG_INDICATOR().Match(line);
- if (!match.Success)
- return;
-
- _oldLine = int.Parse(match.Groups[1].Value);
- _newLine = int.Parse(match.Groups[2].Value);
- _last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, lineBytes, 0, 0);
- _result.TextDiff.Lines.Add(_last);
- }
+ // If the current line is not a chunk-body, try to parse it as chunk-indicator
+ if (ParseChunkStartLine(line))
+ {
+ _isInChunk = true;
+ return;
}
- else
+
+ // Fallback to diff headers to support type-changed diff (multiple headers).
+ ParseDiffHeaderLine(line);
+ }
+
+ private void ParseDiffHeaderLine(string line)
+ {
+ if (line.StartsWith(SPECIAL_DIFF_START, StringComparison.Ordinal))
+ return;
+
+ if (ParseFileModeChange(line))
+ return;
+
+ var match = REG_HASH_CHANGE().Match(line);
+ if (match.Success)
{
- if (line.Length == 0)
- {
- ProcessInlineHighlights();
- _last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", [], _oldLine, _newLine);
- _result.TextDiff.Lines.Add(_last);
- _oldLine++;
- _newLine++;
- return;
- }
+ if (string.IsNullOrEmpty(_result.OldHash))
+ _result.OldHash = match.Groups[1].Value;
+ _result.NewHash = match.Groups[2].Value;
+ return;
+ }
- var ch = line[0];
- if (ch == '-')
- {
- _result.TextDiff.DeletedLines++;
- _last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), lineBytes[1..], _oldLine, 0);
- _deleted.Add(_last);
- _oldLine++;
- }
- else if (ch == '+')
- {
- _result.TextDiff.AddedLines++;
- _last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), lineBytes[1..], 0, _newLine);
- _added.Add(_last);
- _newLine++;
- }
- else if (ch != '\\')
- {
- ProcessInlineHighlights();
- var match = REG_INDICATOR().Match(line);
- if (match.Success)
- {
- _oldLine = int.Parse(match.Groups[1].Value);
- _newLine = int.Parse(match.Groups[2].Value);
- _last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, lineBytes, 0, 0);
- _result.TextDiff.Lines.Add(_last);
- }
- else
- {
- _last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), lineBytes[1..], _oldLine, _newLine);
- _result.TextDiff.Lines.Add(_last);
- _oldLine++;
- _newLine++;
- }
- }
- else if (line.Equals("\\ No newline at end of file", StringComparison.Ordinal))
- {
- _last.NoNewLineEndOfFile = true;
- }
+ if (line.StartsWith(SPECIAL_BINARY, StringComparison.Ordinal))
+ _result.IsBinary = true;
+ }
+
+ private bool ParseChunkStartLine(string line)
+ {
+ var match = REG_INDICATOR().Match(line);
+ if (match.Success)
+ {
+ _oldLine = int.Parse(match.Groups[1].Value);
+ _newLine = int.Parse(match.Groups[2].Value);
+ _last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, null, 0, 0);
+ _result.TextDiff.Lines.Add(_last);
+ return true;
}
+
+ return false;
}
- private bool ParseFileModeChange(string line)
+ private bool ParseChunkBodyLine(string line, ArraySegment lineBytes)
{
- if (line.StartsWith("old mode ", StringComparison.Ordinal))
+ var prefix = line[0];
+ var content = line.Substring(1);
+ var rawContent = lineBytes[1..].ToArray();
+ if (ParseLFSChange(prefix, content))
+ return true;
+
+ if (prefix == PREFIX_DELETED)
{
- _result.OldMode = line.Substring(9);
+ _result.TextDiff.DeletedLines++;
+ _last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, content, rawContent, _oldLine, 0);
+ _deleted.Add(_last);
+ _oldLine++;
return true;
}
- if (line.StartsWith("new mode ", StringComparison.Ordinal))
+ if (prefix == PREFIX_ADDED)
{
- _result.NewMode = line.Substring(9);
+ _result.TextDiff.AddedLines++;
+ _last = new Models.TextDiffLine(Models.TextDiffLineType.Added, content, rawContent, 0, _newLine);
+ _added.Add(_last);
+ _newLine++;
return true;
}
- if (line.StartsWith("deleted file mode ", StringComparison.Ordinal))
+ if (prefix == PREFIX_CONTEXT)
{
- _result.OldMode = line.Substring(18);
+ ProcessInlineHighlights();
+
+ _last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, content, rawContent, _oldLine, _newLine);
+ _result.TextDiff.Lines.Add(_last);
+ _oldLine++;
+ _newLine++;
return true;
}
- if (line.StartsWith("new file mode ", StringComparison.Ordinal))
+ if (prefix == PREFIX_COMMAND)
{
- _result.NewMode = line.Substring(14);
+ if (content.Equals(SPECIAL_NO_NEWLINE, StringComparison.Ordinal))
+ _last.NoNewLineEndOfFile = true;
return true;
}
return false;
}
- private bool ParseLFSChange(string line)
+ private bool ParseFileModeChange(string line)
{
- if (_result.IsLFS)
+ if (line.StartsWith(FILE_MODE_OLD, StringComparison.Ordinal))
{
- if (line.StartsWith("-oid sha256:", StringComparison.Ordinal))
- _result.LFSDiff.Old.Oid = line.Substring(12);
- else if (line.StartsWith("-size ", StringComparison.Ordinal))
- _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
- else if (line.StartsWith("+oid sha256:", StringComparison.Ordinal))
- _result.LFSDiff.New.Oid = line.Substring(12);
- else if (line.StartsWith("+size ", StringComparison.Ordinal))
- _result.LFSDiff.New.Size = long.Parse(line.AsSpan(6));
- else if (line.StartsWith(" size ", StringComparison.Ordinal))
- _result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
+ _result.OldMode = int.Parse(line.AsSpan(9));
+ return true;
+ }
+ if (line.StartsWith(FILE_MODE_NEW, StringComparison.Ordinal))
+ {
+ _result.NewMode = int.Parse(line.AsSpan(9));
return true;
}
- if (_result.TextDiff.Lines.Count != 1)
- return false;
+ if (line.StartsWith(FILE_MODE_DELETED, StringComparison.Ordinal))
+ {
+ _result.OldMode = int.Parse(line.AsSpan(18));
+ return true;
+ }
- var isLFS = (_oldLine == 1 && _newLine == 1 && line.StartsWith(PREFIX_LFS_MODIFY, StringComparison.Ordinal)) ||
- (_oldLine == 1 && _newLine == 0 && line.StartsWith(PREFIX_LFS_DEL, StringComparison.Ordinal)) ||
- (_oldLine == 0 && _newLine == 1 && line.StartsWith(PREFIX_LFS_NEW, StringComparison.Ordinal));
+ if (line.StartsWith(FILE_MODE_ADDED, StringComparison.Ordinal))
+ {
+ _result.NewMode = int.Parse(line.AsSpan(14));
+ return true;
+ }
- if (isLFS)
+ return false;
+ }
+
+ private bool ParseLFSChange(char prefix, string content)
+ {
+ if (_isLFS)
{
- _result.IsLFS = true;
- _result.LFSDiff = new Models.LFSDiff();
+ if (prefix == PREFIX_DELETED)
+ {
+ if (content.StartsWith(LFS_OID_PREFIX, StringComparison.Ordinal))
+ _result.LFSDiff.Old.Oid = content.Substring(11);
+ else if (content.StartsWith(LFS_SIZE_PREFIX, StringComparison.Ordinal))
+ _result.LFSDiff.Old.Size = long.Parse(content.AsSpan(5));
+ }
+ else if (prefix == PREFIX_ADDED)
+ {
+ if (content.StartsWith(LFS_OID_PREFIX, StringComparison.Ordinal))
+ _result.LFSDiff.New.Oid = content.Substring(11);
+ else if (content.StartsWith(LFS_SIZE_PREFIX, StringComparison.Ordinal))
+ _result.LFSDiff.New.Size = long.Parse(content.AsSpan(5));
+ }
+ else if (prefix == PREFIX_CONTEXT)
+ {
+ if (content.StartsWith(LFS_SIZE_PREFIX, StringComparison.Ordinal))
+ _result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(content.AsSpan(5));
+ }
return true;
}
+ if ((_oldLine == 1 && _newLine == 1 && prefix == PREFIX_CONTEXT) ||
+ (_oldLine == 1 && _newLine == 0 && prefix == PREFIX_DELETED) ||
+ (_oldLine == 0 && _newLine == 1 && prefix == PREFIX_ADDED))
+ {
+ if (content.StartsWith(LFS_SPECIFIER, StringComparison.Ordinal))
+ {
+ _isLFS = true;
+ _result.LFSDiff = new Models.LFSDiff();
+ return true;
+ }
+ }
+
return false;
}
@@ -254,11 +333,11 @@ private void ProcessInlineHighlights()
var left = _deleted[i];
var right = _added[i];
- if (left.Content.Length > 1024 || right.Content.Length > 1024)
+ if (left.Content.Length > MAX_INLINE_CONTENT_LENGTH || right.Content.Length > MAX_INLINE_CONTENT_LENGTH)
continue;
var chunks = Models.TextInlineChange.Compare(left.Content, right.Content);
- if (chunks.Count > 4)
+ if (chunks.Count > MAX_INLINE_CHUNKS_PER_LINE)
continue;
foreach (var chunk in chunks)
@@ -289,5 +368,7 @@ private void ProcessInlineHighlights()
private Models.TextDiffLine _last = null;
private int _oldLine = 0;
private int _newLine = 0;
+ private bool _isInChunk = false;
+ private bool _isLFS = false;
}
}
diff --git a/src/Commands/DoesBranchExistOnRemote.cs b/src/Commands/DoesBranchExistOnRemote.cs
new file mode 100644
index 0000000000..3a971c3e8f
--- /dev/null
+++ b/src/Commands/DoesBranchExistOnRemote.cs
@@ -0,0 +1,21 @@
+using System.Threading.Tasks;
+
+namespace SourceGit.Commands
+{
+ public class DoesBranchExistOnRemote : Command
+ {
+ public DoesBranchExistOnRemote(string repo, Models.Remote remote, Models.Branch branch)
+ {
+ WorkingDirectory = repo;
+ SSHKey = remote.PrivateSSHKey;
+ RaiseError = false;
+ Args = $"ls-remote {remote.Name} {branch.Name}";
+ }
+
+ public async Task GetResultAsync()
+ {
+ var rs = await ReadToEndAsync();
+ return rs.IsSuccess && rs.StdOut.Trim().Length > 0;
+ }
+ }
+}
diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs
index 9143612576..679ffc9ac6 100644
--- a/src/Commands/Fetch.cs
+++ b/src/Commands/Fetch.cs
@@ -1,53 +1,40 @@
using System.Text;
-using System.Threading.Tasks;
namespace SourceGit.Commands
{
public class Fetch : Command
{
- public Fetch(string repo, string remote, bool noTags, bool force)
+ public Fetch(string repo, Models.Remote remote, bool noTags, bool force)
{
- _remote = remote;
-
WorkingDirectory = repo;
Context = repo;
+ SSHKey = remote.PrivateSSHKey;
var builder = new StringBuilder(512);
builder.Append("fetch --progress --verbose ");
builder.Append(noTags ? "--no-tags " : "--tags ");
if (force)
builder.Append("--force ");
- builder.Append(remote);
+ builder.Append(remote.Name);
Args = builder.ToString();
}
- public Fetch(string repo, string remote)
+ public Fetch(string repo, Models.Remote remote)
{
- _remote = remote;
-
WorkingDirectory = repo;
Context = repo;
+ SSHKey = remote.PrivateSSHKey;
RaiseError = false;
-
- Args = $"fetch --progress --verbose {remote}";
+ Args = $"fetch --progress --verbose {remote.Name}";
}
- public Fetch(string repo, Models.Branch local, Models.Branch remote)
+ public Fetch(string repo, Models.Remote remote, Models.Branch remoteBranch, Models.Branch local)
{
- _remote = remote.Remote;
-
WorkingDirectory = repo;
Context = repo;
- Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}";
+ SSHKey = remote.PrivateSSHKey;
+ Args = $"fetch --progress --verbose {remote.Name} {remoteBranch.Name}:{local.Name}";
}
-
- public async Task RunAsync()
- {
- SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{_remote}.sshkey").ConfigureAwait(false);
- return await ExecAsync().ConfigureAwait(false);
- }
-
- private readonly string _remote;
}
}
diff --git a/src/Commands/GetFileChangeForAI.cs b/src/Commands/GetFileChangeForAI.cs
index 96ef162dcb..128d0aba64 100644
--- a/src/Commands/GetFileChangeForAI.cs
+++ b/src/Commands/GetFileChangeForAI.cs
@@ -6,13 +6,16 @@ namespace SourceGit.Commands
{
public class GetFileChangeForAI : Command
{
- public GetFileChangeForAI(string repo, string file, string originalFile)
+ public GetFileChangeForAI(string repo, string file, string originalFile, string amendParent)
{
WorkingDirectory = repo;
Context = repo;
var builder = new StringBuilder();
- builder.Append("diff --no-color --no-ext-diff --diff-algorithm=minimal --cached -- ");
+ builder.Append("diff --no-color --no-ext-diff --diff-algorithm=minimal --cached ");
+ if (!string.IsNullOrEmpty(amendParent))
+ builder.Append(amendParent).Append(' ');
+ builder.Append("-- ");
if (!string.IsNullOrEmpty(originalFile) && !file.Equals(originalFile, StringComparison.Ordinal))
builder.Append(originalFile.Quoted()).Append(' ');
builder.Append(file.Quoted());
diff --git a/src/Commands/GitFlow.cs b/src/Commands/GitFlow.cs
index 8e29e6b50e..0438d76de8 100644
--- a/src/Commands/GitFlow.cs
+++ b/src/Commands/GitFlow.cs
@@ -3,53 +3,80 @@
namespace SourceGit.Commands
{
- public static class GitFlow
+ public class GitFlow : Command
{
- public static async Task InitAsync(string repo, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log)
+ public GitFlow(string repo)
{
- var config = new Config(repo);
- await config.SetAsync("gitflow.branch.master", master).ConfigureAwait(false);
+ WorkingDirectory = repo;
+ Context = repo;
+ }
+
+ public async Task InitAsync(string production, string develop, string feature, string release, string hotfix, string tag)
+ {
+ if (Native.OS.GitFlowVersion == Models.GitFlowVersion.Next)
+ {
+ var builder = new StringBuilder();
+ builder
+ .Append("flow init --preset=classic ")
+ .Append("--main=").Append(production.Quoted()).Append(' ')
+ .Append("--develop=").Append(develop.Quoted()).Append(' ')
+ .Append("--feature=").Append(feature).Append(' ')
+ .Append("--bugfix=bugfix/ ")
+ .Append("--release=").Append(release).Append(' ')
+ .Append("--hotfix=").Append(hotfix).Append(' ')
+ .Append("--support=support/");
+
+ if (!string.IsNullOrEmpty(tag))
+ builder.Append(" --tag=").Append(tag);
+
+ Args = builder.ToString();
+ return await ExecAsync().ConfigureAwait(false);
+ }
+
+ var config = new Config(WorkingDirectory);
+ await config.SetAsync("gitflow.branch.master", production).ConfigureAwait(false);
await config.SetAsync("gitflow.branch.develop", develop).ConfigureAwait(false);
await config.SetAsync("gitflow.prefix.feature", feature).ConfigureAwait(false);
await config.SetAsync("gitflow.prefix.bugfix", "bugfix/").ConfigureAwait(false);
await config.SetAsync("gitflow.prefix.release", release).ConfigureAwait(false);
await config.SetAsync("gitflow.prefix.hotfix", hotfix).ConfigureAwait(false);
await config.SetAsync("gitflow.prefix.support", "support/").ConfigureAwait(false);
- await config.SetAsync("gitflow.prefix.versiontag", version, true).ConfigureAwait(false);
+ await config.SetAsync("gitflow.prefix.versiontag", tag, true).ConfigureAwait(false);
- var init = new Command();
- init.WorkingDirectory = repo;
- init.Context = repo;
- init.Args = "flow init -d";
- return await init.Use(log).ExecAsync().ConfigureAwait(false);
+ Args = "flow init -d";
+ return await ExecAsync().ConfigureAwait(false);
}
- public static async Task StartAsync(string repo, Models.GitFlowBranchType type, string name, Models.ICommandLog log)
+ public async Task StartAsync(Models.GitFlowBranchType type, string name, Models.Branch based)
{
- var start = new Command();
- start.WorkingDirectory = repo;
- start.Context = repo;
+ var builder = new StringBuilder();
+ builder.Append("flow ");
switch (type)
{
case Models.GitFlowBranchType.Feature:
- start.Args = $"flow feature start {name}";
+ builder.Append("feature");
break;
case Models.GitFlowBranchType.Release:
- start.Args = $"flow release start {name}";
+ builder.Append("release");
break;
case Models.GitFlowBranchType.Hotfix:
- start.Args = $"flow hotfix start {name}";
+ builder.Append("hotfix");
break;
default:
- Models.Notification.Send(repo, "Bad git-flow branch type!!!", true);
+ RaiseException("Bad git-flow branch type!!!");
return false;
}
- return await start.Use(log).ExecAsync().ConfigureAwait(false);
+ builder.Append(" start ").Append(name);
+ if (based != null)
+ builder.Append(' ').Append(based.Name);
+
+ Args = builder.ToString();
+ return await ExecAsync().ConfigureAwait(false);
}
- public static async Task FinishAsync(string repo, Models.GitFlowBranchType type, string name, bool squash, bool keepBranch, Models.ICommandLog log)
+ public async Task FinishAsync(Models.GitFlowBranchType type, string name, bool rebase, bool squash, bool keepBranch)
{
var builder = new StringBuilder();
builder.Append("flow ");
@@ -66,22 +93,21 @@ public static async Task FinishAsync(string repo, Models.GitFlowBranchType
builder.Append("hotfix");
break;
default:
- Models.Notification.Send(repo, "Bad git-flow branch type!!!", true);
+ RaiseException("Bad git-flow branch type!!!");
return false;
}
builder.Append(" finish ");
+ if (rebase)
+ builder.Append("--rebase ");
if (squash)
builder.Append("--squash ");
if (keepBranch)
- builder.Append("-k ");
+ builder.Append("--keep ");
builder.Append(name);
- var finish = new Command();
- finish.WorkingDirectory = repo;
- finish.Context = repo;
- finish.Args = builder.ToString();
- return await finish.Use(log).ExecAsync().ConfigureAwait(false);
+ Args = builder.ToString();
+ return await ExecAsync().ConfigureAwait(false);
}
}
}
diff --git a/src/Commands/InteractiveRebase.cs b/src/Commands/InteractiveRebase.cs
index 6ea67af2d8..6b61b9d2ab 100644
--- a/src/Commands/InteractiveRebase.cs
+++ b/src/Commands/InteractiveRebase.cs
@@ -11,7 +11,7 @@ public InteractiveRebase(string repo, string basedOn, bool autoStash, bool noVer
Editor = EditorType.RebaseEditor;
var builder = new StringBuilder(512);
- builder.Append("rebase -i --autosquash ");
+ builder.Append("-c core.commentChar=\"^\" -c core.commentString=\"±\" rebase -i --autosquash ");
if (autoStash)
builder.Append("--autostash ");
if (noVerify)
diff --git a/src/Commands/IsConflictResolved.cs b/src/Commands/IsConflictResolved.cs
deleted file mode 100644
index e5b752d354..0000000000
--- a/src/Commands/IsConflictResolved.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System.Threading.Tasks;
-
-namespace SourceGit.Commands
-{
- public class IsConflictResolved : Command
- {
- public IsConflictResolved(string repo, Models.Change change)
- {
- var opt = new Models.DiffOption(change, true);
-
- WorkingDirectory = repo;
- Context = repo;
- Args = $"diff --no-color --no-ext-diff -a --ignore-cr-at-eol --check {opt}";
- }
-
- public bool GetResult()
- {
- return ReadToEnd().IsSuccess;
- }
-
- public async Task GetResultAsync()
- {
- var rs = await ReadToEndAsync().ConfigureAwait(false);
- return rs.IsSuccess;
- }
- }
-}
diff --git a/src/Commands/Merge.cs b/src/Commands/Merge.cs
index bd11b5779a..bcb8811f9a 100644
--- a/src/Commands/Merge.cs
+++ b/src/Commands/Merge.cs
@@ -9,7 +9,6 @@ public Merge(string repo, string source, string mode, bool edit)
{
WorkingDirectory = repo;
Context = repo;
- Editor = EditorType.CoreEditor;
var builder = new StringBuilder();
builder.Append("merge --progress ");
diff --git a/src/Commands/MergeBase.cs b/src/Commands/MergeBase.cs
new file mode 100644
index 0000000000..2e349a84ba
--- /dev/null
+++ b/src/Commands/MergeBase.cs
@@ -0,0 +1,32 @@
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace SourceGit.Commands
+{
+ public partial class MergeBase : Command
+ {
+ [GeneratedRegex(@"^[0-9a-f]{8,64}$")]
+ private static partial Regex REG_HEX();
+
+ public MergeBase(string repo, string rev1, string rev2)
+ {
+ WorkingDirectory = repo;
+ Context = repo;
+ RaiseError = false;
+ Args = $"merge-base {rev1} {rev2}";
+ }
+
+ public async Task GetResultAsync()
+ {
+ var rs = await ReadToEndAsync().ConfigureAwait(false);
+ if (!rs.IsSuccess)
+ return string.Empty;
+
+ var trimmed = rs.StdOut.Trim();
+ if (REG_HEX().IsMatch(trimmed))
+ return trimmed;
+
+ return string.Empty;
+ }
+ }
+}
diff --git a/src/Commands/MergeTree.cs b/src/Commands/MergeTree.cs
new file mode 100644
index 0000000000..41e9ea28aa
--- /dev/null
+++ b/src/Commands/MergeTree.cs
@@ -0,0 +1,34 @@
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace SourceGit.Commands
+{
+ public class MergeTree : Command
+ {
+ public MergeTree(string repo, string source, string dest)
+ {
+ WorkingDirectory = repo;
+ Args = $"merge-tree --write-tree {source} {dest}";
+ }
+
+ public async Task GetExitCodeAsync()
+ {
+ using var proc = new Process();
+ proc.StartInfo = CreateGitStartInfo(false);
+
+ var exitCode = -1;
+ try
+ {
+ proc.Start();
+ await proc.WaitForExitAsync().ConfigureAwait(false);
+ exitCode = proc.ExitCode;
+ }
+ catch
+ {
+ // Ignore any exceptions and just return -1
+ }
+
+ return exitCode;
+ }
+ }
+}
diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs
index e121445bac..7e8f4f731c 100644
--- a/src/Commands/Pull.cs
+++ b/src/Commands/Pull.cs
@@ -1,32 +1,26 @@
using System.Text;
-using System.Threading.Tasks;
namespace SourceGit.Commands
{
public class Pull : Command
{
- public Pull(string repo, string remote, string branch, bool useRebase)
+ public Pull(string repo, Models.Remote remote, Models.Branch remoteBranch, bool useRebase)
{
- _remote = remote;
-
WorkingDirectory = repo;
Context = repo;
+ SSHKey = remote.PrivateSSHKey;
var builder = new StringBuilder(512);
- builder.Append("pull --verbose --progress ");
- if (useRebase)
- builder.Append("--rebase=true ");
- builder.Append(remote).Append(' ').Append(branch);
+ builder
+ .Append("pull --verbose --progress --rebase=")
+ .Append(useRebase ? "true" : "false")
+ .Append(' ')
+ .Append(remote.Name);
- Args = builder.ToString();
- }
+ if (remoteBranch != null)
+ builder.Append(' ').Append(remoteBranch.Name);
- public async Task RunAsync()
- {
- SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{_remote}.sshkey").ConfigureAwait(false);
- return await ExecAsync().ConfigureAwait(false);
+ Args = builder.ToString();
}
-
- private readonly string _remote;
}
}
diff --git a/src/Commands/Push.cs b/src/Commands/Push.cs
index 44394dc412..e04e81ef94 100644
--- a/src/Commands/Push.cs
+++ b/src/Commands/Push.cs
@@ -1,16 +1,14 @@
using System.Text;
-using System.Threading.Tasks;
namespace SourceGit.Commands
{
public class Push : Command
{
- public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force)
+ public Push(string repo, Models.Branch local, Models.Remote remote, Models.Branch remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force, bool noVerify)
{
- _remote = remote;
-
WorkingDirectory = repo;
Context = repo;
+ SSHKey = remote.PrivateSSHKey;
var builder = new StringBuilder(1024);
builder.Append("push --progress --verbose ");
@@ -22,33 +20,41 @@ public Push(string repo, string local, string remote, string remoteBranch, bool
builder.Append("-u ");
if (force)
builder.Append("--force-with-lease ");
+ if (noVerify)
+ builder.Append("--no-verify ");
- builder.Append(remote).Append(' ').Append(local).Append(':').Append(remoteBranch);
+ builder.Append(remote.Name).Append(' ').Append(local.Name).Append(':').Append(remoteBranch.Name);
Args = builder.ToString();
}
- public Push(string repo, string remote, string refname, bool isDelete)
+ public Push(string repo, Models.Commit revision, Models.Remote remote, Models.Branch remoteBranch, bool force)
{
- _remote = remote;
+ WorkingDirectory = repo;
+ Context = repo;
+ SSHKey = remote.PrivateSSHKey;
+
+ var builder = new StringBuilder(1024);
+ builder.Append("push --progress --verbose ");
+ if (force)
+ builder.Append("--force-with-lease ");
+ builder.Append(remote.Name).Append(' ').Append(revision.SHA).Append(':').Append(remoteBranch.Name);
+ Args = builder.ToString();
+ }
+
+ public Push(string repo, Models.Remote remote, string refname, bool isDelete)
+ {
WorkingDirectory = repo;
Context = repo;
+ SSHKey = remote.PrivateSSHKey;
var builder = new StringBuilder(512);
builder.Append("push ");
if (isDelete)
builder.Append("--delete ");
- builder.Append(remote).Append(' ').Append(refname);
+ builder.Append(remote.Name).Append(' ').Append(refname);
Args = builder.ToString();
}
-
- public async Task RunAsync()
- {
- SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{_remote}.sshkey").ConfigureAwait(false);
- return await ExecAsync().ConfigureAwait(false);
- }
-
- private readonly string _remote;
}
}
diff --git a/src/Commands/QueryAuthors.cs b/src/Commands/QueryAuthors.cs
new file mode 100644
index 0000000000..d5e443d5e4
--- /dev/null
+++ b/src/Commands/QueryAuthors.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace SourceGit.Commands
+{
+ public class QueryAuthors : Command
+ {
+ public QueryAuthors(string repo)
+ {
+ WorkingDirectory = repo;
+ Context = repo;
+ RaiseError = false;
+ Args = "log -100000 --all --format=%aN±%aE";
+ }
+
+ public async Task> GetResultAsync()
+ {
+ var rs = await ReadToEndAsync().ConfigureAwait(false);
+ if (!rs.IsSuccess)
+ return [];
+
+ var start = 0;
+ var end = rs.StdOut.IndexOf('\n', start);
+ var added = new HashSet();
+ var users = new List();
+ while (end > 0)
+ {
+ var line = rs.StdOut.Substring(start, end - start);
+ if (!string.IsNullOrEmpty(line) && !added.Contains(line))
+ {
+ var user = Models.User.FindOrAdd(line);
+ users.Add(user);
+ added.Add(line);
+ }
+
+ start = end + 1;
+ if (start >= rs.StdOut.Length - 1)
+ break;
+
+ end = rs.StdOut.IndexOf('\n', start);
+ }
+
+ return users;
+ }
+ }
+}
diff --git a/src/Commands/QueryCommitChildren.cs b/src/Commands/QueryCommitChildren.cs
deleted file mode 100644
index 7e7e888773..0000000000
--- a/src/Commands/QueryCommitChildren.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace SourceGit.Commands
-{
- public class QueryCommitChildren : Command
- {
- public QueryCommitChildren(string repo, string commit, int max)
- {
- WorkingDirectory = repo;
- Context = repo;
- _commit = commit;
- Args = $"rev-list -{max} --parents --branches --remotes --ancestry-path ^{commit}";
- }
-
- public async Task> GetResultAsync()
- {
- var rs = await ReadToEndAsync().ConfigureAwait(false);
- var outs = new List();
- if (rs.IsSuccess)
- {
- var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
- foreach (var line in lines)
- {
- if (line.Contains(_commit))
- outs.Add(line.Substring(0, _commit.Length));
- }
- }
-
- return outs;
- }
-
- private string _commit;
- }
-}
diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs
index 0005abd6e5..1a5073988d 100644
--- a/src/Commands/QueryCommits.cs
+++ b/src/Commands/QueryCommits.cs
@@ -28,10 +28,6 @@ public QueryCommits(string repo, string filter, Models.CommitSearchMethod method
{
builder.Append("-i --author=").Append(filter.Quoted());
}
- else if (method == Models.CommitSearchMethod.ByCommitter)
- {
- builder.Append("-i --committer=").Append(filter.Quoted());
- }
else if (method == Models.CommitSearchMethod.ByMessage)
{
var words = filter.Split([' ', '\t', '\r'], StringSplitOptions.RemoveEmptyEntries);
@@ -80,7 +76,8 @@ public QueryCommits(string repo, string filter, Models.CommitSearchMethod method
commit.Subject = parts[7];
commits.Add(commit);
- findHead |= commit.IsMerged;
+ if (!findHead && commit.IsMerged)
+ findHead = true;
}
await proc.WaitForExitAsync().ConfigureAwait(false);
diff --git a/src/Commands/QueryConflictFileState.cs b/src/Commands/QueryConflictFileState.cs
new file mode 100644
index 0000000000..f9a52ee121
--- /dev/null
+++ b/src/Commands/QueryConflictFileState.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace SourceGit.Commands
+{
+ public class QueryConflictFileState : Command
+ {
+ public QueryConflictFileState(string repo, Models.Change change)
+ {
+ var opt = new Models.DiffOption(change, true);
+
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"diff --no-color --no-ext-diff --no-textconv --full-index --patch {opt}";
+ }
+
+ public async Task GetResultAsync()
+ {
+ try
+ {
+ using var proc = new Process();
+ proc.StartInfo = CreateGitStartInfo(true);
+ proc.Start();
+
+ var isBinary = false;
+ var tokenCount = 0;
+
+ while (await proc.StandardOutput.ReadLineAsync().ConfigureAwait(false) is { } line)
+ {
+ if (isBinary)
+ continue;
+
+ if (line.StartsWith("Binary files ", StringComparison.Ordinal))
+ isBinary = true;
+ else if (line.StartsWith("++<<<<<<<", StringComparison.Ordinal) ||
+ line.StartsWith("++=======", StringComparison.Ordinal) ||
+ line.StartsWith("++>>>>>>>", StringComparison.Ordinal))
+ tokenCount++;
+ }
+
+ await proc.WaitForExitAsync().ConfigureAwait(false);
+
+ if (isBinary)
+ return Models.ConflictFileState.UnmergedBinary;
+
+ return tokenCount == 0 ? Models.ConflictFileState.Resolved : Models.ConflictFileState.UnmergedText;
+ }
+ catch
+ {
+ return Models.ConflictFileState.Unknown;
+ }
+ }
+ }
+}
diff --git a/src/Commands/QueryCurrentBranchCommitHashes.cs b/src/Commands/QueryCurrentBranchCommitHashes.cs
index a795e1f548..393160602b 100644
--- a/src/Commands/QueryCurrentBranchCommitHashes.cs
+++ b/src/Commands/QueryCurrentBranchCommitHashes.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
@@ -9,10 +8,9 @@ public class QueryCurrentBranchCommitHashes : Command
{
public QueryCurrentBranchCommitHashes(string repo, ulong sinceTimestamp)
{
- var since = DateTime.UnixEpoch.AddSeconds(sinceTimestamp).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
WorkingDirectory = repo;
Context = repo;
- Args = $"log --since={since.Quoted()} --format=%H";
+ Args = $"log --since=@{sinceTimestamp} --format=%H";
}
public async Task> GetResultAsync()
diff --git a/src/Commands/QueryFileHistory.cs b/src/Commands/QueryFileHistory.cs
index d112377aa0..5dca109a2c 100644
--- a/src/Commands/QueryFileHistory.cs
+++ b/src/Commands/QueryFileHistory.cs
@@ -8,7 +8,7 @@ namespace SourceGit.Commands
{
public partial class QueryFileHistory : Command
{
- [GeneratedRegex(@"^([MAD])\s+(.+)$")]
+ [GeneratedRegex(@"^([MADT])\s+(.+)$")]
private static partial Regex REG_FORMAT();
[GeneratedRegex(@"^([CR])[0-9]{0,4}\s+(.+)$")]
private static partial Regex REG_RENAME_FORMAT();
@@ -86,6 +86,9 @@ public QueryFileHistory(string repo, string path, string head)
case 'D':
last.Change.Set(Models.ChangeState.Deleted);
break;
+ case 'T':
+ last.Change.Set(Models.ChangeState.TypeChanged);
+ break;
}
}
}
diff --git a/src/Commands/QueryFileSize.cs b/src/Commands/QueryFileSize.cs
index c1ad51dfe5..c2afe26504 100644
--- a/src/Commands/QueryFileSize.cs
+++ b/src/Commands/QueryFileSize.cs
@@ -1,29 +1,21 @@
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
namespace SourceGit.Commands
{
- public partial class QueryFileSize : Command
+ public class QueryFileSize : Command
{
- [GeneratedRegex(@"^\d+\s+\w+\s+[0-9a-f]+\s+(\d+)\s+.*$")]
- private static partial Regex REG_FORMAT();
-
public QueryFileSize(string repo, string file, string revision)
{
WorkingDirectory = repo;
Context = repo;
- Args = $"ls-tree {revision} -l -- {file.Quoted()}";
+ Args = $"cat-file -s {revision}:{file.Quoted()}";
}
public async Task GetResultAsync()
{
var rs = await ReadToEndAsync().ConfigureAwait(false);
- if (rs.IsSuccess)
- {
- var match = REG_FORMAT().Match(rs.StdOut);
- if (match.Success)
- return long.Parse(match.Groups[1].Value);
- }
+ if (rs.IsSuccess && long.TryParse(rs.StdOut.Trim(), out var size))
+ return size;
return 0;
}
diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs
index d1bd30a2c8..7c2e4156ab 100644
--- a/src/Commands/QueryLocalChanges.cs
+++ b/src/Commands/QueryLocalChanges.cs
@@ -34,9 +34,24 @@ public QueryLocalChanges(string repo, bool includeUntracked = true, bool noOptio
try
{
using var proc = new Process();
+ var captured = new CapturedProcess() { Process = proc };
+ var capturedLock = new object();
+
proc.StartInfo = CreateGitStartInfo(true);
proc.Start();
+ if (CancellationToken.CanBeCanceled)
+ {
+ CancellationToken.Register(() =>
+ {
+ lock (capturedLock)
+ {
+ if (captured is { Process: { HasExited: false } proc })
+ Native.OS.TerminateProcess(proc);
+ }
+ });
+ }
+
while (await proc.StandardOutput.ReadLineAsync().ConfigureAwait(false) is { } line)
{
var match = REG_FORMAT().Match(line);
@@ -165,6 +180,11 @@ public QueryLocalChanges(string repo, bool includeUntracked = true, bool noOptio
if (change.Index != Models.ChangeState.None || change.WorkTree != Models.ChangeState.None)
outs.Add(change);
}
+
+ lock (capturedLock)
+ {
+ captured.Process = null;
+ }
}
catch
{
@@ -173,5 +193,10 @@ public QueryLocalChanges(string repo, bool includeUntracked = true, bool noOptio
return outs;
}
+
+ private class CapturedProcess
+ {
+ public Process Process { get; set; } = null;
+ }
}
}
diff --git a/src/Commands/QueryRemotes.cs b/src/Commands/QueryRemotes.cs
index f8a23b7aab..6adff9b36d 100644
--- a/src/Commands/QueryRemotes.cs
+++ b/src/Commands/QueryRemotes.cs
@@ -1,73 +1,78 @@
using System;
using System.Collections.Generic;
-using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace SourceGit.Commands
{
- public partial class QueryRemotes : Command
+ public class QueryRemotes
{
- [GeneratedRegex(@"^([\w\.\-]+)\s*(\S+).*$")]
- private static partial Regex REG_REMOTE();
-
public QueryRemotes(string repo)
{
- WorkingDirectory = repo;
- Context = repo;
- Args = "remote -v";
+ _repo = repo;
}
public async Task> GetResultAsync()
{
- var outs = new List();
- var rs = await ReadToEndAsync().ConfigureAwait(false);
- if (!rs.IsSuccess)
- return outs;
-
- var config = await new Config(WorkingDirectory).ReadAllAsync().ConfigureAwait(false);
+ var names = new HashSet();
+ var urls = new Dictionary();
+ var pushUrls = new Dictionary();
+ var privateSSHKeys = new Dictionary();
var disableAutoFetchRemotes = new HashSet();
+
+ var config = await new Config(_repo).ReadAllAsync().ConfigureAwait(false);
foreach (var (k, v) in config)
{
- if (k.StartsWith("remote.", StringComparison.Ordinal) &&
- k.EndsWith(".disableautofetch", StringComparison.Ordinal) &&
- v.Equals("true", StringComparison.OrdinalIgnoreCase))
+ if (!k.StartsWith("remote.", StringComparison.Ordinal))
+ continue;
+
+ if (k.EndsWith(".url", StringComparison.Ordinal))
+ {
+ var name = k.Substring(7, k.Length - 11).Trim('"');
+ names.Add(name);
+ urls[name] = v;
+ }
+ else if (k.EndsWith(".pushurl", StringComparison.OrdinalIgnoreCase))
+ {
+ var name = k.Substring(7, k.Length - 15).Trim('"');
+ names.Add(name);
+ pushUrls[name] = v;
+ }
+ else if (k.EndsWith(".sshkey", StringComparison.OrdinalIgnoreCase))
{
- var remoteName = k.Substring(7, k.Length - 24).Trim('"');
- disableAutoFetchRemotes.Add(remoteName);
+ var name = k.Substring(7, k.Length - 14).Trim('"');
+ names.Add(name);
+ privateSSHKeys[name] = v;
+ }
+ else if (k.EndsWith(".disableautofetch", StringComparison.OrdinalIgnoreCase) &&
+ v.Equals("true", StringComparison.OrdinalIgnoreCase))
+ {
+ var name = k.Substring(7, k.Length - 24).Trim('"');
+ disableAutoFetchRemotes.Add(name);
}
}
- var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
- foreach (var line in lines)
+ var remotes = new List();
+ foreach (var name in names)
{
- var match = REG_REMOTE().Match(line);
- if (!match.Success)
+ if (!urls.TryGetValue(name, out var url))
continue;
- var remote = new Models.Remote()
+ var r = new Models.Remote()
{
- Name = match.Groups[1].Value,
- URL = match.Groups[2].Value,
- DisableAutoFetch = disableAutoFetchRemotes.Contains(match.Groups[1].Value)
+ Name = name,
+ URL = url,
+ PushURL = pushUrls.TryGetValue(name, out var pushURL) ? pushURL : null,
+ PrivateSSHKey = privateSSHKeys.TryGetValue(name, out var privateSSHKey) ? privateSSHKey : null,
+ DisableAutoFetch = disableAutoFetchRemotes.Contains(name)
};
- if (outs.Find(x => x.Name == remote.Name) != null)
- continue;
-
- if (remote.URL.StartsWith("git@", StringComparison.Ordinal))
- {
- var hostEnd = remote.URL.IndexOf(':', 4);
- if (hostEnd > 4)
- {
- var host = remote.URL.Substring(4, hostEnd - 4);
- Models.HTTPSValidator.Add(host);
- }
- }
-
- outs.Add(remote);
+ remotes.Add(r);
}
- return outs;
+ remotes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
+ return remotes;
}
+
+ private readonly string _repo;
}
}
diff --git a/src/Commands/QueryRepositoryStatus.cs b/src/Commands/QueryRepositoryStatus.cs
index 3d6adac8e1..8244afb215 100644
--- a/src/Commands/QueryRepositoryStatus.cs
+++ b/src/Commands/QueryRepositoryStatus.cs
@@ -17,7 +17,7 @@ public QueryRepositoryStatus(string repo)
public async Task GetResultAsync()
{
- Args = "status --porcelain=v2 -b";
+ Args = "status --porcelain=v2 -b -uall --ignore-submodules=dirty";
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return null;
@@ -36,12 +36,17 @@ public QueryRepositoryStatus(string repo)
else
status.CurrentBranch = head;
- if (count == 4 && lines[3].StartsWith("# branch.ab ", StringComparison.Ordinal))
- ParseTrackStatus(status, lines[3].Substring(12).Trim());
+ if (count >= 4)
+ {
+ var changesCount = count - 3;
+ if (lines[3].StartsWith("# branch.ab ", StringComparison.Ordinal))
+ {
+ ParseTrackStatus(status, lines[3].Substring(12).Trim());
+ changesCount--;
+ }
- status.LocalChanges = await new CountLocalChanges(WorkingDirectory, true) { RaiseError = false }
- .GetResultAsync()
- .ConfigureAwait(false);
+ status.LocalChanges = changesCount;
+ }
return status;
}
diff --git a/src/Commands/QueryStagedFileBlobGuid.cs b/src/Commands/QueryStagedFileBlobGuid.cs
deleted file mode 100644
index 54ecd2ac76..0000000000
--- a/src/Commands/QueryStagedFileBlobGuid.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-
-namespace SourceGit.Commands
-{
- public partial class QueryStagedFileBlobGuid : Command
- {
- [GeneratedRegex(@"^\d+\s+([0-9a-f]+)\s+.*$")]
- private static partial Regex REG_FORMAT();
-
- public QueryStagedFileBlobGuid(string repo, string file)
- {
- WorkingDirectory = repo;
- Context = repo;
- Args = $"ls-files -s -- {file.Quoted()}";
- }
-
- public async Task GetResultAsync()
- {
- var rs = await ReadToEndAsync().ConfigureAwait(false);
- var match = REG_FORMAT().Match(rs.StdOut.Trim());
- return match.Success ? match.Groups[1].Value : string.Empty;
- }
- }
-}
diff --git a/src/Commands/QuerySubmoduleRevision.cs b/src/Commands/QuerySubmoduleRevision.cs
new file mode 100644
index 0000000000..a61f2d880a
--- /dev/null
+++ b/src/Commands/QuerySubmoduleRevision.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Threading.Tasks;
+
+namespace SourceGit.Commands
+{
+ public class QuerySubmoduleRevision : Command
+ {
+ public QuerySubmoduleRevision(string repo, string revision)
+ {
+ WorkingDirectory = repo;
+ Context = repo;
+
+ if (revision.EndsWith("-dirty", StringComparison.Ordinal))
+ {
+ _hasUncommittedChange = true;
+ _revision = revision.Substring(0, revision.Length - 6);
+ }
+ else
+ {
+ _hasUncommittedChange = false;
+ _revision = revision;
+ }
+ }
+
+ public async Task GetResultAsync()
+ {
+ Args = $"show --no-show-signature --decorate=full --format=%H%x00%P%x00%D%x00%aN±%aE%x00%at%x00%cN±%cE%x00%ct%x00%s%x00%B -s {_revision}";
+
+ var rs = await ReadToEndAsync().ConfigureAwait(false);
+ if (!rs.IsSuccess || string.IsNullOrEmpty(rs.StdOut))
+ return new Models.RevisionSubmodule(_revision);
+
+ var commit = new Models.Commit();
+ var lines = rs.StdOut.Split('\0');
+ if (lines.Length < 9)
+ return new Models.RevisionSubmodule(_revision);
+
+ commit.SHA = lines[0];
+ if (!string.IsNullOrEmpty(lines[1]))
+ commit.Parents.AddRange(lines[1].Split(' ', StringSplitOptions.RemoveEmptyEntries));
+ if (!string.IsNullOrEmpty(lines[2]))
+ commit.ParseDecorators(lines[2]);
+ commit.Author = Models.User.FindOrAdd(lines[3]);
+ commit.AuthorTime = ulong.Parse(lines[4]);
+ commit.Committer = Models.User.FindOrAdd(lines[5]);
+ commit.CommitterTime = ulong.Parse(lines[6]);
+ commit.Subject = lines[7];
+
+ var message = lines[8].TrimEnd();
+ var uncommittedChangesCount = 0;
+ if (_hasUncommittedChange)
+ uncommittedChangesCount = await new CountLocalChanges(WorkingDirectory, true)
+ .GetResultAsync()
+ .ConfigureAwait(false);
+
+ return new Models.RevisionSubmodule(commit, message, uncommittedChangesCount);
+ }
+
+ private bool _hasUncommittedChange = false;
+ private string _revision = string.Empty;
+ }
+}
diff --git a/src/Commands/QueryTags.cs b/src/Commands/QueryTags.cs
index 8718542eb3..9033a8d047 100644
--- a/src/Commands/QueryTags.cs
+++ b/src/Commands/QueryTags.cs
@@ -12,7 +12,7 @@ public QueryTags(string repo)
Context = repo;
WorkingDirectory = repo;
- Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objecttype)%00%(objectname)%00%(*objectname)%00%(taggername)±%(taggeremail)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\"";
+ Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objecttype)%00%(objectname)%00%(*objectname)%00%(taggername)±%(taggeremail:trim)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\"";
}
public async Task> GetResultAsync()
diff --git a/src/Commands/Rebase.cs b/src/Commands/Rebase.cs
index 4034cb27ed..fdc585c019 100644
--- a/src/Commands/Rebase.cs
+++ b/src/Commands/Rebase.cs
@@ -10,7 +10,7 @@ public Rebase(string repo, string basedOn, bool autoStash, bool noVerify)
Context = repo;
var builder = new StringBuilder(512);
- builder.Append("rebase ");
+ builder.Append("-c core.commentChar=\"^\" -c core.commentString=\"±\" rebase ");
if (autoStash)
builder.Append("--autostash ");
if (noVerify)
diff --git a/src/Commands/Remote.cs b/src/Commands/Remote.cs
index 6a15037740..f7296b1cef 100644
--- a/src/Commands/Remote.cs
+++ b/src/Commands/Remote.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System.Text;
+using System.Threading.Tasks;
namespace SourceGit.Commands
{
@@ -28,33 +29,25 @@ public async Task RenameAsync(string name, string to)
return await ExecAsync();
}
- public async Task PruneAsync(string name)
+ public async Task PruneAsync(string name, string sshKey)
{
+ SSHKey = sshKey;
Args = $"remote prune {name}";
return await ExecAsync();
}
- public async Task GetURLAsync(string name, bool isPush)
+ public async Task SetURLAsync(string name, string url, bool isDelete, bool isPush)
{
- Args = "remote get-url" + (isPush ? " --push " : " ") + name;
+ var builder = new StringBuilder();
+ builder.Append("remote set-url ");
+ if (isDelete)
+ builder.Append("--delete ");
+ if (isPush)
+ builder.Append("--push ");
+ builder.Append(name).Append(' ').Append(url);
- var rs = await ReadToEndAsync();
- return rs.IsSuccess ? rs.StdOut.Trim() : string.Empty;
- }
-
- public async Task SetURLAsync(string name, string url, bool isPush)
- {
- Args = "remote set-url" + (isPush ? " --push " : " ") + $"{name} {url}";
+ Args = builder.ToString();
return await ExecAsync();
}
-
- public async Task HasBranchAsync(string remote, string branch)
- {
- SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{remote}.sshkey");
- Args = $"ls-remote {remote} {branch}";
-
- var rs = await ReadToEndAsync();
- return rs.IsSuccess && rs.StdOut.Trim().Length > 0;
- }
}
}
diff --git a/src/Commands/Replay.cs b/src/Commands/Replay.cs
new file mode 100644
index 0000000000..3f2546224b
--- /dev/null
+++ b/src/Commands/Replay.cs
@@ -0,0 +1,36 @@
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace SourceGit.Commands
+{
+ public class Replay : Command
+ {
+ public Replay(string repo, string onto, string range)
+ {
+ WorkingDirectory = repo;
+ Context = repo;
+ RaiseError = false;
+ Args = $"replay --onto {onto} {range}";
+ }
+
+ public async Task GetExitCodeAsync()
+ {
+ using var proc = new Process();
+ proc.StartInfo = CreateGitStartInfo(false);
+
+ var exitCode = -1;
+ try
+ {
+ proc.Start();
+ await proc.WaitForExitAsync().ConfigureAwait(false);
+ exitCode = proc.ExitCode;
+ }
+ catch
+ {
+ // Ignore any exceptions and just return -1
+ }
+
+ return exitCode;
+ }
+ }
+}
diff --git a/src/Commands/SaveRevisionFile.cs b/src/Commands/SaveRevisionFile.cs
index 410d15b07c..6c810289d1 100644
--- a/src/Commands/SaveRevisionFile.cs
+++ b/src/Commands/SaveRevisionFile.cs
@@ -16,7 +16,7 @@ public static async Task RunAsync(string repo, string revision, string file, str
var isLFSFiltered = await new IsLFSFiltered(repo, revision, file).GetResultAsync().ConfigureAwait(false);
if (isLFSFiltered)
{
- var pointerStream = await QueryFileContent.RunAsync(repo, revision, file).ConfigureAwait(false);
+ await using var pointerStream = await QueryFileContent.RunAsync(repo, revision, file).ConfigureAwait(false);
await ExecCmdAsync(repo, "lfs smudge", saveTo, pointerStream).ConfigureAwait(false);
}
else
diff --git a/src/Commands/Statistics.cs b/src/Commands/Statistics.cs
index 2d43c7629f..3c6ed017b7 100644
--- a/src/Commands/Statistics.cs
+++ b/src/Commands/Statistics.cs
@@ -1,15 +1,28 @@
using System.IO;
+using System.Text;
using System.Threading.Tasks;
namespace SourceGit.Commands
{
public class Statistics : Command
{
- public Statistics(string repo, int max)
+ public Statistics(string repo, int max, Models.Branch specBranch)
{
WorkingDirectory = repo;
Context = repo;
- Args = $"log --date-order --branches --remotes -{max} --format=%ct$%aN±%aE";
+
+ var builder = new StringBuilder();
+ builder
+ .Append("log --date-order -")
+ .Append(max)
+ .Append(" --format=%ct$%aN±%aE ");
+
+ if (specBranch == null || string.IsNullOrEmpty(specBranch.FullName))
+ builder.Append("--branches --remotes");
+ else
+ builder.Append(specBranch.FullName);
+
+ Args = builder.ToString();
}
public async Task ReadAsync()
diff --git a/src/Commands/Submodule.cs b/src/Commands/Submodule.cs
index 728296ce30..d8b1dbcb7f 100644
--- a/src/Commands/Submodule.cs
+++ b/src/Commands/Submodule.cs
@@ -43,13 +43,15 @@ public async Task SetBranchAsync(string path, string branch)
return await ExecAsync().ConfigureAwait(false);
}
- public async Task UpdateAsync(List modules, bool init = false, bool useRemote = false)
+ public async Task UpdateAsync(List modules, bool init, bool recursive, bool useRemote)
{
var builder = new StringBuilder();
- builder.Append("submodule update --recursive");
+ builder.Append("submodule update");
if (init)
builder.Append(" --init");
+ if (recursive)
+ builder.Append(" --recursive");
if (useRemote)
builder.Append(" --remote");
if (modules.Count > 0)
diff --git a/src/Commands/Worktree.cs b/src/Commands/Worktree.cs
index 7b70e2ab49..137bb3f7f1 100644
--- a/src/Commands/Worktree.cs
+++ b/src/Commands/Worktree.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Text;
using System.Threading.Tasks;
@@ -61,20 +62,33 @@ public Worktree(string repo)
return worktrees;
}
- public async Task AddAsync(string fullpath, string name, bool createNew, string tracking)
+ public async Task AddAsync(string fullpath, string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ Args = $"worktree add {fullpath.Quoted()}";
+ else
+ Args = $"worktree add -b {name} {fullpath.Quoted()}";
+
+ return await ExecAsync().ConfigureAwait(false);
+ }
+
+ public async Task AddWithLocalBranchAsync(string fullpath, string localBranch)
+ {
+ Args = $"worktree add -B {localBranch} {fullpath.Quoted()} {localBranch}";
+ return await ExecAsync().ConfigureAwait(false);
+ }
+
+ public async Task AddWithRemoteBranchAsync(string fullpath, string name, string remoteBranch, bool tracking)
{
var builder = new StringBuilder(1024);
builder.Append("worktree add ");
- if (!string.IsNullOrEmpty(tracking))
- builder.Append("--track ");
- if (!string.IsNullOrEmpty(name))
- builder.Append(createNew ? "-b " : "-B ").Append(name).Append(' ');
- builder.Append(fullpath.Quoted()).Append(' ');
-
- if (!string.IsNullOrEmpty(tracking))
- builder.Append(tracking);
- else if (!string.IsNullOrEmpty(name) && !createNew)
- builder.Append(name);
+
+ if (tracking)
+ builder.Append("--track -b ").Append(string.IsNullOrEmpty(name) ? Path.GetFileName(fullpath) : name).Append(' ');
+ else if (!string.IsNullOrEmpty(name))
+ builder.Append("-b ").Append(name).Append(' ');
+
+ builder.Append(fullpath.Quoted()).Append(' ').Append(remoteBranch);
Args = builder.ToString();
return await ExecAsync().ConfigureAwait(false);
diff --git a/src/Converters/IntConverters.cs b/src/Converters/IntConverters.cs
index 2dcebcf277..4d6c723a8d 100644
--- a/src/Converters/IntConverters.cs
+++ b/src/Converters/IntConverters.cs
@@ -1,7 +1,5 @@
using Avalonia;
-using Avalonia.Controls;
using Avalonia.Data.Converters;
-using Avalonia.Media;
namespace SourceGit.Converters
{
@@ -16,16 +14,16 @@ public static class IntConverters
public static readonly FuncValueConverter IsZero =
new(v => v == 0);
- public static readonly FuncValueConverter IsNotOne =
- new(v => v != 1);
-
public static readonly FuncValueConverter ToTreeMargin =
new(v => new Thickness(v * 16, 0, 0, 0));
- public static readonly FuncValueConverter ToBookmarkBrush =
- new(v => Models.Bookmarks.Get(v) ?? Application.Current?.FindResource("Brush.FG1") as IBrush);
-
public static readonly FuncValueConverter ToUnsolvedDesc =
new(v => v == 0 ? App.Text("MergeConflictEditor.AllResolved") : App.Text("MergeConflictEditor.ConflictsRemaining", v));
+
+ public static readonly FuncValueConverter ToBadgeWithLimit =
+ new(v => v > 999 ? "999+" : v.ToString());
+
+ public static readonly FuncValueConverter IsMaxCountOfStatistics =
+ new(v => v >= 200000);
}
}
diff --git a/src/Converters/LongConverters.cs b/src/Converters/LongConverters.cs
index 05a55e95bd..fb72aadb79 100644
--- a/src/Converters/LongConverters.cs
+++ b/src/Converters/LongConverters.cs
@@ -18,6 +18,11 @@ public static class LongConverters
return $"{(bytes / GB):G3} GB ({bytes:N0})";
});
+ public static readonly FuncValueConverter IsGreaterThanZero = new(bytes =>
+ {
+ return bytes > 0;
+ });
+
private const double KB = 1024;
private const double MB = 1024 * 1024;
private const double GB = 1024 * 1024 * 1024;
diff --git a/src/Converters/ObjectConverters.cs b/src/Converters/ObjectConverters.cs
deleted file mode 100644
index f7c57764dd..0000000000
--- a/src/Converters/ObjectConverters.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Globalization;
-using Avalonia.Data.Converters;
-
-namespace SourceGit.Converters
-{
- public static class ObjectConverters
- {
- public class IsTypeOfConverter : IValueConverter
- {
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- if (value == null || parameter == null)
- return false;
-
- return value.GetType().IsAssignableTo((Type)parameter);
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- return new NotImplementedException();
- }
- }
-
- public static readonly IsTypeOfConverter IsTypeOf = new IsTypeOfConverter();
- }
-}
diff --git a/src/Models/AvatarManager.cs b/src/Models/AvatarManager.cs
index c8d573a227..7e4593fd86 100644
--- a/src/Models/AvatarManager.cs
+++ b/src/Models/AvatarManager.cs
@@ -38,13 +38,13 @@ public static AvatarManager Instance
private readonly Lock _synclock = new();
private string _storePath;
private List _avatars = new List();
- private Dictionary _resources = new Dictionary();
+ private Cache _cache = new Cache(256);
private HashSet _requesting = new HashSet();
private HashSet _defaultAvatars = new HashSet();
public void Start()
{
- _storePath = Path.Combine(Native.OS.DataDir, "avatars");
+ _storePath = Path.Combine(Native.OS.BasicDirectories.CacheDir, "avatars");
if (!Directory.Exists(_storePath))
Directory.CreateDirectory(_storePath);
@@ -53,6 +53,9 @@ public void Start()
Task.Run(async () =>
{
+ using var client = new HttpClient();
+ client.Timeout = TimeSpan.FromSeconds(2);
+
while (true)
{
string email = null;
@@ -88,8 +91,6 @@ public void Start()
Bitmap img = null;
try
{
- using var client = new HttpClient();
- client.Timeout = TimeSpan.FromSeconds(2);
var rsp = await client.GetAsync(url);
if (rsp.IsSuccessStatusCode)
{
@@ -119,7 +120,7 @@ public void Start()
Dispatcher.UIThread.Post(() =>
{
- _resources[email] = img;
+ _cache.AddOrUpdate(email, img);
NotifyResourceChanged(email, img);
});
}
@@ -145,7 +146,7 @@ public Bitmap Request(string email, bool forceRefetch)
if (_defaultAvatars.Contains(email))
return null;
- _resources.Remove(email);
+ _cache.Remove(email);
var localFile = Path.Combine(_storePath, GetEmailHash(email));
if (File.Exists(localFile))
@@ -155,7 +156,7 @@ public Bitmap Request(string email, bool forceRefetch)
}
else
{
- if (_resources.TryGetValue(email, out var value))
+ if (_cache.TryGet(email, out var value))
return value;
var localFile = Path.Combine(_storePath, GetEmailHash(email));
@@ -166,7 +167,7 @@ public Bitmap Request(string email, bool forceRefetch)
using (var stream = File.OpenRead(localFile))
{
var img = Bitmap.DecodeToWidth(stream, 128);
- _resources.Add(email, img);
+ _cache.AddOrUpdate(email, img);
return img;
}
}
@@ -196,7 +197,7 @@ public void SetFromLocal(string email, string file)
image = Bitmap.DecodeToWidth(stream, 128);
}
- _resources[email] = image;
+ _cache.AddOrUpdate(email, image);
lock (_synclock)
{
@@ -216,7 +217,7 @@ public void SetFromLocal(string email, string file)
private void LoadDefaultAvatar(string key, string img)
{
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/{img}", UriKind.RelativeOrAbsolute));
- _resources.Add(key, new Bitmap(icon));
+ _cache.AddOrUpdate(key, new Bitmap(icon));
_defaultAvatars.Add(key);
}
@@ -224,10 +225,7 @@ private string GetEmailHash(string email)
{
var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim();
var hash = MD5.HashData(Encoding.Default.GetBytes(lowered));
- var builder = new StringBuilder(hash.Length * 2);
- foreach (var c in hash)
- builder.Append(c.ToString("x2"));
- return builder.ToString();
+ return Convert.ToHexStringLower(hash);
}
private void NotifyResourceChanged(string email, Bitmap image)
@@ -235,5 +233,76 @@ private void NotifyResourceChanged(string email, Bitmap image)
foreach (var avatar in _avatars)
avatar.OnAvatarResourceChanged(email, image);
}
+
+ private class Cache
+ {
+ private readonly int _capacity;
+ private readonly LinkedList- _imgList;
+ private readonly Dictionary> _key2img;
+
+ private class Item
+ {
+ public string Key { get; }
+ public Bitmap Image { get; set; }
+
+ public Item(string key, Bitmap image)
+ {
+ Key = key;
+ Image = image;
+ }
+ }
+
+ public Cache(int capacity)
+ {
+ _capacity = capacity;
+ _key2img = new Dictionary>();
+ _imgList = new LinkedList
- ();
+ }
+
+ public bool TryGet(string key, out Bitmap bitmap)
+ {
+ if (_key2img.TryGetValue(key, out var node))
+ {
+ _imgList.Remove(node);
+ _imgList.AddFirst(node);
+ bitmap = node.Value.Image;
+ return true;
+ }
+
+ bitmap = null;
+ return false;
+ }
+
+ public void AddOrUpdate(string key, Bitmap bitmap)
+ {
+ if (_key2img.TryGetValue(key, out var node))
+ {
+ _imgList.Remove(node);
+ _imgList.AddFirst(node);
+ node.Value.Image = bitmap;
+ return;
+ }
+
+ if (_key2img.Count >= _capacity)
+ {
+ var lastNode = _imgList.Last;
+ _imgList.RemoveLast();
+ _key2img.Remove(lastNode.Value.Key);
+ }
+
+ var newNode = new LinkedListNode
- (new Item(key, bitmap));
+ _imgList.AddFirst(newNode);
+ _key2img[key] = newNode;
+ }
+
+ public void Remove(string key)
+ {
+ if (_key2img.TryGetValue(key, out var node))
+ {
+ _imgList.Remove(node);
+ _key2img.Remove(key);
+ }
+ }
+ }
}
}
diff --git a/src/Models/BinaryFile.cs b/src/Models/BinaryFile.cs
new file mode 100644
index 0000000000..d875ac2f5b
--- /dev/null
+++ b/src/Models/BinaryFile.cs
@@ -0,0 +1,75 @@
+using System;
+using System.IO;
+
+namespace SourceGit.Models
+{
+ public class BinaryFile : IDisposable
+ {
+ public long Size
+ {
+ get => _size;
+ }
+
+ public BinaryFile(string file, bool needDeleteFile = false)
+ {
+ _filePath = file;
+ _needDeleteFile = needDeleteFile;
+
+ if (File.Exists(_filePath))
+ {
+ _size = new FileInfo(_filePath).Length;
+ _reader = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read, BUFFER_SIZE, FileOptions.RandomAccess);
+ _readedStart = 0;
+ _readedEnd = Math.Min(_size, BUFFER_SIZE);
+
+ if (_size > 0)
+ {
+ _reader.Seek(_readedStart, SeekOrigin.Begin);
+ _reader.ReadExactly(_buffer, 0, (int)_readedEnd);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ _reader?.Dispose();
+ _reader = null;
+
+ if (_needDeleteFile && File.Exists(_filePath))
+ File.Delete(_filePath);
+ }
+
+ public ArraySegment Read(long offset, long length)
+ {
+ if (_reader == null || _size == 0 || offset >= _size)
+ return Array.Empty();
+
+ if (length > 8192)
+ length = 8192;
+
+ if (offset + length > _size)
+ length = _size - offset;
+
+ if (_readedStart <= offset && _readedEnd >= offset + length)
+ return new ArraySegment(_buffer, (int)(offset - _readedStart), (int)length);
+
+ _readedStart = (Math.Max(0, offset - 2048) / 1024) * 1024;
+ _readedEnd = Math.Min(_readedStart + BUFFER_SIZE, _size);
+
+ _reader.Seek(_readedStart, SeekOrigin.Begin);
+ _reader.ReadExactly(_buffer, 0, (int)(_readedEnd - _readedStart));
+
+ return new ArraySegment(_buffer, (int)(offset - _readedStart), (int)length);
+ }
+
+ private const int BUFFER_SIZE = 16384;
+
+ private string _filePath = string.Empty;
+ private bool _needDeleteFile = false;
+ private FileStream _reader = null;
+ private long _size = 0;
+ private long _readedStart = 0;
+ private long _readedEnd = 0;
+ private byte[] _buffer = new byte[BUFFER_SIZE];
+ }
+}
diff --git a/src/Models/Bisect.cs b/src/Models/Bisect.cs
index 2ed8beb259..286a02c4f3 100644
--- a/src/Models/Bisect.cs
+++ b/src/Models/Bisect.cs
@@ -6,16 +6,19 @@ namespace SourceGit.Models
public enum BisectState
{
None = 0,
- WaitingForRange,
- Detecting,
+ WaitingForFirstBad,
+ WaitingForCheckoutAnother,
+ WaitingForFirstGood,
+ WaitingForMark,
}
[Flags]
public enum BisectCommitFlag
{
None = 0,
- Good = 1 << 0,
- Bad = 1 << 1,
+ Good,
+ Bad,
+ Skipped,
}
public class Bisect
@@ -31,5 +34,11 @@ public HashSet Goods
get;
set;
} = [];
+
+ public HashSet Skipped
+ {
+ get;
+ set;
+ } = [];
}
}
diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs
index a45c060303..de299ed6b7 100644
--- a/src/Models/Commit.cs
+++ b/src/Models/Commit.cs
@@ -8,7 +8,6 @@ public enum CommitSearchMethod
{
BySHA = 0,
ByAuthor,
- ByCommitter,
ByMessage,
ByPath,
ByContent,
diff --git a/src/Models/CommitGraph.cs b/src/Models/CommitGraph.cs
index ca865a54ea..916a79b1bb 100644
--- a/src/Models/CommitGraph.cs
+++ b/src/Models/CommitGraph.cs
@@ -14,6 +14,7 @@ public enum CommitGraphHighlighting
CurrentBranchOnly,
SelectedCommitsOnly,
CurrentBranchAndSelectedCommits,
+ SelectedCommitsOnlyFirstParent,
}
public class CommitGraph
@@ -82,67 +83,19 @@ public static CommitGraph Generate(List commits, bool firstParentOnlyEna
var ended = new List();
var offsetY = -halfHeight;
var colorPicker = new ColorPicker();
- var merged = new HashSet();
+ var defHighlighting = highlighting == CommitGraphHighlighting.All;
foreach (var commit in commits)
{
PathHelper major = null;
- // Update merge state of this commit.
- if (commit.IsMerged)
- {
- merged.Remove(commit.SHA);
- foreach (var p in commit.Parents)
- merged.Add(p);
- }
- else if (merged.Remove(commit.SHA))
- {
- commit.IsMerged = true;
- foreach (var p in commit.Parents)
- merged.Add(p);
- }
-
- // Calculate highlighted state
- var isHighlighted = false;
- if (highlighting == CommitGraphHighlighting.All)
- {
- isHighlighted = true;
- }
- else if (highlighting == CommitGraphHighlighting.CurrentBranchOnly)
- {
- isHighlighted = commit.IsMerged;
- }
- else if (highlighting == CommitGraphHighlighting.SelectedCommitsOnly)
- {
- isHighlighted = highlightExtraCommits.Remove(commit.SHA);
- if (isHighlighted)
- {
- commit.IsHighlightedInGraph = true;
- foreach (var p in commit.Parents)
- highlightExtraCommits.Add(p);
- }
- }
- else
- {
- if (commit.IsMerged)
- {
- isHighlighted = true;
- }
- else if (highlightExtraCommits.Remove(commit.SHA))
- {
- isHighlighted = true;
- foreach (var p in commit.Parents)
- highlightExtraCommits.Add(p);
- }
- }
- commit.IsHighlightedInGraph = isHighlighted;
-
// Update current y offset
offsetY += unitHeight;
// Find first curves that links to this commit and marks others that links to this commit ended.
var offsetX = 4 - halfWidth;
var maxOffsetOld = unsolved.Count > 0 ? unsolved[^1].LastX : offsetX + unitWidth;
+ var isHighlighted = defHighlighting;
foreach (var l in unsolved)
{
if (l.Next.Equals(commit.SHA, StringComparison.Ordinal))
@@ -151,6 +104,7 @@ public static CommitGraph Generate(List commits, bool firstParentOnlyEna
{
offsetX += unitWidth;
major = l;
+ isHighlighted = major.IsHighlighted;
if (commit.Parents.Count > 0)
{
@@ -167,6 +121,9 @@ public static CommitGraph Generate(List commits, bool firstParentOnlyEna
{
l.End(major.LastX, offsetY, halfHeight);
ended.Add(l);
+
+ if (!isHighlighted && l.IsHighlighted)
+ isHighlighted = true;
}
}
else
@@ -184,6 +141,41 @@ public static CommitGraph Generate(List commits, bool firstParentOnlyEna
}
ended.Clear();
+ // Calculate highlighted state
+ if (!isHighlighted)
+ {
+ switch (highlighting)
+ {
+ case CommitGraphHighlighting.CurrentBranchOnly:
+ isHighlighted = commit.IsMerged;
+ break;
+ case CommitGraphHighlighting.SelectedCommitsOnly:
+ case CommitGraphHighlighting.SelectedCommitsOnlyFirstParent:
+ if (highlightExtraCommits.Remove(commit.SHA))
+ {
+ isHighlighted = true;
+ // Highlight first parent, other parents are dealt with later
+ if (commit.Parents.Count > 0)
+ highlightExtraCommits.Add(commit.Parents[0]);
+ }
+ break;
+ default: // CommitGraphHighlighting.CurrentBranchAndSelectedCommits
+ if (commit.IsMerged)
+ {
+ isHighlighted = true;
+ }
+ else if (highlightExtraCommits.Remove(commit.SHA))
+ {
+ isHighlighted = true;
+ // Highlight first parent, other parents are dealt with later
+ if (commit.Parents.Count > 0)
+ highlightExtraCommits.Add(commit.Parents[0]);
+ }
+ break;
+ }
+ }
+ commit.IsHighlightedInGraph = isHighlighted;
+
// If no path found, create new curve for branch head
// Otherwise, create new curve for new merged commit
if (major == null)
@@ -218,6 +210,9 @@ public static CommitGraph Generate(List commits, bool firstParentOnlyEna
// Deal with other parents (the first parent has been processed)
if (!firstParentOnlyEnabled)
{
+ if (highlighting == CommitGraphHighlighting.SelectedCommitsOnlyFirstParent)
+ isHighlighted = false;
+
for (int j = 1; j < commit.Parents.Count; j++)
{
var parentHash = commit.Parents[j];
diff --git a/src/Models/Conflict.cs b/src/Models/Conflict.cs
index 112fdd9b75..fd9c690308 100644
--- a/src/Models/Conflict.cs
+++ b/src/Models/Conflict.cs
@@ -2,6 +2,14 @@
namespace SourceGit.Models
{
+ public enum ConflictFileState
+ {
+ Unknown,
+ UnmergedText,
+ UnmergedBinary,
+ Resolved
+ }
+
public enum ConflictPanelType
{
Ours,
diff --git a/src/Models/DiffOption.cs b/src/Models/DiffOption.cs
index def59beddd..af64b39a4b 100644
--- a/src/Models/DiffOption.cs
+++ b/src/Models/DiffOption.cs
@@ -6,16 +6,7 @@ namespace SourceGit.Models
{
public class DiffOption
{
- ///
- /// Enable `--ignore-cr-at-eol` by default?
- ///
- public static bool IgnoreCRAtEOL
- {
- get;
- set;
- } = true;
-
- public Change WorkingCopyChange => _workingCopyChange;
+ public bool IsLocalChange => _revisions.Count == 0;
public bool IsUnstaged => _isUnstaged;
public List Revisions => _revisions;
public string Path => _path;
@@ -28,7 +19,6 @@ public static bool IgnoreCRAtEOL
///
public DiffOption(Change change, bool isUnstaged)
{
- _workingCopyChange = change;
_isUnstaged = isUnstaged;
_path = change.Path;
_orgPath = change.OriginalPath;
@@ -66,18 +56,6 @@ public DiffOption(Commit commit, Change change)
_orgPath = change.OriginalPath;
}
- ///
- /// Diff with filepath. Used by FileHistories
- ///
- ///
- ///
- public DiffOption(Commit commit, string file)
- {
- _revisions.Add(commit.FirstParentToCompare);
- _revisions.Add(commit.SHA);
- _path = file;
- }
-
///
/// Used to diff in `FileHistory`
///
@@ -171,7 +149,6 @@ public override string ToString()
return builder.ToString();
}
- private readonly Change _workingCopyChange = null;
private readonly bool _isUnstaged = false;
private readonly string _path;
private readonly string _orgPath = string.Empty;
diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs
index 27a8ff093d..35b175c530 100644
--- a/src/Models/DiffResult.cs
+++ b/src/Models/DiffResult.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.IO;
-using System.Text.RegularExpressions;
using Avalonia.Media.Imaging;
namespace SourceGit.Models
@@ -34,546 +33,26 @@ public class TextDiffLine
public string NewLine => NewLineNumber == 0 ? string.Empty : NewLineNumber.ToString();
public TextDiffLine() { }
- public TextDiffLine(TextDiffLineType type, string line, byte[] rawContent, int oldLine, int newLine)
+ public TextDiffLine(TextDiffLineType type, string content, byte[] rawContent, int oldLine, int newLine)
{
Type = type;
- Content = line;
+ Content = content;
RawContent = rawContent;
OldLineNumber = oldLine;
NewLineNumber = newLine;
}
}
- public class TextDiffSelection
- {
- public int StartLine { get; set; } = 0;
- public int EndLine { get; set; } = 0;
- public bool HasChanges { get; set; } = false;
- public int IgnoredAdds { get; set; } = 0;
- public int IgnoredDeletes { get; set; } = 0;
- }
-
public partial class TextDiff
{
public List Lines { get; set; } = new List();
public int MaxLineNumber = 0;
public int AddedLines { get; set; } = 0;
public int DeletedLines { get; set; } = 0;
-
- public TextDiffSelection MakeSelection(int startLine, int endLine, bool isCombined, bool isOldSide)
- {
- var rs = new TextDiffSelection();
- rs.StartLine = startLine;
- rs.EndLine = endLine;
-
- for (int i = 0; i < startLine - 1; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Added)
- rs.IgnoredAdds++;
- else if (line.Type == TextDiffLineType.Deleted)
- rs.IgnoredDeletes++;
- }
-
- for (int i = startLine - 1; i < endLine; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Added)
- {
- if (isCombined || !isOldSide)
- {
- rs.HasChanges = true;
- break;
- }
- }
- else if (line.Type == TextDiffLineType.Deleted)
- {
- if (isCombined || isOldSide)
- {
- rs.HasChanges = true;
- break;
- }
- }
- }
-
- return rs;
- }
-
- public void GenerateNewPatchFromSelection(string file, string fileBlobGuid, TextDiffSelection selection, bool revert, string output)
- {
- var isTracked = !string.IsNullOrEmpty(fileBlobGuid);
- var fileGuid = isTracked ? fileBlobGuid : "00000000";
-
- using var writer = new StreamWriter(output);
- writer.NewLine = "\n";
- writer.WriteLine($"diff --git a/{file} b/{file}");
- if (!revert && !isTracked)
- writer.WriteLine("new file mode 100644");
- writer.WriteLine($"index 00000000...{fileGuid}");
- writer.WriteLine($"--- {(revert || isTracked ? $"a/{file}" : "/dev/null")}");
- writer.WriteLine($"+++ b/{file}");
-
- var additions = selection.EndLine - selection.StartLine;
- if (selection.StartLine != 1)
- additions++;
-
- if (revert)
- {
- var totalLines = Lines.Count - 1;
- writer.WriteLine($"@@ -0,{totalLines - additions} +0,{totalLines} @@");
- for (int i = 1; i <= totalLines; i++)
- {
- var line = Lines[i];
- if (line.Type != TextDiffLineType.Added)
- continue;
-
- if (i >= selection.StartLine - 1 && i < selection.EndLine)
- WriteLine(writer, '+', line);
- else
- WriteLine(writer, ' ', line);
- }
- }
- else
- {
- writer.WriteLine($"@@ -0,0 +0,{additions} @@");
- for (int i = selection.StartLine - 1; i < selection.EndLine; i++)
- {
- var line = Lines[i];
- if (line.Type != TextDiffLineType.Added)
- continue;
- WriteLine(writer, '+', line);
- }
- }
-
- writer.Flush();
- }
-
- public void GeneratePatchFromSelection(string file, string fileTreeGuid, TextDiffSelection selection, bool revert, string output)
- {
- using var writer = new StreamWriter(output);
- writer.NewLine = "\n";
- writer.WriteLine($"diff --git a/{file} b/{file}");
- writer.WriteLine($"index 00000000...{fileTreeGuid} 100644");
- writer.WriteLine($"--- a/{file}");
- writer.WriteLine($"+++ b/{file}");
-
- // If last line of selection is a change. Find one more line.
- TextDiffLine tail = null;
- if (selection.EndLine < Lines.Count)
- {
- var lastLine = Lines[selection.EndLine - 1];
- if (lastLine.Type == TextDiffLineType.Added || lastLine.Type == TextDiffLineType.Deleted)
- {
- for (int i = selection.EndLine; i < Lines.Count; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Indicator)
- break;
- if (line.Type == TextDiffLineType.Normal ||
- (revert && line.Type == TextDiffLineType.Added) ||
- (!revert && line.Type == TextDiffLineType.Deleted))
- {
- tail = line;
- break;
- }
- }
- }
- }
-
- // If the first line is not indicator.
- if (Lines[selection.StartLine - 1].Type != TextDiffLineType.Indicator)
- {
- var indicator = selection.StartLine - 1;
- for (int i = selection.StartLine - 2; i >= 0; i--)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Indicator)
- {
- indicator = i;
- break;
- }
- }
-
- var ignoreAdds = 0;
- var ignoreRemoves = 0;
- for (int i = 0; i < indicator; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Added)
- {
- ignoreAdds++;
- }
- else if (line.Type == TextDiffLineType.Deleted)
- {
- ignoreRemoves++;
- }
- }
-
- for (int i = indicator; i < selection.StartLine - 1; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Indicator)
- {
- ProcessIndicatorForPatch(writer, line, i, selection.StartLine, selection.EndLine, ignoreRemoves, ignoreAdds, revert, tail != null);
- }
- else if (line.Type == TextDiffLineType.Added)
- {
- if (revert)
- WriteLine(writer, ' ', line);
- }
- else if (line.Type == TextDiffLineType.Deleted)
- {
- if (!revert)
- WriteLine(writer, ' ', line);
- }
- else if (line.Type == TextDiffLineType.Normal)
- {
- WriteLine(writer, ' ', line);
- }
- }
- }
-
- // Outputs the selected lines.
- for (int i = selection.StartLine - 1; i < selection.EndLine; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Indicator)
- {
- if (!ProcessIndicatorForPatch(writer, line, i, selection.StartLine, selection.EndLine, selection.IgnoredDeletes, selection.IgnoredAdds, revert, tail != null))
- break;
- }
- else if (line.Type == TextDiffLineType.Normal)
- {
- WriteLine(writer, ' ', line);
- }
- else if (line.Type == TextDiffLineType.Added)
- {
- WriteLine(writer, '+', line);
- }
- else if (line.Type == TextDiffLineType.Deleted)
- {
- WriteLine(writer, '-', line);
- }
- }
-
- if (tail != null)
- WriteLine(writer, ' ', tail);
- writer.Flush();
- }
-
- public void GeneratePatchFromSelectionSingleSide(string file, string fileTreeGuid, TextDiffSelection selection, bool revert, bool isOldSide, string output)
- {
- using var writer = new StreamWriter(output);
- writer.NewLine = "\n";
- writer.WriteLine($"diff --git a/{file} b/{file}");
- writer.WriteLine($"index 00000000...{fileTreeGuid} 100644");
- writer.WriteLine($"--- a/{file}");
- writer.WriteLine($"+++ b/{file}");
-
- // If last line of selection is a change. Find one more line.
- TextDiffLine tail = null;
- if (selection.EndLine < Lines.Count)
- {
- var lastLine = Lines[selection.EndLine - 1];
- if (lastLine.Type == TextDiffLineType.Added || lastLine.Type == TextDiffLineType.Deleted)
- {
- for (int i = selection.EndLine; i < Lines.Count; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Indicator)
- break;
- if (revert)
- {
- if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Added)
- {
- tail = line;
- break;
- }
- }
- else
- {
- if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Deleted)
- {
- tail = line;
- break;
- }
- }
- }
- }
- }
-
- // If the first line is not indicator.
- if (Lines[selection.StartLine - 1].Type != TextDiffLineType.Indicator)
- {
- var indicator = selection.StartLine - 1;
- for (int i = selection.StartLine - 2; i >= 0; i--)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Indicator)
- {
- indicator = i;
- break;
- }
- }
-
- var ignoreAdds = 0;
- var ignoreRemoves = 0;
- for (int i = 0; i < indicator; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Added)
- {
- ignoreAdds++;
- }
- else if (line.Type == TextDiffLineType.Deleted)
- {
- ignoreRemoves++;
- }
- }
-
- for (int i = indicator; i < selection.StartLine - 1; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Indicator)
- {
- ProcessIndicatorForPatchSingleSide(writer, line, i, selection.StartLine, selection.EndLine, ignoreRemoves, ignoreAdds, revert, isOldSide, tail != null);
- }
- else if (line.Type == TextDiffLineType.Added)
- {
- if (revert)
- WriteLine(writer, ' ', line);
- }
- else if (line.Type == TextDiffLineType.Deleted)
- {
- if (!revert)
- WriteLine(writer, ' ', line);
- }
- else if (line.Type == TextDiffLineType.Normal)
- {
- WriteLine(writer, ' ', line);
- }
- }
- }
-
- // Outputs the selected lines.
- for (int i = selection.StartLine - 1; i < selection.EndLine; i++)
- {
- var line = Lines[i];
- if (line.Type == TextDiffLineType.Indicator)
- {
- if (!ProcessIndicatorForPatchSingleSide(writer, line, i, selection.StartLine, selection.EndLine, selection.IgnoredDeletes, selection.IgnoredAdds, revert, isOldSide, tail != null))
- break;
- }
- else if (line.Type == TextDiffLineType.Normal)
- {
- WriteLine(writer, ' ', line);
- }
- else if (line.Type == TextDiffLineType.Added)
- {
- if (isOldSide)
- {
- if (revert)
- {
- WriteLine(writer, ' ', line);
- }
- else
- {
- selection.IgnoredAdds++;
- }
- }
- else
- {
- WriteLine(writer, '+', line);
- }
- }
- else if (line.Type == TextDiffLineType.Deleted)
- {
- if (isOldSide)
- {
- WriteLine(writer, '-', line);
- }
- else
- {
- if (!revert)
- {
- WriteLine(writer, ' ', line);
- }
- else
- {
- selection.IgnoredDeletes++;
- }
- }
- }
- }
-
- if (tail != null)
- WriteLine(writer, ' ', tail);
- writer.Flush();
- }
-
- private bool ProcessIndicatorForPatch(StreamWriter writer, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool tailed)
- {
- var match = REG_INDICATOR().Match(indicator.Content);
- var oldStart = int.Parse(match.Groups[1].Value);
- var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
- var oldCount = 0;
- var newCount = 0;
- for (int i = idx + 1; i < end; i++)
- {
- var test = Lines[i];
- if (test.Type == TextDiffLineType.Indicator)
- break;
-
- if (test.Type == TextDiffLineType.Normal)
- {
- oldCount++;
- newCount++;
- }
- else if (test.Type == TextDiffLineType.Added)
- {
- if (i < start - 1)
- {
- if (revert)
- {
- newCount++;
- oldCount++;
- }
- }
- else
- {
- newCount++;
- }
-
- if (i == end - 1 && tailed)
- {
- newCount++;
- oldCount++;
- }
- }
- else if (test.Type == TextDiffLineType.Deleted)
- {
- if (i < start - 1)
- {
- if (!revert)
- {
- newCount++;
- oldCount++;
- }
- }
- else
- {
- oldCount++;
- }
-
- if (i == end - 1 && tailed)
- {
- newCount++;
- oldCount++;
- }
- }
- }
-
- if (oldCount == 0 && newCount == 0)
- return false;
-
- writer.WriteLine($"@@ -{oldStart},{oldCount} +{newStart},{newCount} @@");
- return true;
- }
-
- private bool ProcessIndicatorForPatchSingleSide(StreamWriter writer, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool isOldSide, bool tailed)
- {
- var match = REG_INDICATOR().Match(indicator.Content);
- var oldStart = int.Parse(match.Groups[1].Value);
- var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
- var oldCount = 0;
- var newCount = 0;
- for (int i = idx + 1; i < end; i++)
- {
- var test = Lines[i];
- if (test.Type == TextDiffLineType.Indicator)
- break;
-
- if (test.Type == TextDiffLineType.Normal)
- {
- oldCount++;
- newCount++;
- }
- else if (test.Type == TextDiffLineType.Added)
- {
- if (i < start - 1 || isOldSide)
- {
- if (revert)
- {
- newCount++;
- oldCount++;
- }
- }
- else
- {
- newCount++;
- }
-
- if (i == end - 1 && tailed)
- {
- newCount++;
- oldCount++;
- }
- }
- else if (test.Type == TextDiffLineType.Deleted)
- {
- if (i < start - 1)
- {
- if (!revert)
- {
- newCount++;
- oldCount++;
- }
- }
- else
- {
- if (isOldSide)
- {
- oldCount++;
- }
- else
- {
- if (!revert)
- {
- newCount++;
- oldCount++;
- }
- }
- }
-
- if (i == end - 1 && tailed)
- {
- newCount++;
- oldCount++;
- }
- }
- }
-
- if (oldCount == 0 && newCount == 0)
- return false;
-
- writer.WriteLine($"@@ -{oldStart},{oldCount} +{newStart},{newCount} @@");
- return true;
- }
-
- private static void WriteLine(StreamWriter writer, char prefix, TextDiffLine line)
- {
- writer.Flush();
-
- writer.BaseStream.WriteByte((byte)prefix);
- writer.BaseStream.Write(line.RawContent);
- writer.BaseStream.WriteByte((byte)'\n');
-
- if (line.NoNewLineEndOfFile)
- writer.WriteLine("\\ No newline at end of file");
- }
-
- [GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
- private static partial Regex REG_INDICATOR();
+ public int OldMode { get; set; } = 0;
+ public int NewMode { get; set; } = 0;
+ public string OldHash { get; set; } = string.Empty;
+ public string NewHash { get; set; } = string.Empty;
}
public class LFSDiff
@@ -584,6 +63,10 @@ public class LFSDiff
public class BinaryDiff
{
+ public string Repository { get; set; } = string.Empty;
+ public string FilePath { get; set; } = string.Empty;
+ public string NewRevision { get; set; } = string.Empty;
+
public long OldSize { get; set; } = 0;
public long NewSize { get; set; } = 0;
}
@@ -600,6 +83,12 @@ public class ImageDiff
public string NewImageSize => New != null ? $"{New.PixelSize.Width} x {New.PixelSize.Height}" : "0 x 0";
}
+ public class EmptyFile
+ {
+ public const string SHA1 = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391";
+ public const string SHA256 = "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813";
+ }
+
public class NoOrEOLChange;
public class SubmoduleDiff
@@ -616,26 +105,12 @@ public class SubmoduleDiff
public class DiffResult
{
public bool IsBinary { get; set; } = false;
- public bool IsLFS { get; set; } = false;
+ public bool IsSubmoduleChange { get; set; } = false;
public string OldHash { get; set; } = string.Empty;
public string NewHash { get; set; } = string.Empty;
- public string OldMode { get; set; } = string.Empty;
- public string NewMode { get; set; } = string.Empty;
+ public int OldMode { get; set; } = 0;
+ public int NewMode { get; set; } = 0;
public TextDiff TextDiff { get; set; } = null;
public LFSDiff LFSDiff { get; set; } = null;
-
- public string FileModeChange
- {
- get
- {
- if (string.IsNullOrEmpty(OldMode) && string.IsNullOrEmpty(NewMode))
- return string.Empty;
-
- var oldDisplay = string.IsNullOrEmpty(OldMode) ? "0" : OldMode;
- var newDisplay = string.IsNullOrEmpty(NewMode) ? "0" : NewMode;
-
- return $"{oldDisplay} → {newDisplay}";
- }
- }
}
}
diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs
index d6d1030a98..04dc2acd4b 100644
--- a/src/Models/ExternalTool.cs
+++ b/src/Models/ExternalTool.cs
@@ -27,11 +27,13 @@ public LaunchOption(string title, string args)
public string Name { get; }
public string ExecFile { get; }
public Bitmap IconImage { get; }
+ public bool SupportOpenFolder { get; }
- public ExternalTool(string name, string icon, string execFile, Func> optionsGenerator = null)
+ public ExternalTool(string name, string icon, string execFile, Func> optionsGenerator, bool supportOpenFolder)
{
Name = name;
ExecFile = execFile;
+ SupportOpenFolder = supportOpenFolder;
_optionsGenerator = optionsGenerator;
@@ -61,6 +63,7 @@ public void Launch(string args)
FileName = ExecFile,
Arguments = args,
UseShellExecute = false,
+ CreateNoWindow = true,
});
}
}
@@ -82,30 +85,18 @@ public class VisualStudioInstance
public class JetBrainsState
{
- [JsonPropertyName("version")]
- public int Version { get; set; } = 0;
- [JsonPropertyName("appVersion")]
- public string AppVersion { get; set; } = string.Empty;
[JsonPropertyName("tools")]
public List Tools { get; set; } = new List();
}
public class JetBrainsTool
{
- [JsonPropertyName("channelId")]
- public string ChannelId { get; set; }
- [JsonPropertyName("toolId")]
- public string ToolId { get; set; }
[JsonPropertyName("productCode")]
public string ProductCode { get; set; }
- [JsonPropertyName("tag")]
- public string Tag { get; set; }
[JsonPropertyName("displayName")]
public string DisplayName { get; set; }
[JsonPropertyName("displayVersion")]
public string DisplayVersion { get; set; }
- [JsonPropertyName("buildNumber")]
- public string BuildNumber { get; set; }
[JsonPropertyName("installLocation")]
public string InstallLocation { get; set; }
[JsonPropertyName("launchCommand")]
@@ -130,7 +121,7 @@ public List Tools
public ExternalToolsFinder()
{
- var customPathsConfig = Path.Combine(Native.OS.DataDir, "external_editors.json");
+ var customPathsConfig = Path.Combine(Native.OS.BasicDirectories.ConfigDir, "external_editors.json");
try
{
if (File.Exists(customPathsConfig))
@@ -147,20 +138,20 @@ public ExternalToolsFinder()
_customization ??= new ExternalToolCustomization();
}
- public void TryAdd(string name, string icon, Func finder, Func> optionsGenerator = null)
+ public void TryAdd(string name, string icon, Func finder, Func> optionsGenerator = null, bool supportOpenFolder = true)
{
if (_customization.Excludes.Contains(name))
return;
if (_customization.Tools.TryGetValue(name, out var customPath) && File.Exists(customPath))
{
- Tools.Add(new ExternalTool(name, icon, customPath, optionsGenerator));
+ Tools.Add(new ExternalTool(name, icon, customPath, optionsGenerator, supportOpenFolder));
}
else
{
var path = finder();
if (!string.IsNullOrEmpty(path) && File.Exists(path))
- Tools.Add(new ExternalTool(name, icon, path, optionsGenerator));
+ Tools.Add(new ExternalTool(name, icon, path, optionsGenerator, supportOpenFolder));
}
}
@@ -196,8 +187,7 @@ public void Cursor(Func platformFinder)
public void FindJetBrainsFromToolbox(Func platformFinder)
{
- var exclude = new List { "fleet", "dotmemory", "dottrace", "resharper-u", "androidstudio" };
- var supportedIcons = new List { "CL", "DB", "DL", "DS", "GO", "JB", "PC", "PS", "PY", "QA", "QD", "RD", "RM", "RR", "WRS", "WS" };
+ var supported = new List { "AI", "CL", "DB", "DL", "DS", "GO", "IC", "IU", "JB", "PC", "PS", "PY", "QA", "QD", "RD", "RM", "RR", "WRS", "WS" };
var state = Path.Combine(platformFinder(), "state.json");
if (File.Exists(state))
{
@@ -207,13 +197,15 @@ public void FindJetBrainsFromToolbox(Func platformFinder)
var stateData = JsonSerializer.Deserialize(stream, JsonCodeGen.Default.JetBrainsState);
foreach (var tool in stateData.Tools)
{
- if (exclude.Contains(tool.ToolId.ToLowerInvariant()))
+ if (!supported.Contains(tool.ProductCode))
continue;
Tools.Add(new ExternalTool(
$"{tool.DisplayName} {tool.DisplayVersion}",
- supportedIcons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : "JetBrains/JB",
- Path.Combine(tool.InstallLocation, tool.LaunchCommand)));
+ $"JetBrains/{tool.ProductCode}",
+ Path.Combine(tool.InstallLocation, tool.LaunchCommand),
+ null,
+ true));
}
}
catch
diff --git a/src/Models/GitFlow.cs b/src/Models/GitFlow.cs
index 05ade0acee..a648d4db27 100644
--- a/src/Models/GitFlow.cs
+++ b/src/Models/GitFlow.cs
@@ -1,5 +1,15 @@
-namespace SourceGit.Models
+using System;
+using System.Collections.Generic;
+
+namespace SourceGit.Models
{
+ public enum GitFlowVersion
+ {
+ None = 0,
+ Legacy,
+ Next,
+ }
+
public enum GitFlowBranchType
{
None = 0,
@@ -10,18 +20,74 @@ public enum GitFlowBranchType
public class GitFlow
{
- public string Master { get; set; } = string.Empty;
- public string Develop { get; set; } = string.Empty;
+ public string ProductionBranch { get; set; } = string.Empty;
+ public string DevelopmentBranch { get; set; } = string.Empty;
public string FeaturePrefix { get; set; } = string.Empty;
public string ReleasePrefix { get; set; } = string.Empty;
public string HotfixPrefix { get; set; } = string.Empty;
+ public void Parse(Dictionary config)
+ {
+ // Reset to default values
+ ProductionBranch = string.Empty;
+ DevelopmentBranch = string.Empty;
+ FeaturePrefix = string.Empty;
+ ReleasePrefix = string.Empty;
+ HotfixPrefix = string.Empty;
+
+ // Try to parse `git-flow-next` style configuration first if the `git-flow-next` is installed.
+ if (Native.OS.GitFlowVersion == GitFlowVersion.Next &&
+ config.TryGetValue("gitflow.initialized", out var initialized) &&
+ initialized.Equals("true", StringComparison.OrdinalIgnoreCase))
+ {
+ foreach (var kv in config)
+ {
+ if (!kv.Key.StartsWith("gitflow.branch.", StringComparison.Ordinal))
+ continue;
+
+ if (kv.Key.EndsWith(".type", StringComparison.Ordinal) && kv.Value.Equals("base", StringComparison.Ordinal))
+ {
+ var b = kv.Key.Substring("gitflow.branch.".Length, kv.Key.Length - "gitflow.branch.".Length - ".type".Length);
+ if (config.ContainsKey($"gitflow.branch.{b}.parent"))
+ DevelopmentBranch = b;
+ else
+ ProductionBranch = b;
+ }
+ else if (kv.Key.EndsWith(".prefix", StringComparison.Ordinal))
+ {
+ var t = kv.Key.Substring("gitflow.branch.".Length, kv.Key.Length - "gitflow.branch.".Length - ".prefix".Length);
+ if (t.Equals("feature", StringComparison.Ordinal))
+ FeaturePrefix = kv.Value;
+ else if (t.Equals("release", StringComparison.Ordinal))
+ ReleasePrefix = kv.Value;
+ else if (t.Equals("hotfix", StringComparison.Ordinal))
+ HotfixPrefix = kv.Value;
+ }
+ }
+ }
+
+ // Fall back to `git-flow` style configuration if `git-flow-next` style is not valid.
+ if (!IsValid)
+ {
+ if (config.TryGetValue("gitflow.branch.master", out var masterName))
+ ProductionBranch = masterName;
+ if (config.TryGetValue("gitflow.branch.develop", out var developName))
+ DevelopmentBranch = developName;
+ if (config.TryGetValue("gitflow.prefix.feature", out var featurePrefix))
+ FeaturePrefix = featurePrefix;
+ if (config.TryGetValue("gitflow.prefix.release", out var releasePrefix))
+ ReleasePrefix = releasePrefix;
+ if (config.TryGetValue("gitflow.prefix.hotfix", out var hotfixPrefix))
+ HotfixPrefix = hotfixPrefix;
+ }
+ }
+
public bool IsValid
{
get
{
- return !string.IsNullOrEmpty(Master) &&
- !string.IsNullOrEmpty(Develop) &&
+ return !string.IsNullOrEmpty(ProductionBranch) &&
+ !string.IsNullOrEmpty(DevelopmentBranch) &&
!string.IsNullOrEmpty(FeaturePrefix) &&
!string.IsNullOrEmpty(ReleasePrefix) &&
!string.IsNullOrEmpty(HotfixPrefix);
diff --git a/src/Models/GitIgnoreFile.cs b/src/Models/GitIgnoreFile.cs
index a23456b678..2f3540e8b7 100644
--- a/src/Models/GitIgnoreFile.cs
+++ b/src/Models/GitIgnoreFile.cs
@@ -1,26 +1,36 @@
-using System.Collections.Generic;
-using System.IO;
-using Avalonia.Media;
+using System;
+using System.Collections.Generic;
namespace SourceGit.Models
{
- public class GitIgnoreFile
+ public record GitIgnoreFile(string DisplayName, string FullPath, string Pattern, bool IsLocalOnly)
{
- public static readonly List Supported = [new(true), new(false)];
+ public static List GetSupported(string repo, string gitDir, string pattern)
+ {
+ var supported = new List();
- public bool IsShared { get; set; }
- public string File => IsShared ? ".gitignore" : "/info/exclude";
- public string Desc => IsShared ? "Shared" : "Private";
- public IBrush Brush => IsShared ? Brushes.Green : Brushes.Gray;
+ // .gitignore in repository root.
+ supported.Add(new(".gitignore", $"{repo}/.gitignore", pattern, false));
- public GitIgnoreFile(bool isShared)
- {
- IsShared = isShared;
- }
+ // If pattern points to a file/directory that in sub-directory of repository, we should also support .gitignore in that sub-directory.
+ var normalizedPattern = pattern.Replace('\\', '/').TrimEnd('/');
+ var lastDirIdx = normalizedPattern.LastIndexOf('/');
+ if (lastDirIdx > 0)
+ {
+ var parentDir = normalizedPattern.Substring(0, lastDirIdx);
+ var overridedPattern = normalizedPattern.Substring(lastDirIdx + 1);
+ supported.Add(new($"{parentDir}/.gitignore", $"{repo}/{parentDir}/.gitignore", overridedPattern, false));
+ }
- public string GetFullPath(string repoPath, string gitDir)
- {
- return IsShared ? Path.Combine(repoPath, ".gitignore") : Path.Combine(gitDir, "info", "exclude");
+ // .git/info/exclude in git directory.
+ var normalizedGitDir = gitDir.Replace('\\', '/');
+ var testGitDir = $"{repo}/.git".Replace('\\', '/');
+ if (normalizedGitDir.Equals(testGitDir, StringComparison.Ordinal))
+ supported.Add(new(".git/info/exclude", $"{normalizedGitDir}/info/exclude", pattern, true));
+ else
+ supported.Add(new(".git/info/exclude", $"{gitDir}/info/exclude", pattern, true));
+
+ return supported;
}
}
}
diff --git a/src/Models/GitVersions.cs b/src/Models/GitVersions.cs
index 8aae63a3b2..71fb4657ec 100644
--- a/src/Models/GitVersions.cs
+++ b/src/Models/GitVersions.cs
@@ -16,5 +16,15 @@ public static class GitVersions
/// The minimal version of Git that supports the `stash push` command with the `--staged` option.
///
public static readonly System.Version STASH_PUSH_ONLY_STAGED = new(2, 35, 0);
+
+ ///
+ /// The minimal version of Git that supports the `git merge-tree --write-tree` command, which is used for testing merge results without actually performing a merge.
+ ///
+ public static readonly System.Version TESTING_MERGE = new(2, 38, 0);
+
+ ///
+ /// The minimal version of Git that supports the `git replay` command.
+ ///
+ public static readonly System.Version REPLAY = new(2, 44, 0);
}
}
diff --git a/src/Models/HistoryFilter.cs b/src/Models/HistoryFilter.cs
index b09f074cb2..369b237d72 100644
--- a/src/Models/HistoryFilter.cs
+++ b/src/Models/HistoryFilter.cs
@@ -1,4 +1,5 @@
-using CommunityToolkit.Mvvm.ComponentModel;
+using System.Text.Json.Serialization;
+using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.Models
{
@@ -38,9 +39,11 @@ public FilterMode Mode
set => SetProperty(ref _mode, value);
}
- public bool IsBranch
+ [JsonIgnore]
+ public bool IsValid
{
- get => Type != FilterType.Tag;
+ get => _isValid;
+ set => SetProperty(ref _isValid, value);
}
public HistoryFilter()
@@ -56,5 +59,6 @@ public HistoryFilter(string pattern, FilterType type, FilterMode mode)
private string _pattern = string.Empty;
private FilterMode _mode = FilterMode.None;
+ private bool _isValid = true;
}
}
diff --git a/src/Models/IpcChannel.cs b/src/Models/IpcChannel.cs
index 702f0630be..1a169d2acf 100644
--- a/src/Models/IpcChannel.cs
+++ b/src/Models/IpcChannel.cs
@@ -1,6 +1,8 @@
using System;
using System.IO;
using System.IO.Pipes;
+using System.Security.Cryptography;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -8,18 +10,56 @@ namespace SourceGit.Models
{
public class IpcChannel : IDisposable
{
- public bool IsFirstInstance { get; }
+ public bool IsFirstInstance { get; private set; }
public event Action MessageReceived;
public IpcChannel()
{
- try
+ IsFirstInstance = false;
+
+ var lockFile = GetLockFilePath();
+ _lockFilePath = lockFile.Path;
+
+ if (OperatingSystem.IsLinux() && lockFile.NeedChangePermissions)
+ {
+ // On Linux, if the lock file is created in the XDG_RUNTIME_DIR, we need to set the permissions to 700 (rwx------) to ensure that only the current user can access it.
+ try
+ {
+ _singletonLock = new FileStream(_lockFilePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);
+ File.SetUnixFileMode(_lockFilePath, File.GetUnixFileMode(_lockFilePath) | UnixFileMode.StickyBit);
+ IsFirstInstance = true;
+ }
+ catch
+ {
+ try
+ {
+ _singletonLock = new FileStream(_lockFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
+ IsFirstInstance = true;
+ }
+ catch
+ {
+ // Just ignore the exception and assume that another instance is running.
+ }
+ }
+ }
+ else
+ {
+ try
+ {
+ _singletonLock = File.Open(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
+ IsFirstInstance = true;
+ }
+ catch
+ {
+ // Just ignore the exception and assume that another instance is running.
+ }
+ }
+
+ if (IsFirstInstance)
{
- _singletonLock = File.Open(Path.Combine(Native.OS.DataDir, "process.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
- IsFirstInstance = true;
_server = new NamedPipeServerStream(
- "SourceGitIPCChannel" + Environment.UserName,
+ GetPipeName(),
PipeDirection.In,
-1,
PipeTransmissionMode.Byte,
@@ -27,17 +67,13 @@ public IpcChannel()
_cancellationTokenSource = new CancellationTokenSource();
Task.Run(StartServer);
}
- catch
- {
- IsFirstInstance = false;
- }
}
public void SendToFirstInstance(string cmd)
{
try
{
- using (var client = new NamedPipeClientStream(".", "SourceGitIPCChannel" + Environment.UserName, PipeDirection.Out, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly))
+ using (var client = new NamedPipeClientStream(".", GetPipeName(), PipeDirection.Out, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly))
{
client.Connect(1000);
if (!client.IsConnected)
@@ -65,6 +101,45 @@ public void Dispose()
{
_cancellationTokenSource?.Cancel();
_singletonLock?.Dispose();
+
+ if (IsFirstInstance && File.Exists(_lockFilePath))
+ File.Delete(_lockFilePath);
+ }
+
+ private LockFile GetLockFilePath()
+ {
+ // On Windows and macOS, we can use the cache directory for the lock file.
+ if (!OperatingSystem.IsLinux())
+ return new LockFile(Path.Combine(Native.OS.BasicDirectories.CacheDir, "process.lock"), false);
+
+ // On Linux, we should first check if the app is running in portable mode.
+ var appImage = Environment.GetEnvironmentVariable("APPIMAGE");
+ if (!string.IsNullOrEmpty(appImage) && File.Exists(appImage))
+ {
+ var portableDir = Path.Combine(Path.GetDirectoryName(appImage)!, "data");
+ if (Directory.Exists(portableDir))
+ return new LockFile(Path.Combine(portableDir, "process.lock"), false);
+ }
+
+ // If not in portable mode, we should use the XDG_RUNTIME_DIR environment variable for the lock file.
+ var runtimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR");
+ if (string.IsNullOrEmpty(runtimeDir) || !Directory.Exists(runtimeDir))
+ return new LockFile(Path.Combine(Native.OS.BasicDirectories.CacheDir, "process.lock"), false);
+
+ return new LockFile(Path.Combine(runtimeDir, "sourcegit.instance.lock"), true);
+ }
+
+ private string GetPipeName()
+ {
+ // SourceGit does not support multiple instances on macOS, so we can use a fixed pipe name for macOS.
+ if (OperatingSystem.IsMacOS())
+ return "SourceGit";
+
+ // Windows and Linux can have multiple instances of SourceGit running (portable-mode), so we need to generate a unique pipe name based on the data directory.
+ var dataDir = Native.OS.BasicDirectories.CacheDir.Replace('\\', '/').TrimEnd('/');
+ var hashStr = $"{Environment.UserName}_{dataDir}";
+ var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(hashStr))).Substring(0, 10);
+ return $"SG_{hash}";
}
private async void StartServer()
@@ -93,6 +168,9 @@ private async void StartServer()
}
}
+ private record LockFile(string Path, bool NeedChangePermissions);
+
+ private string _lockFilePath = null;
private FileStream _singletonLock = null;
private NamedPipeServerStream _server = null;
private CancellationTokenSource _cancellationTokenSource = null;
diff --git a/src/Models/Locales.cs b/src/Models/Locales.cs
index 0274333366..5bdb2431bc 100644
--- a/src/Models/Locales.cs
+++ b/src/Models/Locales.cs
@@ -9,9 +9,11 @@ public class Locale
public static readonly List Supported = new List() {
new Locale("Deutsch", "de_DE"),
+ new Locale("Ελληνικά", "el_GR"),
new Locale("English", "en_US"),
new Locale("Español", "es_ES"),
new Locale("Français", "fr_FR"),
+ new Locale("עברית", "he_IL"),
new Locale("Bahasa Indonesia", "id_ID"),
new Locale("Italiano", "it_IT"),
new Locale("Português (Brasil)", "pt_BR"),
diff --git a/src/Models/Null.cs b/src/Models/Null.cs
index 1820d4d050..9e25977d07 100644
--- a/src/Models/Null.cs
+++ b/src/Models/Null.cs
@@ -1,4 +1,7 @@
namespace SourceGit.Models
{
- public class Null;
+ public class Null
+ {
+ public static readonly Null Instance = new Null();
+ }
}
diff --git a/src/Models/PatchGenerator.cs b/src/Models/PatchGenerator.cs
new file mode 100644
index 0000000000..d73b78e1aa
--- /dev/null
+++ b/src/Models/PatchGenerator.cs
@@ -0,0 +1,511 @@
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Models
+{
+ public partial class PatchGenerator
+ {
+ [GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
+ private static partial Regex REG_INDICATOR();
+
+ public bool IsValid => _selection != null;
+
+ public PatchGenerator(DiffOption option, TextDiff diff, int startLine, int endLine, bool isCombined, bool isOldSide)
+ {
+ _targetFile = option.Path;
+ _diff = diff;
+ _isCombined = isCombined;
+ _isOldSide = isOldSide;
+
+ var lines = _diff.Lines;
+ var hasChanges = false;
+
+ var selection = new Selection();
+ selection.StartLine = startLine;
+ selection.EndLine = endLine;
+
+ for (int i = 0; i < startLine; i++)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Added)
+ selection.IgnoredAdds++;
+ else if (line.Type == TextDiffLineType.Deleted)
+ selection.IgnoredDeletes++;
+ }
+
+ for (int i = startLine; i <= endLine; i++)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Added)
+ {
+ if (isCombined || !isOldSide)
+ {
+ hasChanges = true;
+ break;
+ }
+ }
+ else if (line.Type == TextDiffLineType.Deleted)
+ {
+ if (isCombined || isOldSide)
+ {
+ hasChanges = true;
+ break;
+ }
+ }
+ }
+
+ if (hasChanges)
+ _selection = selection;
+ }
+
+ public void Generate(string saveTo, bool revert)
+ {
+ if (_selection == null)
+ return;
+
+ using var writer = CreateWriter(saveTo, revert);
+
+ if (_isCombined)
+ GenerateCombined(writer, revert);
+ else
+ GenerateForSingleSide(writer, revert);
+ }
+
+ private void GenerateCombined(StreamWriter writer, bool revert)
+ {
+ var lines = _diff.Lines;
+ var tail = FindTail(revert);
+
+ // If the first line is not indicator.
+ if (lines[_selection.StartLine].Type != TextDiffLineType.Indicator)
+ {
+ var indicator = _selection.StartLine;
+ for (int i = _selection.StartLine - 1; i >= 0; i--)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Indicator)
+ {
+ indicator = i;
+ break;
+ }
+ }
+
+ var ignoreAdds = 0;
+ var ignoreRemoves = 0;
+ for (int i = 0; i < indicator; i++)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Added)
+ {
+ ignoreAdds++;
+ }
+ else if (line.Type == TextDiffLineType.Deleted)
+ {
+ ignoreRemoves++;
+ }
+ }
+
+ for (int i = indicator; i < _selection.StartLine; i++)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Indicator)
+ {
+ ProcessIndicator(writer, line, i, ignoreRemoves, ignoreAdds, revert, tail != null);
+ }
+ else if (line.Type == TextDiffLineType.Added)
+ {
+ if (revert)
+ Append(writer, ' ', line);
+ }
+ else if (line.Type == TextDiffLineType.Deleted)
+ {
+ if (!revert)
+ Append(writer, ' ', line);
+ }
+ else if (line.Type == TextDiffLineType.Normal)
+ {
+ Append(writer, ' ', line);
+ }
+ }
+ }
+
+ // Outputs the selected lines.
+ for (int i = _selection.StartLine; i <= _selection.EndLine; i++)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Indicator)
+ {
+ if (!ProcessIndicator(writer, line, i, _selection.IgnoredDeletes, _selection.IgnoredAdds, revert, tail != null))
+ break;
+ }
+ else if (line.Type == TextDiffLineType.Normal)
+ {
+ Append(writer, ' ', line);
+ }
+ else if (line.Type == TextDiffLineType.Added)
+ {
+ Append(writer, '+', line);
+ }
+ else if (line.Type == TextDiffLineType.Deleted)
+ {
+ Append(writer, '-', line);
+ }
+ }
+
+ if (tail != null)
+ Append(writer, ' ', tail);
+ }
+
+ private void GenerateForSingleSide(StreamWriter writer, bool revert)
+ {
+ var lines = _diff.Lines;
+ var tail = FindTail(revert);
+
+ // If the first line is not indicator.
+ if (lines[_selection.StartLine].Type != TextDiffLineType.Indicator)
+ {
+ var indicator = _selection.StartLine;
+ for (int i = _selection.StartLine - 1; i >= 0; i--)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Indicator)
+ {
+ indicator = i;
+ break;
+ }
+ }
+
+ var ignoreAdds = 0;
+ var ignoreRemoves = 0;
+ for (int i = 0; i < indicator; i++)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Added)
+ {
+ ignoreAdds++;
+ }
+ else if (line.Type == TextDiffLineType.Deleted)
+ {
+ ignoreRemoves++;
+ }
+ }
+
+ for (int i = indicator; i < _selection.StartLine; i++)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Indicator)
+ {
+ ProcessIndicatorForSingleSide(writer, line, i, ignoreRemoves, ignoreAdds, revert, tail != null);
+ }
+ else if (line.Type == TextDiffLineType.Added)
+ {
+ if (revert)
+ Append(writer, ' ', line);
+ }
+ else if (line.Type == TextDiffLineType.Deleted)
+ {
+ if (!revert)
+ Append(writer, ' ', line);
+ }
+ else if (line.Type == TextDiffLineType.Normal)
+ {
+ Append(writer, ' ', line);
+ }
+ }
+ }
+
+ // Outputs the selected lines.
+ for (int i = _selection.StartLine; i <= _selection.EndLine; i++)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Indicator)
+ {
+ if (!ProcessIndicatorForSingleSide(writer, line, i, _selection.IgnoredDeletes, _selection.IgnoredAdds, revert, tail != null))
+ break;
+ }
+ else if (line.Type == TextDiffLineType.Normal)
+ {
+ Append(writer, ' ', line);
+ }
+ else if (line.Type == TextDiffLineType.Added)
+ {
+ if (_isOldSide)
+ {
+ if (revert)
+ Append(writer, ' ', line);
+ else
+ _selection.IgnoredAdds++;
+ }
+ else
+ {
+ Append(writer, '+', line);
+ }
+ }
+ else if (line.Type == TextDiffLineType.Deleted)
+ {
+ if (_isOldSide)
+ {
+ Append(writer, '-', line);
+ }
+ else
+ {
+ if (!revert)
+ Append(writer, ' ', line);
+ else
+ _selection.IgnoredDeletes++;
+ }
+ }
+ }
+
+ if (tail != null)
+ Append(writer, ' ', tail);
+ }
+
+ private StreamWriter CreateWriter(string saveTo, bool revert)
+ {
+ var writer = new StreamWriter(saveTo) { NewLine = "\n" };
+ writer.WriteLine($"diff --git \"a/{_targetFile}\" \"b/{_targetFile}\"");
+
+ if (_diff.OldMode == 0)
+ {
+ if (_diff.NewMode == 0 || revert)
+ {
+ writer.WriteLine($"index {_diff.OldHash}..{_diff.NewHash}");
+ writer.WriteLine($"--- a/{_targetFile}");
+ }
+ else
+ {
+ writer.WriteLine($"new file mode {_diff.NewMode}");
+ writer.WriteLine($"--- /dev/null");
+ }
+ }
+ else if (_diff.NewMode == 0)
+ {
+ writer.WriteLine($"index {_diff.OldHash}..{_diff.NewHash} {_diff.OldMode}");
+ writer.WriteLine($"--- a/{_targetFile}");
+ }
+ else
+ {
+ writer.WriteLine($"--- a/{_targetFile}");
+ }
+
+ writer.WriteLine($"+++ b/{_targetFile}");
+ return writer;
+ }
+
+ private TextDiffLine FindTail(bool revert)
+ {
+ var lines = _diff.Lines;
+
+ if (_selection.EndLine < lines.Count - 1)
+ {
+ var lastLine = lines[_selection.EndLine];
+ if (lastLine.Type == TextDiffLineType.Added || lastLine.Type == TextDiffLineType.Deleted)
+ {
+ for (int i = _selection.EndLine + 1; i < lines.Count; i++)
+ {
+ var line = lines[i];
+ if (line.Type == TextDiffLineType.Indicator)
+ break;
+
+ if (revert)
+ {
+ if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Added)
+ return line;
+ }
+ else
+ {
+ if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Deleted)
+ return line;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void Append(StreamWriter writer, char prefix, TextDiffLine line)
+ {
+ writer.Flush();
+
+ writer.BaseStream.WriteByte((byte)prefix);
+ writer.BaseStream.Write(line.RawContent);
+ writer.BaseStream.WriteByte((byte)'\n');
+
+ if (line.NoNewLineEndOfFile)
+ writer.WriteLine("\\ No newline at end of file");
+ }
+
+ private bool ProcessIndicator(StreamWriter writer, TextDiffLine indicator, int idx, int ignoreRemoves, int ignoreAdds, bool revert, bool tailed)
+ {
+ var lines = _diff.Lines;
+ var start = _selection.StartLine;
+ var end = _selection.EndLine;
+ var match = REG_INDICATOR().Match(indicator.Content);
+ var oldStart = int.Parse(match.Groups[1].Value);
+ var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
+ var oldCount = 0;
+ var newCount = 0;
+
+ for (int i = idx + 1; i <= end; i++)
+ {
+ var test = lines[i];
+ if (test.Type == TextDiffLineType.Indicator)
+ break;
+
+ if (test.Type == TextDiffLineType.Normal)
+ {
+ oldCount++;
+ newCount++;
+ }
+ else if (test.Type == TextDiffLineType.Added)
+ {
+ if (i < start)
+ {
+ if (revert)
+ {
+ newCount++;
+ oldCount++;
+ }
+ }
+ else
+ {
+ newCount++;
+ }
+
+ if (i == end && tailed)
+ {
+ newCount++;
+ oldCount++;
+ }
+ }
+ else if (test.Type == TextDiffLineType.Deleted)
+ {
+ if (i < start)
+ {
+ if (!revert)
+ {
+ newCount++;
+ oldCount++;
+ }
+ }
+ else
+ {
+ oldCount++;
+ }
+
+ if (i == end && tailed)
+ {
+ newCount++;
+ oldCount++;
+ }
+ }
+ }
+
+ if (oldCount == 0 && newCount == 0)
+ return false;
+
+ writer.WriteLine($"@@ -{oldStart},{oldCount} +{newStart},{newCount} @@");
+ return true;
+ }
+
+ private bool ProcessIndicatorForSingleSide(StreamWriter writer, TextDiffLine indicator, int idx, int ignoreRemoves, int ignoreAdds, bool revert, bool tailed)
+ {
+ var lines = _diff.Lines;
+ var start = _selection.StartLine;
+ var end = _selection.EndLine;
+ var match = REG_INDICATOR().Match(indicator.Content);
+ var oldStart = int.Parse(match.Groups[1].Value);
+ var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
+ var oldCount = 0;
+ var newCount = 0;
+
+ for (int i = idx + 1; i <= end; i++)
+ {
+ var test = lines[i];
+ if (test.Type == TextDiffLineType.Indicator)
+ break;
+
+ if (test.Type == TextDiffLineType.Normal)
+ {
+ oldCount++;
+ newCount++;
+ }
+ else if (test.Type == TextDiffLineType.Added)
+ {
+ if (i < start || _isOldSide)
+ {
+ if (revert)
+ {
+ newCount++;
+ oldCount++;
+ }
+ }
+ else
+ {
+ newCount++;
+ }
+
+ if (i == end && tailed)
+ {
+ newCount++;
+ oldCount++;
+ }
+ }
+ else if (test.Type == TextDiffLineType.Deleted)
+ {
+ if (i < start)
+ {
+ if (!revert)
+ {
+ newCount++;
+ oldCount++;
+ }
+ }
+ else
+ {
+ if (_isOldSide)
+ {
+ oldCount++;
+ }
+ else
+ {
+ if (!revert)
+ {
+ newCount++;
+ oldCount++;
+ }
+ }
+ }
+
+ if (i == end && tailed)
+ {
+ newCount++;
+ oldCount++;
+ }
+ }
+ }
+
+ if (oldCount == 0 && newCount == 0)
+ return false;
+
+ writer.WriteLine($"@@ -{oldStart},{oldCount} +{newStart},{newCount} @@");
+ return true;
+ }
+
+ private class Selection
+ {
+ public int StartLine { get; set; } = 0;
+ public int EndLine { get; set; } = 0;
+ public int IgnoredAdds { get; set; } = 0;
+ public int IgnoredDeletes { get; set; } = 0;
+ }
+
+ private string _targetFile = null;
+ private TextDiff _diff = null;
+ private bool _isCombined = false;
+ private bool _isOldSide = false;
+ private Selection _selection = null;
+ }
+}
diff --git a/src/Models/RefName.cs b/src/Models/RefName.cs
new file mode 100644
index 0000000000..42ad6d785d
--- /dev/null
+++ b/src/Models/RefName.cs
@@ -0,0 +1,84 @@
+using System;
+
+namespace SourceGit.Models
+{
+ ///
+ /// Validates branch and tag names using the same rules that git itself enforces,
+ /// mirroring `git check-ref-format --allow-onelevel`. Rules are taken from the git-check-ref-format documentation.
+ /// Rule 2 (a refname must contain at least one slash) is intentionally waived, matching `--allow-onelevel`.
+ ///
+ public static class RefName
+ {
+ public static bool IsValidBranchName(string name)
+ {
+ if (string.Equals(name, "HEAD", StringComparison.Ordinal))
+ return false;
+
+ return IsValidRefName(name);
+ }
+
+ public static bool IsValidTagName(string name) => IsValidRefName(name);
+
+ private static bool IsValidRefName(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ return false;
+
+ // Anything starting with '-' is treated as a CLI option.
+ if (name.StartsWith('-'))
+ return false;
+
+ // Rule 9: cannot be the single character '@'.
+ if (name.Equals("@", StringComparison.Ordinal))
+ return false;
+
+ // Rule 6: cannot begin or end with '/', or contain consecutive slashes.
+ if (name[0] == '/' || name[^1] == '/' || name.Contains("//", StringComparison.Ordinal))
+ return false;
+
+ // Rule 7: cannot end with a dot.
+ if (name[^1] == '.')
+ return false;
+
+ // Rule 3: cannot contain two consecutive dots.
+ if (name.Contains("..", StringComparison.Ordinal))
+ return false;
+
+ // Rule 8: cannot contain the sequence '@{'.
+ if (name.Contains("@{", StringComparison.Ordinal))
+ return false;
+
+ // Rules 4, 5 & 10: no control chars, DEL, space, or ~ ^ : ? * [ \.
+ foreach (var ch in name)
+ {
+ if (ch is < ' ' or '\x7f')
+ return false;
+
+ switch (ch)
+ {
+ case ' ':
+ case '~':
+ case '^':
+ case ':':
+ case '?':
+ case '*':
+ case '[':
+ case '\\':
+ return false;
+ }
+ }
+
+ // Rule 1: no slash-separated component may begin with a dot or end with ".lock".
+ foreach (var component in name.Split('/'))
+ {
+ if (component[0] == '.')
+ return false;
+
+ if (component.EndsWith(".lock", StringComparison.Ordinal))
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs
index 12681b4b4d..ab16874d26 100644
--- a/src/Models/Remote.cs
+++ b/src/Models/Remote.cs
@@ -31,6 +31,8 @@ public partial class Remote
public string Name { get; set; }
public string URL { get; set; }
+ public string PushURL { get; set; }
+ public string PrivateSSHKey { get; set; }
public bool DisableAutoFetch { get; set; }
public static bool IsSSH(string url)
diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs
index 17beedb824..506e6ded78 100644
--- a/src/Models/RepositorySettings.cs
+++ b/src/Models/RepositorySettings.cs
@@ -30,6 +30,12 @@ public string ConventionalTypesOverride
set;
} = string.Empty;
+ public bool EnableRecursiveWhenAutoUpdatingSubmodules
+ {
+ get;
+ set;
+ } = true;
+
public bool AskBeforeAutoUpdatingSubmodules
{
get;
@@ -48,12 +54,6 @@ public AvaloniaList CommitTemplates
set;
} = [];
- public AvaloniaList CommitMessages
- {
- get;
- set;
- } = [];
-
public AvaloniaList CustomActions
{
get;
@@ -116,25 +116,6 @@ public void Save()
}
}
- public void PushCommitMessage(string message)
- {
- message = message.Trim().ReplaceLineEndings("\n");
- var existIdx = CommitMessages.IndexOf(message);
- if (existIdx == 0)
- return;
-
- if (existIdx > 0)
- {
- CommitMessages.Move(existIdx, 0);
- return;
- }
-
- if (CommitMessages.Count > 9)
- CommitMessages.RemoveRange(9, CommitMessages.Count - 9);
-
- CommitMessages.Insert(0, message);
- }
-
public CustomAction AddNewCustomAction()
{
var act = new CustomAction() { Name = "Unnamed Action" };
@@ -165,10 +146,7 @@ public void MoveCustomActionDown(CustomAction act)
private static string HashContent(string source)
{
var hash = MD5.HashData(Encoding.Default.GetBytes(source));
- var builder = new StringBuilder(hash.Length * 2);
- foreach (var c in hash)
- builder.Append(c.ToString("x2"));
- return builder.ToString();
+ return Convert.ToHexStringLower(hash);
}
private static Dictionary _cache = new();
diff --git a/src/Models/RepositoryUIStates.cs b/src/Models/RepositoryUIStates.cs
index dd9fa14007..0b35f8d618 100644
--- a/src/Models/RepositoryUIStates.cs
+++ b/src/Models/RepositoryUIStates.cs
@@ -27,12 +27,24 @@ public bool IsSHAColumnVisibleInHistory
set;
} = true;
- public bool IsDateTimeColumnVisibleInHistory
+ public bool IsAuthorTimeColumnVisibleInHistory
+ {
+ get;
+ set;
+ } = false;
+
+ public bool IsCommitTimeColumnVisibleInHistory
{
get;
set;
} = true;
+ public double AuthorColumnWidth
+ {
+ get;
+ set;
+ } = 120;
+
public bool EnableTopoOrderInHistory
{
get;
@@ -219,6 +231,18 @@ public AvaloniaList HistoryFilters
set;
} = [];
+ public bool IsHistoryFiltersCollapsed
+ {
+ get;
+ set;
+ } = false;
+
+ public List RecentCommitMessages
+ {
+ get;
+ set;
+ } = [];
+
public static RepositoryUIStates Load(string gitDir)
{
var fileInfo = new FileInfo(Path.Combine(gitDir, "sourcegit.uistates"));
@@ -352,7 +376,7 @@ public void RenameBranchFilter(string oldName, string newName)
if (filter.Type == FilterType.LocalBranch &&
filter.Pattern.Equals(oldName, StringComparison.Ordinal))
{
- filter.Pattern = $"refs/heads/{newName}";
+ filter.Pattern = newName;
break;
}
}
@@ -376,51 +400,8 @@ public void RemoveBranchFiltersByPrefix(string pattern)
HistoryFilters.Remove(filter);
}
- public string BuildHistoryParams()
+ public string BuildHistoryParams(string gitDir)
{
- var includedRefs = new List();
- var excludedBranches = new List();
- var excludedRemotes = new List();
- var excludedTags = new List();
- foreach (var filter in HistoryFilters)
- {
- if (filter.Type == FilterType.LocalBranch)
- {
- if (filter.Mode == FilterMode.Included)
- includedRefs.Add(filter.Pattern);
- else if (filter.Mode == FilterMode.Excluded)
- excludedBranches.Add($"--exclude=\"{filter.Pattern.AsSpan(11)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
- }
- else if (filter.Type == FilterType.LocalBranchFolder)
- {
- if (filter.Mode == FilterMode.Included)
- includedRefs.Add($"--branches={filter.Pattern.AsSpan(11)}/*");
- else if (filter.Mode == FilterMode.Excluded)
- excludedBranches.Add($"--exclude=\"{filter.Pattern.AsSpan(11)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
- }
- else if (filter.Type == FilterType.RemoteBranch)
- {
- if (filter.Mode == FilterMode.Included)
- includedRefs.Add(filter.Pattern);
- else if (filter.Mode == FilterMode.Excluded)
- excludedRemotes.Add($"--exclude=\"{filter.Pattern.AsSpan(13)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
- }
- else if (filter.Type == FilterType.RemoteBranchFolder)
- {
- if (filter.Mode == FilterMode.Included)
- includedRefs.Add($"--remotes={filter.Pattern.AsSpan(13)}/*");
- else if (filter.Mode == FilterMode.Excluded)
- excludedRemotes.Add($"--exclude=\"{filter.Pattern.AsSpan(13)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
- }
- else if (filter.Type == FilterType.Tag)
- {
- if (filter.Mode == FilterMode.Included)
- includedRefs.Add($"refs/tags/{filter.Pattern}");
- else if (filter.Mode == FilterMode.Excluded)
- excludedTags.Add($"--exclude=\"{filter.Pattern}\" --decorate-refs-exclude=\"refs/tags/{filter.Pattern}\"");
- }
- }
-
var builder = new StringBuilder();
if (EnableTopoOrderInHistory)
@@ -437,46 +418,95 @@ public string BuildHistoryParams()
if (HistoryShowFlags.HasFlag(HistoryShowFlags.SimplifyByDecoration))
builder.Append("--simplify-by-decoration ");
- if (includedRefs.Count > 0)
+ var mode = GetHistoryFilterMode();
+ if (mode == FilterMode.None)
+ builder.Append("--branches --remotes --tags HEAD");
+ else if (mode == FilterMode.Included)
+ BuildHistoryParamsForIncluded(builder);
+ else
+ BuildHistoryParamsForExcluded(builder, gitDir);
+
+ return builder.ToString();
+ }
+
+ private void BuildHistoryParamsForIncluded(StringBuilder builder)
+ {
+ foreach (var filter in HistoryFilters)
{
- foreach (var r in includedRefs)
- {
- builder.Append(r);
- builder.Append(' ');
- }
+ if (filter.Type == FilterType.LocalBranch)
+ builder.Append(filter.Pattern).Append(' ');
+ else if (filter.Type == FilterType.LocalBranchFolder)
+ builder.Append($"--branches={filter.Pattern.AsSpan(11)}/* ");
+ else if (filter.Type == FilterType.RemoteBranch)
+ builder.Append(filter.Pattern).Append(' ');
+ else if (filter.Type == FilterType.RemoteBranchFolder)
+ builder.Append($"--remotes={filter.Pattern.AsSpan(13)}/* ");
+ else if (filter.Type == FilterType.Tag)
+ builder.Append($"refs/tags/{filter.Pattern} ");
}
- else if (excludedBranches.Count + excludedRemotes.Count + excludedTags.Count > 0)
+ }
+
+ private void BuildHistoryParamsForExcluded(StringBuilder builder, string gitDir)
+ {
+ var excludedBranches = new List();
+ var excludedRemotes = new List();
+ var excludedTags = new List();
+ foreach (var filter in HistoryFilters)
{
- foreach (var b in excludedBranches)
- {
- builder.Append(b);
- builder.Append(' ');
- }
+ if (filter.Type == FilterType.LocalBranch)
+ excludedBranches.Add($"--exclude=\"{filter.Pattern.AsSpan(11)}\" --decorate-refs-exclude=\"{filter.Pattern}\" ");
+ else if (filter.Type == FilterType.LocalBranchFolder)
+ excludedBranches.Add($"--exclude=\"{filter.Pattern.AsSpan(11)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\" ");
+ else if (filter.Type == FilterType.RemoteBranch)
+ excludedRemotes.Add($"--exclude=\"{filter.Pattern.AsSpan(13)}\" --decorate-refs-exclude=\"{filter.Pattern}\" ");
+ else if (filter.Type == FilterType.RemoteBranchFolder)
+ excludedRemotes.Add($"--exclude=\"{filter.Pattern.AsSpan(13)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\" ");
+ else if (filter.Type == FilterType.Tag)
+ excludedTags.Add($"--exclude=\"{filter.Pattern}\" --decorate-refs-exclude=\"refs/tags/{filter.Pattern}\" ");
+ }
- builder.Append("--exclude=HEAD --branches ");
+ foreach (var b in excludedBranches)
+ builder.Append(b);
- foreach (var r in excludedRemotes)
- {
- builder.Append(r);
- builder.Append(' ');
- }
+ builder.Append("--branches ");
- builder.Append("--exclude=origin/HEAD --remotes ");
+ var isInProgress = File.Exists(Path.Combine(gitDir, "CHERRY_PICK_HEAD")) ||
+ Directory.Exists(Path.Combine(gitDir, "rebase-merge")) ||
+ Directory.Exists(Path.Combine(gitDir, "rebase-apply")) ||
+ File.Exists(Path.Combine(gitDir, "REVERT_HEAD")) ||
+ File.Exists(Path.Combine(gitDir, "MERGE_HEAD"));
+ if (isInProgress)
+ builder.Append("HEAD ");
- foreach (var t in excludedTags)
- {
- builder.Append(t);
- builder.Append(' ');
- }
+ foreach (var r in excludedRemotes)
+ builder.Append(r);
- builder.Append("--tags ");
- }
- else
+ builder.Append("--exclude=origin/HEAD --remotes ");
+
+ foreach (var t in excludedTags)
+ builder.Append(t);
+
+ builder.Append("--tags ");
+ }
+
+ public void AddRecentCommitMessage(string message)
+ {
+ message = message.Trim().ReplaceLineEndings("\n");
+ var existIdx = RecentCommitMessages.IndexOf(message);
+ if (existIdx == 0)
+ return;
+
+ if (existIdx > 0)
{
- builder.Append("--branches --remotes --tags HEAD");
+ RecentCommitMessages.RemoveAt(existIdx);
+ RecentCommitMessages.Insert(0, message);
+ return;
}
- return builder.ToString();
+ if (RecentCommitMessages.Count > 9)
+ RecentCommitMessages.RemoveRange(9, RecentCommitMessages.Count - 9);
+
+ RecentCommitMessages.Insert(0, message);
}
private string _file = string.Empty;
diff --git a/src/Models/RevisionFile.cs b/src/Models/RevisionFile.cs
index 9a9fe7d209..86e9a1aedd 100644
--- a/src/Models/RevisionFile.cs
+++ b/src/Models/RevisionFile.cs
@@ -6,7 +6,18 @@ namespace SourceGit.Models
{
public class RevisionBinaryFile
{
+ public string Repository { get; set; } = string.Empty;
+ public string File { get; set; } = string.Empty;
+ public string Revision { get; set; } = string.Empty;
public long Size { get; set; } = 0;
+
+ public RevisionBinaryFile(string repo, string file, string revision, long size)
+ {
+ Repository = repo;
+ File = file;
+ Revision = revision;
+ Size = size;
+ }
}
public class RevisionImageFile
@@ -40,5 +51,18 @@ public class RevisionSubmodule
public Commit Commit { get; set; } = null;
public CommitFullMessage FullMessage { get; set; } = null;
public int UncommittedChanges { get; set; } = 0;
+
+ public RevisionSubmodule(string hash)
+ {
+ Commit = new Commit() { SHA = hash };
+ FullMessage = new CommitFullMessage() { Message = string.Empty };
+ }
+
+ public RevisionSubmodule(Commit commit, string message, int uncommittedChanges)
+ {
+ Commit = commit;
+ FullMessage = new CommitFullMessage() { Message = message };
+ UncommittedChanges = uncommittedChanges;
+ }
}
}
diff --git a/src/Models/SSHKeyPair.cs b/src/Models/SSHKeyPair.cs
new file mode 100644
index 0000000000..1094b03137
--- /dev/null
+++ b/src/Models/SSHKeyPair.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace SourceGit.Models
+{
+ public record SSHKeyType(string Name, string Description, string Cmdline)
+ {
+ public static readonly List Supported = [
+ new SSHKeyType("ED25519", "Recommended", "-t ed25519"),
+ new SSHKeyType("RSA", "4096 Bits", "-t rsa -b 4096"),
+ ];
+ }
+
+ public class SSHKeyPair
+ {
+ public string PrivateKeyPath { get; set; }
+ public string PublicKeyPath { get; set; }
+ public string RawPublicKey { get; set; }
+ public string Fingerprint { get; set; } = "--- (invalid)";
+ public string Name => Path.GetFileName(PrivateKeyPath);
+
+ public SSHKeyPair(string privateKey, string publicKey)
+ {
+ PrivateKeyPath = privateKey;
+ PublicKeyPath = publicKey;
+ RawPublicKey = File.ReadAllText(publicKey);
+
+ var parts = RawPublicKey.Split(' ', StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length != 3)
+ return;
+
+ try
+ {
+ var blob = Convert.FromBase64String(parts[1]);
+ Fingerprint = "SHA256:" + Convert.ToBase64String(SHA256.HashData(blob)).TrimEnd('=');
+ }
+ catch
+ {
+ // Ignore errors and keep the fingerprint as invalid
+ }
+ }
+ }
+}
diff --git a/src/Models/SafeDirectories.cs b/src/Models/SafeDirectories.cs
new file mode 100644
index 0000000000..5fcf24a3af
--- /dev/null
+++ b/src/Models/SafeDirectories.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Models
+{
+ ///
+ /// Helpers for the `safe.directory` protection introduced by git 2.35.2 (CVE-2022-24765).
+ /// Git refuses to use a repository whose top-level directory is owned by another user (which is
+ /// always the case for network shares / UNC paths) unless the path is listed in the `safe.directory` config.
+ ///
+ public static partial class SafeDirectories
+ {
+ public static bool IsUntrustedRepository(string output)
+ {
+ if (string.IsNullOrWhiteSpace(output))
+ return false;
+
+ return output.Contains("detected dubious ownership", StringComparison.OrdinalIgnoreCase) ||
+ output.Contains("unsafe repository", StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Tries to get the value that should be written into the `safe.directory` config.
+ /// Prefers the value suggested by git itself (which knows the correct form for the current platform),
+ /// and falls back to building a value from the given path.
+ ///
+ public static bool TryGetSafeDirectoryValue(string path, string output, out string value)
+ {
+ value = ParseSuggestedValue(output);
+ if (!string.IsNullOrEmpty(value))
+ return true;
+
+ var normalized = Normalize(path);
+ if (string.IsNullOrEmpty(normalized))
+ return false;
+
+ // Git for Windows uses the `%(prefix)/` prefix for UNC paths.
+ value = OperatingSystem.IsWindows() && normalized.StartsWith("//", StringComparison.Ordinal) ? $"%(prefix)/{normalized}" : normalized;
+ return true;
+ }
+
+ private static string Normalize(string path)
+ {
+ return path?.Replace('\\', '/').TrimEnd('/') ?? string.Empty;
+ }
+
+ private static string ParseSuggestedValue(string output)
+ {
+ if (string.IsNullOrEmpty(output))
+ return string.Empty;
+
+ var match = REG_SAFE_DIRECTORY_HINT().Match(output);
+ if (!match.Success)
+ return string.Empty;
+
+ return Dequote(match.Groups["value"].Value.Trim());
+ }
+
+ ///
+ /// Git quotes the suggested value with `sq_quote_buf()`, which escapes `'` and `!` as `'\''` and
+ /// `'\!'`. Decodes it back to the plain path (see `quote.c` of git).
+ ///
+ private static string Dequote(string value)
+ {
+ if (value.Length > 1 && value[0] == '\'')
+ {
+ var builder = new StringBuilder(value.Length);
+ for (var i = 1; i < value.Length; i++)
+ {
+ var c = value[i];
+ if (c != '\'')
+ {
+ builder.Append(c);
+ continue;
+ }
+
+ if (i == value.Length - 1)
+ return builder.ToString();
+
+ if (i + 3 < value.Length && value[i + 1] == '\\' && (value[i + 2] == '\'' || value[i + 2] == '!') && value[i + 3] == '\'')
+ {
+ builder.Append(value[i + 2]);
+ i += 3;
+ continue;
+ }
+
+ return string.Empty;
+ }
+
+ return string.Empty;
+ }
+
+ if (value.Length > 1 && value[0] == '"' && value[^1] == '"')
+ return value[1..^1];
+
+ return value;
+ }
+
+ [GeneratedRegex(@"--add\s+safe\.directory\s+(?[^\r\n]+)", RegexOptions.Multiline)]
+ private static partial Regex REG_SAFE_DIRECTORY_HINT();
+ }
+}
diff --git a/src/Models/ShellOrTerminal.cs b/src/Models/ShellOrTerminal.cs
index fa818bfe72..e8e5854754 100644
--- a/src/Models/ShellOrTerminal.cs
+++ b/src/Models/ShellOrTerminal.cs
@@ -44,7 +44,10 @@ static ShellOrTerminal()
new ShellOrTerminal("iterm2", "iTerm", "iTerm"),
new ShellOrTerminal("warp", "Warp", "Warp"),
new ShellOrTerminal("ghostty", "Ghostty", "Ghostty"),
- new ShellOrTerminal("kitty", "kitty", "kitty")
+ new ShellOrTerminal("kitty", "kitty", "kitty"),
+ new ShellOrTerminal("otty", "Otty", "Otty"),
+ new ShellOrTerminal("cmux", "cmux", "cmux"),
+ new ShellOrTerminal("mux0", "mux0", "mux0"),
};
}
else
diff --git a/src/Models/Statistics.cs b/src/Models/Statistics.cs
index 700c9311b4..4ef9927fc5 100644
--- a/src/Models/Statistics.cs
+++ b/src/Models/Statistics.cs
@@ -2,18 +2,11 @@
using System.Collections.Generic;
using System.Globalization;
-using LiveChartsCore;
-using LiveChartsCore.Defaults;
-using LiveChartsCore.SkiaSharpView;
-using LiveChartsCore.SkiaSharpView.Painting;
-
-using SkiaSharp;
-
namespace SourceGit.Models
{
public enum StatisticsMode
{
- All,
+ All = 0,
ThisMonth,
ThisWeek,
}
@@ -24,45 +17,103 @@ public class StatisticsAuthor(User user, int count)
public int Count { get; set; } = count;
}
+ public class StatisticsSamples
+ {
+ public DateTime StartTime { get; }
+ public DateTime EndTime { get; }
+ public int Count { get; }
+ public int MaxValue { get; }
+ public bool HasSpecialUser => _user != null;
+
+ public StatisticsSamples(StatisticsMode mode, DateTime start, DateTime end, Dictionary all, int maxValue)
+ {
+ _mode = mode;
+ _all = all;
+
+ StartTime = start;
+ EndTime = end;
+
+ if (maxValue < 8)
+ MaxValue = 8;
+ else if (maxValue < 16)
+ MaxValue = 16;
+ else if (maxValue < 24)
+ MaxValue = 24;
+ else
+ MaxValue = (int)(Math.Floor(maxValue / 6.0) * 8.0);
+
+ if (maxValue == 0)
+ Count = 0;
+ else
+ Count = mode switch
+ {
+ StatisticsMode.All => (end.Year - start.Year) * 12 + (end.Month - start.Month) + 1,
+ _ => all.Count,
+ };
+ }
+
+ public void WithUser(Dictionary user)
+ {
+ _user = user;
+ }
+
+ public (string, int, int) GetSample(DateTime time)
+ {
+ var label = _mode switch
+ {
+ StatisticsMode.All => time.ToString("yyyy/MM"),
+ StatisticsMode.ThisMonth => time.ToString("MM/dd"),
+ _ => s_weekdays[(int)time.DayOfWeek]
+ };
+
+ var total = _all.GetValueOrDefault(time, 0);
+ var user = _user?.GetValueOrDefault(time, 0) ?? 0;
+ return (label, total, user);
+ }
+
+ public DateTime NextSampleTime(DateTime time)
+ {
+ if (_mode != StatisticsMode.All)
+ return time.AddDays(-1);
+
+ if (time.Month == 1)
+ return new DateTime(time.Year - 1, 12, 1).ToLocalTime().Date;
+
+ return new DateTime(time.Year, time.Month - 1, 1).ToLocalTime().Date;
+ }
+
+ private static readonly string[] s_weekdays = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"];
+ private StatisticsMode _mode;
+ private Dictionary _all;
+ private Dictionary _user;
+ }
+
public class StatisticsReport
{
+ public StatisticsMode Mode { get; }
public int Total { get; set; } = 0;
public List Authors { get; set; } = new();
- public List Series { get; set; } = new();
- public List XAxes { get; set; } = new();
- public List YAxes { get; set; } = new();
- public StatisticsAuthor SelectedAuthor { get => _selectedAuthor; set => ChangeAuthor(value); }
public StatisticsReport(StatisticsMode mode, DateTime start)
{
- _mode = mode;
-
- YAxes.Add(new Axis()
- {
- TextSize = 10,
- MinLimit = 0,
- SeparatorsPaint = new SolidColorPaint(new SKColor(0x40808080)) { StrokeThickness = 1 }
- });
+ Mode = mode;
if (mode == StatisticsMode.ThisWeek)
{
- for (int i = 0; i < 7; i++)
- _mapSamples.Add(start.AddDays(i), 0);
+ _minSampleTime = start;
+ _maxSampleTime = start.AddDays(6);
- XAxes.Add(new DateTimeAxis(TimeSpan.FromDays(1), v => WEEKDAYS[(int)v.DayOfWeek]) { TextSize = 10 });
+ for (int i = 0; i < 7; i++)
+ _all.Add(start.AddDays(i), 0);
}
else if (mode == StatisticsMode.ThisMonth)
{
var now = DateTime.Now;
var maxDays = DateTime.DaysInMonth(now.Year, now.Month);
+ _minSampleTime = start;
+ _maxSampleTime = start.AddDays(maxDays - 1);
for (int i = 0; i < maxDays; i++)
- _mapSamples.Add(start.AddDays(i), 0);
-
- XAxes.Add(new DateTimeAxis(TimeSpan.FromDays(1), v => $"{v:MM/dd}") { TextSize = 10 });
- }
- else
- {
- XAxes.Add(new DateTimeAxis(TimeSpan.FromDays(30), v => $"{v:yyyy/MM}") { TextSize = 10 });
+ _all.Add(start.AddDays(i), 0);
}
}
@@ -71,15 +122,24 @@ public void AddCommit(DateTime time, User author)
Total++;
DateTime normalized;
- if (_mode == StatisticsMode.ThisWeek || _mode == StatisticsMode.ThisMonth)
+ if (Mode == StatisticsMode.ThisWeek || Mode == StatisticsMode.ThisMonth)
+ {
normalized = time.Date;
+ }
else
- normalized = new DateTime(time.Year, time.Month, 1).ToLocalTime();
+ {
+ normalized = new DateTime(time.Year, time.Month, 1).ToLocalTime().Date;
+
+ if (normalized < _minSampleTime)
+ _minSampleTime = normalized;
+ if (normalized > _maxSampleTime)
+ _maxSampleTime = normalized;
+ }
- if (_mapSamples.TryGetValue(normalized, out var vs))
- _mapSamples[normalized] = vs + 1;
+ if (_all.TryGetValue(normalized, out var vs))
+ _all[normalized] = vs + 1;
else
- _mapSamples.Add(normalized, 1);
+ _all.Add(normalized, 1);
if (_mapUsers.TryGetValue(author, out var vu))
_mapUsers[author] = vu + 1;
@@ -95,10 +155,9 @@ public void AddCommit(DateTime time, User author)
}
else
{
- _mapUserSamples.Add(author, new Dictionary
- {
- { normalized, 1 }
- });
+ var added = new Dictionary();
+ added.Add(normalized, 1);
+ _mapUserSamples.Add(author, added);
}
}
@@ -107,76 +166,30 @@ public void Complete()
foreach (var kv in _mapUsers)
Authors.Add(new StatisticsAuthor(kv.Key, kv.Value));
- Authors.Sort((l, r) => r.Count - l.Count);
-
- var samples = new List();
- foreach (var kv in _mapSamples)
- samples.Add(new DateTimePoint(kv.Key, kv.Value));
-
- Series.Add(
- new ColumnSeries()
- {
- Values = samples,
- Stroke = null,
- Fill = null,
- Padding = 1,
- }
- );
-
_mapUsers.Clear();
- _mapSamples.Clear();
- }
-
- public void ChangeColor(uint color)
- {
- _fillColor = color;
-
- var fill = new SKColor(color);
-
- if (Series.Count > 0 && Series[0] is ColumnSeries total)
- total.Fill = new SolidColorPaint(_selectedAuthor == null ? fill : fill.WithAlpha(51));
-
- if (Series.Count > 1 && Series[1] is ColumnSeries user)
- user.Fill = new SolidColorPaint(fill);
- }
-
- public void ChangeAuthor(StatisticsAuthor author)
- {
- if (author == _selectedAuthor)
- return;
+ Authors.Sort((l, r) => r.Count - l.Count);
- _selectedAuthor = author;
- Series.RemoveRange(1, Series.Count - 1);
- if (author == null || !_mapUserSamples.TryGetValue(author.User, out var userSamples))
+ foreach (var kv in _all)
{
- ChangeColor(_fillColor);
- return;
+ if (kv.Value > _maxSampleValue)
+ _maxSampleValue = kv.Value;
}
+ }
- var samples = new List();
- foreach (var kv in userSamples)
- samples.Add(new DateTimePoint(kv.Key, kv.Value));
-
- Series.Add(
- new ColumnSeries()
- {
- Values = samples,
- Stroke = null,
- Fill = null,
- Padding = 1,
- }
- );
-
- ChangeColor(_fillColor);
+ public StatisticsSamples GetSamples(StatisticsAuthor withUser)
+ {
+ var samples = new StatisticsSamples(Mode, _minSampleTime, _maxSampleTime, _all, _maxSampleValue);
+ if (withUser != null && _mapUserSamples.TryGetValue(withUser.User, out var userSamples))
+ samples.WithUser(userSamples);
+ return samples;
}
- private static readonly string[] WEEKDAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
- private StatisticsMode _mode;
+ private DateTime _minSampleTime = DateTime.MaxValue;
+ private DateTime _maxSampleTime = DateTime.MinValue;
+ private int _maxSampleValue = 0;
+ private Dictionary _all = new();
private Dictionary _mapUsers = new();
- private Dictionary _mapSamples = new();
private Dictionary> _mapUserSamples = new();
- private StatisticsAuthor _selectedAuthor = null;
- private uint _fillColor = 255;
}
public class Statistics
@@ -207,7 +220,7 @@ public void AddCommit(string author, double timestamp)
_users.Add(email, user);
}
- var time = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime();
+ var time = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().Date;
if (time >= _thisWeekStart)
Week.AddCommit(time, user);
diff --git a/src/Models/TextMateHelper.cs b/src/Models/TextMateHelper.cs
index 38c0aaa724..bdddeb1b54 100644
--- a/src/Models/TextMateHelper.cs
+++ b/src/Models/TextMateHelper.cs
@@ -27,14 +27,21 @@ public static class GrammarUtility
new ExtraGrammar("source.hxml", [".hxml"], "hxml.json"),
new ExtraGrammar("text.html.jsp", [".jsp", ".jspf", ".tag"], "jsp.json"),
new ExtraGrammar("source.vue", [".vue"], "vue.json"),
+ new ExtraGrammar("source.erlang", [".erl", ".escript", ".hrl"], "erlang.json"),
+ new ExtraGrammar("source.ocaml", [".ml", ".mli"], "ocaml.json"),
+ new ExtraGrammar("source.ocamllex", [".mll"], "ocamllex.json"),
+ new ExtraGrammar("source.ocamlyacc", [".mly"], "ocamlyacc.json"),
+ new ExtraGrammar("source.swift", [".swift"], "swift.json"),
];
+ private static readonly Dictionary s_cachedRawGrammars = new();
+
public static string GetScope(string file, RegistryOptions reg)
{
var extension = Path.GetExtension(file);
if (extension == ".h")
extension = ".cpp";
- else if (extension is ".resx" or ".plist" or ".manifest")
+ else if (extension is ".resx" or ".plist" or ".manifest" or ".sln" or ".slnx")
extension = ".xml";
else if (extension == ".command")
extension = ".sh";
@@ -53,6 +60,12 @@ public static string GetScope(string file, RegistryOptions reg)
public static IRawGrammar GetGrammar(string scopeName, RegistryOptions reg)
{
+ if (string.IsNullOrEmpty(scopeName))
+ return null;
+
+ if (s_cachedRawGrammars.TryGetValue(scopeName, out var cached))
+ return cached;
+
foreach (var grammar in s_extraGrammars)
{
if (grammar.Scope.Equals(scopeName, StringComparison.OrdinalIgnoreCase))
@@ -62,7 +75,9 @@ public static IRawGrammar GetGrammar(string scopeName, RegistryOptions reg)
try
{
- return GrammarReader.ReadGrammarSync(new StreamReader(asset));
+ var raw = GrammarReader.ReadGrammarSync(new StreamReader(asset));
+ s_cachedRawGrammars.Add(scopeName, raw);
+ return raw;
}
catch
{
@@ -71,7 +86,9 @@ public static IRawGrammar GetGrammar(string scopeName, RegistryOptions reg)
}
}
- return reg.GetGrammar(scopeName);
+ var fallback = reg.GetGrammar(scopeName);
+ s_cachedRawGrammars.Add(scopeName, fallback);
+ return fallback;
}
private record ExtraGrammar(string Scope, List Extensions, string File)
@@ -88,11 +105,21 @@ public class RegistryOptionsWrapper(ThemeName defaultTheme) : IRegistryOptions
public IRawTheme GetTheme(string scopeName) => _backend.GetTheme(scopeName);
public IRawTheme GetDefaultTheme() => _backend.GetDefaultTheme();
- public IRawTheme LoadTheme(ThemeName name) => _backend.LoadTheme(name);
public ICollection GetInjections(string scopeName) => _backend.GetInjections(scopeName);
public IRawGrammar GetGrammar(string scopeName) => GrammarUtility.GetGrammar(scopeName, _backend);
public string GetScope(string filename) => GrammarUtility.GetScope(filename, _backend);
+ public IRawTheme LoadTheme(ThemeName name)
+ {
+ if (s_cachedTheme.TryGetValue(name, out var cached))
+ return cached;
+
+ var loaded = _backend.LoadTheme(name);
+ s_cachedTheme.Add(name, loaded);
+ return loaded;
+ }
+
+ private static readonly Dictionary s_cachedTheme = new();
private readonly RegistryOptions _backend = new(defaultTheme);
}
@@ -122,7 +149,7 @@ public static void SetGrammarByFileName(TextMate.Installation installation, stri
if (reg.LastScope != scope)
{
reg.LastScope = scope;
- installation.SetGrammar(reg.GetScope(filePath));
+ installation.SetGrammar(scope);
GC.Collect();
}
}
diff --git a/src/Models/ThemeOverrides.cs b/src/Models/ThemeOverrides.cs
index 531cbccdd2..c80576dc83 100644
--- a/src/Models/ThemeOverrides.cs
+++ b/src/Models/ThemeOverrides.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-
using Avalonia.Media;
namespace SourceGit.Models
@@ -9,7 +8,6 @@ public class ThemeOverrides
public Dictionary BasicColors { get; set; } = new Dictionary();
public double GraphPenThickness { get; set; } = 2;
public double OpacityForNotMergedCommits { get; set; } = 0.5;
- public bool UseMicaOnWindows11 { get; set; } = true;
public List GraphColors { get; set; } = new List();
}
}
diff --git a/src/Models/User.cs b/src/Models/User.cs
index 15a45762bc..a1b984a040 100644
--- a/src/Models/User.cs
+++ b/src/Models/User.cs
@@ -4,35 +4,23 @@ namespace SourceGit.Models
{
public class User
{
- public static readonly User Invalid = new User();
+ public static readonly User Invalid = new User(string.Empty);
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
- public User()
- {
- // Only used by User.Invalid
- }
-
public User(string data)
{
var parts = data.Split('±', 2);
if (parts.Length < 2)
- parts = [string.Empty, data];
-
- Name = parts[0];
- Email = parts[1].TrimStart('<').TrimEnd('>');
- _hash = data.GetHashCode();
- }
-
- public override bool Equals(object obj)
- {
- return obj is User other && Name == other.Name && Email == other.Email;
- }
-
- public override int GetHashCode()
- {
- return _hash;
+ {
+ Email = data;
+ }
+ else
+ {
+ Name = parts[0];
+ Email = parts[1];
+ }
}
public static User FindOrAdd(string data)
@@ -46,6 +34,5 @@ public override string ToString()
}
private static ConcurrentDictionary _caches = new ConcurrentDictionary();
- private readonly int _hash;
}
}
diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs
index ce18fe4be2..66e246bc07 100644
--- a/src/Models/Watcher.cs
+++ b/src/Models/Watcher.cs
@@ -43,6 +43,7 @@ public Watcher(IRepository repo, string fullpath, string gitDir)
combined.Renamed += OnRepositoryChanged;
combined.Changed += OnRepositoryChanged;
combined.Deleted += OnRepositoryChanged;
+ combined.Error += OnWatcherError;
combined.EnableRaisingEvents = false;
_watchers.Add(combined);
@@ -58,6 +59,7 @@ public Watcher(IRepository repo, string fullpath, string gitDir)
wc.Renamed += OnWorkingCopyChanged;
wc.Changed += OnWorkingCopyChanged;
wc.Deleted += OnWorkingCopyChanged;
+ wc.Error += OnWatcherError;
wc.EnableRaisingEvents = false;
var git = new FileSystemWatcher();
@@ -69,6 +71,7 @@ public Watcher(IRepository repo, string fullpath, string gitDir)
git.Renamed += OnGitDirChanged;
git.Changed += OnGitDirChanged;
git.Deleted += OnGitDirChanged;
+ git.Error += OnWatcherError;
git.EnableRaisingEvents = false;
_watchers.Add(wc);
@@ -240,6 +243,21 @@ private void OnRepositoryChanged(object o, FileSystemEventArgs e)
HandleWorkingCopyFileChanged(name, e.FullPath);
}
+ ///
+ /// The internal buffer overflowed (a large fetch or checkout writes thousands of files), so
+ /// some events were dropped and we cannot know which. Reload everything once things settle.
+ ///
+ private void OnWatcherError(object o, ErrorEventArgs e)
+ {
+ var desired = DateTime.Now.AddSeconds(1).ToFileTime();
+ Interlocked.Exchange(ref _updateBranch, desired);
+ Interlocked.Exchange(ref _updateTags, desired);
+ Interlocked.Exchange(ref _updateStashes, desired);
+ Interlocked.Exchange(ref _updateWC, desired);
+ if (_repo.MayHaveSubmodules())
+ Interlocked.Exchange(ref _updateSubmodules, desired);
+ }
+
private void OnGitDirChanged(object o, FileSystemEventArgs e)
{
if (string.IsNullOrEmpty(e.Name))
@@ -290,7 +308,7 @@ private void HandleGitDirFileChanged(string name)
{
Interlocked.Exchange(ref _updateTags, DateTime.Now.AddSeconds(.5).ToFileTime());
}
- else if (name.StartsWith("refs/stash", StringComparison.Ordinal))
+ else if (name.StartsWith("refs/stash", StringComparison.Ordinal) || name.Equals("logs/refs/stash", StringComparison.Ordinal))
{
Interlocked.Exchange(ref _updateStashes, DateTime.Now.AddSeconds(.5).ToFileTime());
}
@@ -302,6 +320,12 @@ private void HandleGitDirFileChanged(string name)
{
Interlocked.Exchange(ref _updateBranch, DateTime.Now.AddSeconds(.5).ToFileTime());
}
+ else if (name.Equals("packed-refs", StringComparison.Ordinal))
+ {
+ var desired = DateTime.Now.AddSeconds(.5).ToFileTime();
+ Interlocked.Exchange(ref _updateBranch, desired);
+ Interlocked.Exchange(ref _updateTags, desired);
+ }
else if (name.StartsWith("reftable/", StringComparison.Ordinal))
{
var desired = DateTime.Now.AddSeconds(.5).ToFileTime();
diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs
index e411cbcef9..3d09f82663 100644
--- a/src/Native/Linux.cs
+++ b/src/Native/Linux.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Avalonia;
@@ -13,6 +14,9 @@ namespace SourceGit.Native
[SupportedOSPlatform("linux")]
internal class Linux : OS.IBackend
{
+ [DllImport("libc", SetLastError = true)]
+ private static extern int kill(int pid, int sig);
+
public void SetupApp(AppBuilder builder)
{
builder.With(new X11PlatformOptions() { EnableIme = true });
@@ -20,6 +24,8 @@ public void SetupApp(AppBuilder builder)
public void SetupWindow(Window window)
{
+ window.BorderThickness = new Thickness(0);
+
if (OS.UseSystemWindowFrame)
{
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.Default;
@@ -33,53 +39,55 @@ public void SetupWindow(Window window)
}
}
- public void HideSelf()
- {
- // Do Nothing. Never used.
- }
-
- public void HideOtherApplications()
+ public OS.Directories GetOrCreateDirectories()
{
- // Do Nothing. Never used.
- }
-
- public void ShowAllApplications()
- {
- // Do Nothing. Never used.
- }
+ var dirs = new OS.Directories();
+ var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
- public string GetDataDir()
- {
// AppImage supports portable mode
var appImage = Environment.GetEnvironmentVariable("APPIMAGE");
if (!string.IsNullOrEmpty(appImage) && File.Exists(appImage))
{
var portableDir = Path.Combine(Path.GetDirectoryName(appImage)!, "data");
if (Directory.Exists(portableDir))
- return portableDir;
+ {
+ dirs.ConfigDir = portableDir;
+ dirs.CacheDir = portableDir;
+ return dirs;
+ }
}
- // Runtime data dir: ~/.sourcegit
- var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
- var dataDir = Path.Combine(home, ".sourcegit");
- if (Directory.Exists(dataDir))
- return dataDir;
+ // XDG Base Directory Specification: https://specifications.freedesktop.org/basedir/latest/
+ dirs.ConfigDir = GetXdgDirectory("XDG_CONFIG_HOME", Path.Combine(home, ".config"), "SourceGit");
+ dirs.CacheDir = GetXdgDirectory("XDG_CACHE_HOME", Path.Combine(home, ".cache"), "SourceGit");
+
+ // If the app basic dirs already exist, we can skip the migration step
+ if (Directory.Exists(dirs.ConfigDir) && Directory.Exists(dirs.CacheDir))
+ return dirs;
- // Migrate old data: ~/.config/SourceGit
- var oldDataDir = Path.Combine(home, ".config", "SourceGit");
- if (Directory.Exists(oldDataDir))
+ // Create the config and cache directories if they don't exist
+ if (!Directory.Exists(dirs.ConfigDir))
+ Directory.CreateDirectory(dirs.ConfigDir);
+ if (!Directory.Exists(dirs.CacheDir))
+ Directory.CreateDirectory(dirs.CacheDir);
+
+ // Migrate legacy data dir: ~/.sourcegit to XDG standard directories
+ var legacyDir = Path.Combine(home, ".sourcegit");
+ if (Directory.Exists(legacyDir))
{
try
{
- Directory.Move(oldDataDir, dataDir);
+ File.Copy(Path.Combine(legacyDir, "preference.json"), Path.Combine(dirs.ConfigDir, "preference.json"), true);
+ Directory.Move(Path.Combine(legacyDir, "avatars"), Path.Combine(dirs.CacheDir, "avatars"));
+ Directory.Delete(legacyDir, true);
}
catch
{
- // Ignore errors
+ // Ignore any errors during migration
}
}
- return dataDir;
+ return dirs;
}
public string FindGitExecutable()
@@ -169,6 +177,39 @@ public void OpenWithDefaultEditor(string file)
}
}
+ public bool SupportSetSid()
+ {
+ return true;
+ }
+
+ public string GetSetSidExecutable()
+ {
+ return "setsid";
+ }
+
+ public void TerminateProcess(Process proc)
+ {
+ if (kill(-proc.Id, 15) != 0)
+ {
+ // If the process already exited, we can just ignore the error.
+ if (Marshal.GetLastPInvokeError() == 3 /* ESRCH */)
+ return;
+
+ // Actually, this will not be called since the process is
+ // spawned by us (EPERM will not happen), and SIGTERM (15)
+ // is a valid signal (EINVAL will not happen).
+ // See https://www.man7.org/linux/man-pages/man2/kill.2.html
+ try
+ {
+ proc.Kill(true);
+ }
+ catch
+ {
+ // Ignore any errors when trying to kill the process
+ }
+ }
+ }
+
private string FindExecutable(string filename)
{
var pathVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
@@ -183,5 +224,14 @@ private string FindExecutable(string filename)
var local = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "bin", filename);
return File.Exists(local) ? local : string.Empty;
}
+
+ private string GetXdgDirectory(string envVar, string fallback, string subDirName)
+ {
+ var dir = Environment.GetEnvironmentVariable(envVar);
+ if (!string.IsNullOrEmpty(dir) && Directory.Exists(dir))
+ return Path.Combine(dir, subDirName);
+
+ return Path.Combine(fallback, subDirName);
+ }
}
}
diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs
index 6431d771f3..06d09e8fa3 100644
--- a/src/Native/MacOS.cs
+++ b/src/Native/MacOS.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@@ -14,17 +15,13 @@ namespace SourceGit.Native
[SupportedOSPlatform("macOS")]
internal class MacOS : OS.IBackend
{
- [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_getClass")]
- public static extern IntPtr objc_getClass(string name);
-
- [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "sel_registerName")]
- public static extern IntPtr sel_registerName(string name);
-
- [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
- public static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector);
+ [DllImport("/usr/lib/libSystem.B.dylib", SetLastError = true)]
+ private static extern int kill(int pid, int sig);
- [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
- public static extern IntPtr objc_msgSendWithArg(IntPtr receiver, IntPtr selector, IntPtr arg);
+ public MacOS()
+ {
+ _setsidExecutable = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "setsid");
+ }
public void SetupApp(AppBuilder builder)
{
@@ -40,7 +37,7 @@ public void SetupApp(AppBuilder builder)
else if (!path.Contains("/opt/homebrew/", StringComparison.Ordinal))
path = "/opt/homebrew/bin:/opt/homebrew/sbin:" + path;
- var customPathFile = Path.Combine(OS.DataDir, "PATH");
+ var customPathFile = Path.Combine(OS.BasicDirectories.ConfigDir, "PATH");
if (File.Exists(customPathFile))
{
var env = File.ReadAllText(customPathFile).Trim();
@@ -55,46 +52,22 @@ public void SetupWindow(Window window)
{
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome;
window.ExtendClientAreaToDecorationsHint = true;
+ window.BorderThickness = new Thickness(0);
}
- public void HideSelf()
- {
- IntPtr nsApplicationClass = objc_getClass("NSApplication");
- IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication");
- IntPtr nsApp = objc_msgSend(nsApplicationClass, nsSharedApplicationSelector);
- IntPtr nsMethodSelector = sel_registerName("hide:");
- IntPtr nsDelegateSelector = sel_registerName("delegate");
- IntPtr nsDelegate = objc_msgSend(nsApp, nsDelegateSelector);
- objc_msgSendWithArg(nsApp, nsMethodSelector, nsDelegate);
- }
-
- public void HideOtherApplications()
- {
- IntPtr nsApplicationClass = objc_getClass("NSApplication");
- IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication");
- IntPtr nsApp = objc_msgSend(nsApplicationClass, nsSharedApplicationSelector);
- IntPtr nsMethodSelector = sel_registerName("hideOtherApplications:");
- IntPtr nsDelegateSelector = sel_registerName("delegate");
- IntPtr nsDelegate = objc_msgSend(nsApp, nsDelegateSelector);
- objc_msgSendWithArg(nsApp, nsMethodSelector, nsDelegate);
- }
-
- public void ShowAllApplications()
- {
- IntPtr nsApplicationClass = objc_getClass("NSApplication");
- IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication");
- IntPtr nsApp = objc_msgSend(nsApplicationClass, nsSharedApplicationSelector);
- IntPtr nsMethodSelector = sel_registerName("unhideAllApplications:");
- IntPtr nsDelegateSelector = sel_registerName("delegate");
- IntPtr nsDelegate = objc_msgSend(nsApp, nsDelegateSelector);
- objc_msgSendWithArg(nsApp, nsMethodSelector, nsDelegate);
- }
-
- public string GetDataDir()
+ public OS.Directories GetOrCreateDirectories()
{
- return Path.Combine(
+ var dirs = new OS.Directories();
+ var appDataDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"SourceGit");
+
+ if (!Directory.Exists(appDataDir))
+ Directory.CreateDirectory(appDataDir);
+
+ dirs.ConfigDir = appDataDir;
+ dirs.CacheDir = appDataDir;
+ return dirs;
}
public string FindGitExecutable()
@@ -156,5 +129,218 @@ public void OpenWithDefaultEditor(string file)
{
Process.Start("open", file.Quoted());
}
+
+ public bool SupportSetSid()
+ {
+ return File.Exists(_setsidExecutable);
+ }
+
+ public string GetSetSidExecutable()
+ {
+ return _setsidExecutable;
+ }
+
+ public void TerminateProcess(Process proc)
+ {
+ if (!SupportSetSid())
+ {
+ proc.Kill(true);
+ return;
+ }
+
+ if (kill(-proc.Id, 15) != 0)
+ {
+ // If the process already exited, we can just ignore the error.
+ if (Marshal.GetLastPInvokeError() == 3 /* ESRCH */)
+ return;
+
+ // Actually, this will not be called since the process is
+ // spawned by us (EPERM will not happen), and SIGTERM (15)
+ // is a valid signal (EINVAL will not happen).
+ // See https://www.man7.org/linux/man-pages/man2/kill.2.html
+ try
+ {
+ proc.Kill(true);
+ }
+ catch
+ {
+ // Ignore any errors when trying to kill the process
+ }
+ }
+ }
+
+ private string _setsidExecutable = null;
+ }
+
+ [SupportedOSPlatform("macOS")]
+ public static class MacOSUtilities
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct CGPoint
+ {
+ public double X;
+ public double Y;
+
+ public CGPoint(double x, double y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(X, Y);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object obj)
+ {
+ if (obj is not CGPoint point)
+ return false;
+
+ return X == point.X && Y == point.Y;
+ }
+
+ public static bool operator ==(CGPoint left, CGPoint right) => left.Equals(right);
+ public static bool operator !=(CGPoint left, CGPoint right) => !left.Equals(right);
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct CGSize
+ {
+ public double Width;
+ public double Height;
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Width, Height);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object obj)
+ {
+ if (obj is not CGSize size)
+ return false;
+
+ return Width == size.Width && Height == size.Height;
+ }
+
+ public static bool operator ==(CGSize left, CGSize right) => left.Equals(right);
+ public static bool operator !=(CGSize left, CGSize right) => !left.Equals(right);
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct CGRect
+ {
+ public CGPoint Origin;
+ public CGSize Size;
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Origin, Size);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object obj)
+ {
+ if (obj is not CGRect rect)
+ return false;
+
+ return Origin == rect.Origin && Size == rect.Size;
+ }
+
+ public static bool operator ==(CGRect left, CGRect right) => left.Equals(right);
+ public static bool operator !=(CGRect left, CGRect right) => !left.Equals(right);
+ }
+
+ [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_getClass")]
+ public static extern IntPtr objc_getClass(string name);
+
+ [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "sel_registerName")]
+ public static extern IntPtr sel_registerName(string name);
+
+ [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_msgSend")]
+ public static extern IntPtr objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector);
+
+ [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_msgSend")]
+ public static extern IntPtr objc_msgSend_IntPtr_Int(IntPtr receiver, IntPtr selector, int arg);
+
+ [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_msgSend")]
+ public static extern void objc_msgSend_Void_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg);
+
+ [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_msgSend")]
+ public static extern void objc_msgSend_Void_Point(IntPtr receiver, IntPtr selector, CGPoint arg);
+
+ [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_msgSend_stret")]
+ public static extern void objc_msgSendStrect_Rect(out CGRect rect, IntPtr receiver, IntPtr selector);
+
+ [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_msgSend")]
+ public static extern CGRect objc_msgSend_Rect(IntPtr receiver, IntPtr selector);
+
+ private static readonly IntPtr s_selStandardWindowButton = sel_registerName("standardWindowButton:");
+ private static readonly IntPtr s_selFrame = sel_registerName("frame");
+ private static readonly IntPtr s_selSetFrameOrigin = sel_registerName("setFrameOrigin:");
+
+ public static void AdjustTrafficLightsForThickTitleBar(Window window)
+ {
+ IntPtr nsWindow = window.TryGetPlatformHandle()?.Handle ?? IntPtr.Zero;
+ if (nsWindow == IntPtr.Zero)
+ return;
+
+ IntPtr nsCloseBtn = objc_msgSend_IntPtr_Int(nsWindow, s_selStandardWindowButton, 0);
+ IntPtr nsMinBtn = objc_msgSend_IntPtr_Int(nsWindow, s_selStandardWindowButton, 1);
+ IntPtr nsZoomBtn = objc_msgSend_IntPtr_Int(nsWindow, s_selStandardWindowButton, 2);
+ if (nsCloseBtn == IntPtr.Zero || nsMinBtn == IntPtr.Zero || nsZoomBtn == IntPtr.Zero)
+ return;
+
+ // For Intel CPU, we need to use `objc_msgSend_stret` to get the `CGRect` struct, while for Apple Silicon, we can directly use `objc_msgSend`.
+ if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
+ {
+ objc_msgSendStrect_Rect(out var frame, nsCloseBtn, s_selFrame);
+ if (Math.Abs(frame.Origin.X - 14) <= 0.5 || Math.Abs(frame.Origin.Y - 2) <= 0.5)
+ return;
+ }
+ else
+ {
+ CGRect frame = objc_msgSend_Rect(nsCloseBtn, s_selFrame);
+ if (Math.Abs(frame.Origin.X - 14) <= 0.5 || Math.Abs(frame.Origin.Y - 2) <= 0.5)
+ return;
+ }
+
+ var yOffset = OperatingSystem.IsMacOSVersionAtLeast(26) ? 6 : 2;
+ objc_msgSend_Void_Point(nsCloseBtn, s_selSetFrameOrigin, new(14, yOffset));
+ objc_msgSend_Void_Point(nsMinBtn, s_selSetFrameOrigin, new(14 + 20, yOffset));
+ objc_msgSend_Void_Point(nsZoomBtn, s_selSetFrameOrigin, new(14 + 40, yOffset));
+ }
+
+ public static void HideSelf()
+ {
+ IntPtr nsApplicationClass = objc_getClass("NSApplication");
+ IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication");
+ IntPtr nsApp = objc_msgSend_IntPtr(nsApplicationClass, nsSharedApplicationSelector);
+ IntPtr nsMethodSelector = sel_registerName("hide:");
+ IntPtr nsDelegateSelector = sel_registerName("delegate");
+ IntPtr nsDelegate = objc_msgSend_IntPtr(nsApp, nsDelegateSelector);
+ objc_msgSend_Void_IntPtr(nsApp, nsMethodSelector, nsDelegate);
+ }
+
+ public static void HideOtherApplications()
+ {
+ IntPtr nsApplicationClass = objc_getClass("NSApplication");
+ IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication");
+ IntPtr nsApp = objc_msgSend_IntPtr(nsApplicationClass, nsSharedApplicationSelector);
+ IntPtr nsMethodSelector = sel_registerName("hideOtherApplications:");
+ IntPtr nsDelegateSelector = sel_registerName("delegate");
+ IntPtr nsDelegate = objc_msgSend_IntPtr(nsApp, nsDelegateSelector);
+ objc_msgSend_Void_IntPtr(nsApp, nsMethodSelector, nsDelegate);
+ }
+
+ public static void ShowAllApplications()
+ {
+ IntPtr nsApplicationClass = objc_getClass("NSApplication");
+ IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication");
+ IntPtr nsApp = objc_msgSend_IntPtr(nsApplicationClass, nsSharedApplicationSelector);
+ IntPtr nsMethodSelector = sel_registerName("unhideAllApplications:");
+ IntPtr nsDelegateSelector = sel_registerName("delegate");
+ IntPtr nsDelegate = objc_msgSend_IntPtr(nsApp, nsDelegateSelector);
+ objc_msgSend_Void_IntPtr(nsApp, nsMethodSelector, nsDelegate);
+ }
}
}
diff --git a/src/Native/OS.cs b/src/Native/OS.cs
index c445e60c43..741fcf8440 100644
--- a/src/Native/OS.cs
+++ b/src/Native/OS.cs
@@ -6,6 +6,7 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
+using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
@@ -13,16 +14,18 @@ namespace SourceGit.Native
{
public static partial class OS
{
+ public class Directories
+ {
+ public string ConfigDir { get; set; } = string.Empty;
+ public string CacheDir { get; set; } = string.Empty;
+ }
+
public interface IBackend
{
void SetupApp(AppBuilder builder);
void SetupWindow(Window window);
- void HideSelf();
- void HideOtherApplications();
- void ShowAllApplications();
-
- string GetDataDir();
+ Directories GetOrCreateDirectories();
string FindGitExecutable();
string FindTerminal(Models.ShellOrTerminal shell);
List FindExternalTools();
@@ -31,13 +34,17 @@ public interface IBackend
void OpenInFileManager(string path);
void OpenBrowser(string url);
void OpenWithDefaultEditor(string file);
+
+ bool SupportSetSid();
+ string GetSetSidExecutable();
+ void TerminateProcess(Process proc);
}
- public static string DataDir
+ public static Directories BasicDirectories
{
get;
private set;
- } = string.Empty;
+ } = new();
public static string GitExecutable
{
@@ -64,6 +71,12 @@ public static Version GitVersion
private set;
} = new Version(0, 0, 0);
+ public static Models.GitFlowVersion GitFlowVersion
+ {
+ get;
+ private set;
+ } = Models.GitFlowVersion.None;
+
public static string CredentialHelper
{
get;
@@ -130,11 +143,9 @@ static OS()
throw new PlatformNotSupportedException();
}
- public static void SetupDataDir()
+ public static void SetupBasicDirectories()
{
- DataDir = _backend.GetDataDir();
- if (!Directory.Exists(DataDir))
- Directory.CreateDirectory(DataDir);
+ BasicDirectories = _backend.GetOrCreateDirectories();
}
public static void SetupApp(AppBuilder builder)
@@ -152,27 +163,12 @@ public static void SetupForWindow(Window window)
_backend.SetupWindow(window);
}
- public static void HideSelf()
- {
- _backend.HideSelf();
- }
-
- public static void HideOtherApplications()
- {
- _backend.HideOtherApplications();
- }
-
- public static void ShowAllApplications()
- {
- _backend.ShowAllApplications();
- }
-
public static void LogException(Exception ex)
{
if (ex == null)
return;
- var crashDir = Path.Combine(DataDir, "crashes");
+ var crashDir = Path.Combine(BasicDirectories.CacheDir, "crashes");
if (!Directory.Exists(crashDir))
Directory.CreateDirectory(crashDir);
@@ -254,9 +250,26 @@ public static void OpenInFileManager(string path)
public static void OpenBrowser(string url)
{
+ if (!IsSafeBrowserTarget(url))
+ {
+ Models.Notification.Send(null, $"Blocked unsafe URL: {url}", true);
+ return;
+ }
+
_backend.OpenBrowser(url);
}
+ private static bool IsSafeBrowserTarget(string url)
+ {
+ return Uri.IsWellFormedUriString(url, UriKind.Absolute) &&
+ Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
+ (
+ uri.Scheme == Uri.UriSchemeHttp ||
+ uri.Scheme == Uri.UriSchemeHttps ||
+ uri.Scheme == Uri.UriSchemeFtp
+ );
+ }
+
public static void OpenTerminal(string workdir)
{
if (string.IsNullOrEmpty(ShellOrTerminal))
@@ -292,12 +305,28 @@ public static string GetRelativePathToHome(string path)
return path;
}
+ public static bool SupportSetSid()
+ {
+ return _backend.SupportSetSid();
+ }
+
+ public static string GetSetSidExecutable()
+ {
+ return _backend.GetSetSidExecutable();
+ }
+
+ public static void TerminateProcess(Process proc)
+ {
+ _backend.TerminateProcess(proc);
+ }
+
private static void UpdateGitVersion()
{
if (string.IsNullOrEmpty(_gitExecutable) || !File.Exists(_gitExecutable))
{
GitVersionString = string.Empty;
GitVersion = new Version(0, 0, 0);
+ GitFlowVersion = Models.GitFlowVersion.None;
return;
}
@@ -329,6 +358,42 @@ private static void UpdateGitVersion()
GitVersion = new Version(major, minor, build);
GitVersionString = GitVersionString.Substring(11).Trim();
}
+
+ // Update git flow version in background to avoid blocking the UI
+ Task.Run(UpdateGitFlowVersion);
+ }
+ }
+ catch
+ {
+ // Ignore errors
+ }
+ }
+
+ private static void UpdateGitFlowVersion()
+ {
+ var start = new ProcessStartInfo();
+ start.FileName = _gitExecutable;
+ start.Arguments = "flow version";
+ start.UseShellExecute = false;
+ start.CreateNoWindow = true;
+ start.RedirectStandardOutput = true;
+ start.RedirectStandardError = true;
+ start.StandardOutputEncoding = Encoding.UTF8;
+ start.StandardErrorEncoding = Encoding.UTF8;
+
+ GitFlowVersion = Models.GitFlowVersion.None;
+
+ try
+ {
+ using var proc = Process.Start(start)!;
+ var rs = proc.StandardOutput.ReadToEnd();
+ proc.WaitForExit();
+ if (proc.ExitCode == 0 && !string.IsNullOrWhiteSpace(rs))
+ {
+ if (rs.Contains("git-flow-next", StringComparison.Ordinal))
+ GitFlowVersion = Models.GitFlowVersion.Next;
+ else
+ GitFlowVersion = Models.GitFlowVersion.Legacy;
}
}
catch
diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs
index 35b8aba662..fde6cb25a7 100644
--- a/src/Native/Windows.cs
+++ b/src/Native/Windows.cs
@@ -17,18 +17,6 @@ namespace SourceGit.Native
[SupportedOSPlatform("windows")]
internal class Windows : OS.IBackend
{
- [StructLayout(LayoutKind.Sequential)]
- internal struct MARGINS
- {
- public int cxLeftWidth;
- public int cxRightWidth;
- public int cyTopHeight;
- public int cyBottomHeight;
- }
-
- [DllImport("dwmapi.dll")]
- private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
-
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = false)]
private static extern bool PathFindOnPath([In, Out] StringBuilder pszFile, [In] string[] ppszOtherDirs);
@@ -41,14 +29,21 @@ internal struct MARGINS
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags);
+ [DllImport("kernel32.dll")]
+ private static extern bool SetConsoleCtrlHandler(IntPtr handler, bool add);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool AttachConsole(int dwProcessId);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, int dwProcessGroupId);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool FreeConsole();
+
public void SetupApp(AppBuilder builder)
{
- // Fix drop shadow issue on Windows 10
- if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
- {
- Window.WindowStateProperty.Changed.AddClassHandler((w, _) => FixWindowFrameOnWin10(w));
- Control.LoadedEvent.AddClassHandler((w, _) => FixWindowFrameOnWin10(w));
- }
+ // Do nothing for now.
}
public void SetupWindow(Window window)
@@ -56,33 +51,33 @@ public void SetupWindow(Window window)
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
window.ExtendClientAreaToDecorationsHint = true;
window.BorderThickness = new Thickness(1);
+ window.Padding = new Thickness(0);
}
- public void HideSelf()
- {
- // Do Nothing. Never used.
- }
-
- public void HideOtherApplications()
- {
- // Do Nothing. Never used.
- }
-
- public void ShowAllApplications()
+ public OS.Directories GetOrCreateDirectories()
{
- // Do Nothing. Never used.
- }
-
- public string GetDataDir()
- {
- var execFile = Process.GetCurrentProcess().MainModule!.FileName;
+ var dirs = new OS.Directories();
+ var execFile = Environment.ProcessPath;
var portableDir = Path.Combine(Path.GetDirectoryName(execFile)!, "data");
if (Directory.Exists(portableDir))
- return portableDir;
+ {
+ dirs.ConfigDir = portableDir;
+ dirs.CacheDir = portableDir;
+ }
+ else
+ {
+ var appDataDir = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "SourceGit");
- return Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- "SourceGit");
+ if (!Directory.Exists(appDataDir))
+ Directory.CreateDirectory(appDataDir);
+
+ dirs.ConfigDir = appDataDir;
+ dirs.CacheDir = appDataDir;
+ }
+
+ return dirs;
}
public string FindGitExecutable()
@@ -158,17 +153,18 @@ public string FindTerminal(Models.ShellOrTerminal shell)
finder.VSCode(FindVSCode);
finder.VSCodeInsiders(FindVSCodeInsiders);
finder.VSCodium(FindVSCodium);
+ FindVisualStudio(finder);
finder.Cursor(() => Path.Combine(localAppDataDir, @"Programs\Cursor\Cursor.exe"));
finder.FindJetBrainsFromToolbox(() => Path.Combine(localAppDataDir, @"JetBrains\Toolbox"));
finder.SublimeText(FindSublimeText);
finder.Zed(FindZed);
- FindVisualStudio(finder);
return finder.Tools;
}
public void OpenBrowser(string url)
{
- var info = new ProcessStartInfo("cmd", $"""/c start "" {url.Quoted()}""");
+ var info = new ProcessStartInfo(url);
+ info.UseShellExecute = true;
info.CreateNoWindow = true;
Process.Start(info);
}
@@ -226,22 +222,42 @@ public void OpenWithDefaultEditor(string file)
Process.Start(start);
}
- #region HELPER_METHODS
- private void FixWindowFrameOnWin10(Window w)
+ public bool SupportSetSid()
{
- // Schedule the DWM frame extension to run in the next render frame
- // to ensure proper timing with the window initialization sequence
- Dispatcher.UIThread.Post(() =>
+ return false;
+ }
+
+ public string GetSetSidExecutable()
+ {
+ return null;
+ }
+
+ public void TerminateProcess(Process proc)
+ {
+ var pid = proc.Id;
+ if (AttachConsole(pid))
{
- var platformHandle = w.TryGetPlatformHandle();
- if (platformHandle == null)
- return;
+ SetConsoleCtrlHandler(IntPtr.Zero, true);
+ try
+ {
+ if (!GenerateConsoleCtrlEvent(0, 0))
+ proc.Kill(true);
- var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
- DwmExtendFrameIntoClientArea(platformHandle.Handle, ref margins);
- }, DispatcherPriority.Render);
+ proc.WaitForExit(2000);
+ }
+ finally
+ {
+ FreeConsole();
+ SetConsoleCtrlHandler(IntPtr.Zero, false);
+ }
+ }
+ else
+ {
+ proc.Kill(true);
+ }
}
+ #region HELPER_METHODS
private List GenerateVSProjectLaunchOptions(string path)
{
var root = new DirectoryInfo(path);
@@ -386,7 +402,7 @@ private void FindVisualStudio(Models.ExternalToolsFinder finder)
{
var exec = instance.ProductPath;
var icon = instance.IsPrerelease ? "vs-preview" : "vs";
- finder.TryAdd(instance.DisplayName, icon, () => exec, GenerateVSProjectLaunchOptions);
+ finder.TryAdd(instance.DisplayName, icon, () => exec, GenerateVSProjectLaunchOptions, false);
}
}
}
@@ -415,4 +431,47 @@ private string FindZed()
}
#endregion
}
+
+ [SupportedOSPlatform("windows")]
+ public static class Win64Utilities
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct MARGINS
+ {
+ public int cxLeftWidth;
+ public int cxRightWidth;
+ public int cyTopHeight;
+ public int cyBottomHeight;
+ }
+
+ [DllImport("dwmapi.dll")]
+ private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
+
+ public static void FixWindowFrame(Window w)
+ {
+ if (w.WindowState == WindowState.Maximized)
+ {
+ w.BorderThickness = new Thickness(0);
+ w.Padding = new Thickness(8, 6, 8, 8);
+ }
+ else
+ {
+ w.BorderThickness = new Thickness(1);
+ w.Padding = new Thickness(0);
+ }
+
+ if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
+ return;
+
+ Dispatcher.UIThread.Post(() =>
+ {
+ var platformHandle = w.TryGetPlatformHandle();
+ if (platformHandle == null)
+ return;
+
+ var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
+ DwmExtendFrameIntoClientArea(platformHandle.Handle, ref margins);
+ }, DispatcherPriority.Render);
+ }
+ }
}
diff --git a/src/Resources/Fonts.axaml b/src/Resources/Fonts.axaml
new file mode 100644
index 0000000000..832026bead
--- /dev/null
+++ b/src/Resources/Fonts.axaml
@@ -0,0 +1,5 @@
+
+ fonts:Inter#Inter
+ fonts:SourceGit#JetBrains Mono NL
+
diff --git a/src/Resources/Fonts/JetBrainsMono-Bold.ttf b/src/Resources/Fonts/JetBrainsMono-Bold.ttf
deleted file mode 100644
index 8c93043de6..0000000000
Binary files a/src/Resources/Fonts/JetBrainsMono-Bold.ttf and /dev/null differ
diff --git a/src/Resources/Fonts/JetBrainsMono-Italic.ttf b/src/Resources/Fonts/JetBrainsMono-Italic.ttf
deleted file mode 100644
index ccc9d6a5bc..0000000000
Binary files a/src/Resources/Fonts/JetBrainsMono-Italic.ttf and /dev/null differ
diff --git a/src/Resources/Fonts/JetBrainsMono-Regular.ttf b/src/Resources/Fonts/JetBrainsMono-Regular.ttf
deleted file mode 100644
index dff66cc507..0000000000
Binary files a/src/Resources/Fonts/JetBrainsMono-Regular.ttf and /dev/null differ
diff --git a/src/Resources/Fonts/JetBrainsMonoNL-Bold.ttf b/src/Resources/Fonts/JetBrainsMonoNL-Bold.ttf
new file mode 100644
index 0000000000..f78f84fb43
Binary files /dev/null and b/src/Resources/Fonts/JetBrainsMonoNL-Bold.ttf differ
diff --git a/src/Resources/Fonts/JetBrainsMonoNL-Italic.ttf b/src/Resources/Fonts/JetBrainsMonoNL-Italic.ttf
new file mode 100644
index 0000000000..4e9c3802dd
Binary files /dev/null and b/src/Resources/Fonts/JetBrainsMonoNL-Italic.ttf differ
diff --git a/src/Resources/Fonts/JetBrainsMonoNL-Regular.ttf b/src/Resources/Fonts/JetBrainsMonoNL-Regular.ttf
new file mode 100644
index 0000000000..70d2ec9e28
Binary files /dev/null and b/src/Resources/Fonts/JetBrainsMonoNL-Regular.ttf differ
diff --git a/src/Resources/Grammars/erlang.json b/src/Resources/Grammars/erlang.json
new file mode 100644
index 0000000000..216086ce5f
--- /dev/null
+++ b/src/Resources/Grammars/erlang.json
@@ -0,0 +1,1753 @@
+{
+ "comment": "The recognition of function definitions and compiler directives (such as module, record and macro definitions) requires that each of the aforementioned constructs must be the first string inside a line (except for whitespace). Also, the function/module/record/macro names must be given unquoted. -- desp",
+ "fileTypes": ["erl", "escript", "hrl", "xrl", "yrl"],
+ "keyEquivalent": "^~E",
+ "name": "erlang",
+ "patterns": [
+ {
+ "include": "#module-directive"
+ },
+ {
+ "include": "#import-export-directive"
+ },
+ {
+ "include": "#behaviour-directive"
+ },
+ {
+ "include": "#record-directive"
+ },
+ {
+ "include": "#define-directive"
+ },
+ {
+ "include": "#macro-directive"
+ },
+ {
+ "include": "#directive"
+ },
+ {
+ "include": "#function"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ],
+ "repository": {
+ "atom": {
+ "patterns": [
+ {
+ "begin": "(')",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.definition.symbol.begin.erlang"
+ }
+ },
+ "end": "(')",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.symbol.end.erlang"
+ }
+ },
+ "name": "constant.other.symbol.quoted.single.erlang",
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.definition.escape.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.escape.erlang"
+ }
+ },
+ "match": "(\\\\)([bdefnrstv\\\\'\"]|(\\^)[@-_a-z]|[0-7]{1,3}|x[\\da-fA-F]{2})",
+ "name": "constant.other.symbol.escape.erlang"
+ },
+ {
+ "match": "\\\\\\^?.?",
+ "name": "invalid.illegal.atom.erlang"
+ }
+ ]
+ },
+ {
+ "match": "[a-z][a-zA-Z\\d@_]*+",
+ "name": "constant.other.symbol.unquoted.erlang"
+ }
+ ]
+ },
+ "behaviour-directive": {
+ "captures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.behaviour.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ },
+ "4": {
+ "name": "entity.name.type.class.behaviour.definition.erlang"
+ },
+ "5": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "6": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "match": "^\\s*+(-)\\s*+(behaviour)\\s*+(\\()\\s*+([a-z][a-zA-Z\\d@_]*+)\\s*+(\\))\\s*+(\\.)",
+ "name": "meta.directive.behaviour.erlang"
+ },
+ "binary": {
+ "begin": "(<<)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.definition.binary.begin.erlang"
+ }
+ },
+ "end": "(>>)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.binary.end.erlang"
+ }
+ },
+ "name": "meta.structure.binary.erlang",
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.binary.erlang"
+ },
+ "2": {
+ "name": "punctuation.separator.value-size.erlang"
+ }
+ },
+ "match": "(,)|(:)"
+ },
+ {
+ "include": "#internal-type-specifiers"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ "character": {
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.definition.character.erlang"
+ },
+ "2": {
+ "name": "constant.character.escape.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.escape.erlang"
+ },
+ "5": {
+ "name": "punctuation.definition.escape.erlang"
+ }
+ },
+ "match": "(\\$)((\\\\)([bdefnrstv\\\\'\"]|(\\^)[@-_a-z]|[0-7]{1,3}|x[\\da-fA-F]{2}))",
+ "name": "constant.character.erlang"
+ },
+ {
+ "match": "\\$\\\\\\^?.?",
+ "name": "invalid.illegal.character.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.definition.character.erlang"
+ }
+ },
+ "match": "(\\$)[ \\S]",
+ "name": "constant.character.erlang"
+ },
+ {
+ "match": "\\$.?",
+ "name": "invalid.illegal.character.erlang"
+ }
+ ]
+ },
+ "comment": {
+ "begin": "(^[ \\t]+)?(?=%)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.whitespace.comment.leading.erlang"
+ }
+ },
+ "end": "(?!\\G)",
+ "patterns": [
+ {
+ "begin": "%",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.comment.erlang"
+ }
+ },
+ "end": "\\n",
+ "name": "comment.line.percentage.erlang"
+ }
+ ]
+ },
+ "define-directive": {
+ "patterns": [
+ {
+ "begin": "^\\s*+(-)\\s*+(define)\\s*+(\\()\\s*+([a-zA-Z\\d@_]++)\\s*+",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.define.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ },
+ "4": {
+ "name": "entity.name.function.macro.definition.erlang"
+ }
+ },
+ "end": "(\\))\\s*+(\\.)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "2": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "name": "meta.directive.define.erlang",
+ "patterns": [
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "(?=^\\s*+-\\s*+define\\s*+\\(\\s*+[a-zA-Z\\d@_]++\\s*+\\()",
+ "end": "(\\))\\s*+(\\.)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "2": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "name": "meta.directive.define.erlang",
+ "patterns": [
+ {
+ "begin": "^\\s*+(-)\\s*+(define)\\s*+(\\()\\s*+([a-zA-Z\\d@_]++)\\s*+(\\()",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.define.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ },
+ "4": {
+ "name": "entity.name.function.macro.definition.erlang"
+ },
+ "5": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ }
+ },
+ "end": "(\\))\\s*(,)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "2": {
+ "name": "punctuation.separator.parameters.erlang"
+ }
+ },
+ "patterns": [
+ {
+ "match": ",",
+ "name": "punctuation.separator.parameters.erlang"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "match": "\\|\\||\\||:|;|,|\\.|->",
+ "name": "punctuation.separator.define.erlang"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ }
+ ]
+ },
+ "directive": {
+ "patterns": [
+ {
+ "begin": "^\\s*+(-)\\s*+([a-z][a-zA-Z\\d@_]*+)\\s*+(\\(?)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ }
+ },
+ "end": "(\\)?)\\s*+(\\.)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "2": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "name": "meta.directive.erlang",
+ "patterns": [
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.erlang"
+ },
+ "3": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "match": "^\\s*+(-)\\s*+([a-z][a-zA-Z\\d@_]*+)\\s*+(\\.)",
+ "name": "meta.directive.erlang"
+ }
+ ]
+ },
+ "everything-else": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#record-usage"
+ },
+ {
+ "include": "#macro-usage"
+ },
+ {
+ "include": "#expression"
+ },
+ {
+ "include": "#keyword"
+ },
+ {
+ "include": "#textual-operator"
+ },
+ {
+ "include": "#language-constant"
+ },
+ {
+ "include": "#function-call"
+ },
+ {
+ "include": "#tuple"
+ },
+ {
+ "include": "#list"
+ },
+ {
+ "include": "#binary"
+ },
+ {
+ "include": "#parenthesized-expression"
+ },
+ {
+ "include": "#character"
+ },
+ {
+ "include": "#number"
+ },
+ {
+ "include": "#atom"
+ },
+ {
+ "include": "#string"
+ },
+ {
+ "include": "#symbolic-operator"
+ },
+ {
+ "include": "#variable"
+ }
+ ]
+ },
+ "expression": {
+ "patterns": [
+ {
+ "begin": "\\b(if)\\b",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.if.erlang"
+ }
+ },
+ "end": "\\b(end)\\b",
+ "endCaptures": {
+ "1": {
+ "name": "keyword.control.end.erlang"
+ }
+ },
+ "name": "meta.expression.if.erlang",
+ "patterns": [
+ {
+ "include": "#internal-expression-punctuation"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "\\b(case)\\b",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.case.erlang"
+ }
+ },
+ "end": "\\b(end)\\b",
+ "endCaptures": {
+ "1": {
+ "name": "keyword.control.end.erlang"
+ }
+ },
+ "name": "meta.expression.case.erlang",
+ "patterns": [
+ {
+ "include": "#internal-expression-punctuation"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "\\b(receive)\\b",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.receive.erlang"
+ }
+ },
+ "end": "\\b(end)\\b",
+ "endCaptures": {
+ "1": {
+ "name": "keyword.control.end.erlang"
+ }
+ },
+ "name": "meta.expression.receive.erlang",
+ "patterns": [
+ {
+ "include": "#internal-expression-punctuation"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "keyword.control.fun.erlang"
+ },
+ "4": {
+ "name": "entity.name.type.class.module.erlang"
+ },
+ "5": {
+ "name": "variable.other.erlang"
+ },
+ "6": {
+ "name": "punctuation.separator.module-function.erlang"
+ },
+ "8": {
+ "name": "entity.name.function.erlang"
+ },
+ "9": {
+ "name": "variable.other.erlang"
+ },
+ "10": {
+ "name": "punctuation.separator.function-arity.erlang"
+ }
+ },
+ "comment": "Implicit function expression with optional module qualifier when both module and function can be atom or variable",
+ "match": "\\b(fun)\\s+((([a-z][a-zA-Z\\d@_]*+)|(_[a-zA-Z\\d@_]++|[A-Z][a-zA-Z\\d@_]*+))\\s*+(:)\\s*+)?(([a-z][a-zA-Z\\d@_]*+|'[^']*+')|(_[a-zA-Z\\d@_]++|[A-Z][a-zA-Z\\d@_]*+))\\s*(/)",
+ "name": "meta.expression.fun.implicit.erlang"
+ },
+ {
+ "begin": "\\b(fun)\\s+(([a-z][a-zA-Z\\d@_]*+)|(_[a-zA-Z\\d@_]++|[A-Z][a-zA-Z\\d@_]*+))\\s*+(:)",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.fun.erlang"
+ },
+ "3": {
+ "name": "entity.name.type.class.module.erlang"
+ },
+ "4": {
+ "name": "variable.other.erlang"
+ },
+ "5": {
+ "name": "punctuation.separator.module-function.erlang"
+ }
+ },
+ "comment": "Implicit function expression with module qualifier when module can be atom or variable and function can by anything",
+ "end": "(/)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.separator.function-arity.erlang"
+ }
+ },
+ "name": "meta.expression.fun.implicit.erlang",
+ "patterns": [
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "\\b(fun)\\s+(?!\\()",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.fun.erlang"
+ }
+ },
+ "comment": "Implicit function expression when both module and function can by anything",
+ "end": "(/)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.separator.function-arity.erlang"
+ }
+ },
+ "name": "meta.expression.fun.implicit.erlang",
+ "patterns": [
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "\\b(fun)\\s*+(\\()(?=(\\s*+\\())",
+ "beginCaptures": {
+ "1": {
+ "name": "entity.name.function.erlang"
+ },
+ "2": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ }
+ },
+ "comment": "Function type in type specification",
+ "end": "(\\))",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "\\b(fun)\\b",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.fun.erlang"
+ }
+ },
+ "comment": "Explicit function expression",
+ "end": "\\b(end)\\b",
+ "endCaptures": {
+ "1": {
+ "name": "keyword.control.end.erlang"
+ }
+ },
+ "name": "meta.expression.fun.erlang",
+ "patterns": [
+ {
+ "begin": "(?=\\()",
+ "end": "(;)|(?=\\bend\\b)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.separator.clauses.erlang"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#internal-function-parts"
+ }
+ ]
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "\\b(try)\\b",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.try.erlang"
+ }
+ },
+ "end": "\\b(end)\\b",
+ "endCaptures": {
+ "1": {
+ "name": "keyword.control.end.erlang"
+ }
+ },
+ "name": "meta.expression.try.erlang",
+ "patterns": [
+ {
+ "include": "#internal-expression-punctuation"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "\\b(begin)\\b",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.begin.erlang"
+ }
+ },
+ "end": "\\b(end)\\b",
+ "endCaptures": {
+ "1": {
+ "name": "keyword.control.end.erlang"
+ }
+ },
+ "name": "meta.expression.begin.erlang",
+ "patterns": [
+ {
+ "include": "#internal-expression-punctuation"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "\\b(maybe)\\b",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.control.maybe.erlang"
+ }
+ },
+ "end": "\\b(end)\\b",
+ "endCaptures": {
+ "1": {
+ "name": "keyword.control.end.erlang"
+ }
+ },
+ "name": "meta.expression.maybe.erlang",
+ "patterns": [
+ {
+ "include": "#internal-expression-punctuation"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ }
+ ]
+ },
+ "function": {
+ "begin": "^\\s*+([a-z][a-zA-Z\\d@_]*+|'[^']*+')\\s*+(?=\\()",
+ "beginCaptures": {
+ "1": {
+ "name": "entity.name.function.definition.erlang"
+ }
+ },
+ "end": "(\\.)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.terminator.function.erlang"
+ }
+ },
+ "name": "meta.function.erlang",
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "entity.name.function.erlang"
+ }
+ },
+ "match": "^\\s*+([a-z][a-zA-Z\\d@_]*+|'[^']*+')\\s*+(?=\\()"
+ },
+ {
+ "begin": "(?=\\()",
+ "end": "(;)|(?=\\.)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.separator.clauses.erlang"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#parenthesized-expression"
+ },
+ {
+ "include": "#internal-function-parts"
+ }
+ ]
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ "function-call": {
+ "begin": "(?=([a-z][a-zA-Z\\d@_]*+|'[^']*+'|_[a-zA-Z\\d@_]++|[A-Z][a-zA-Z\\d@_]*+)\\s*+(\\(|:\\s*+([a-z][a-zA-Z\\d@_]*+|'[^']*+'|_[a-zA-Z\\d@_]++|[A-Z][a-zA-Z\\d@_]*+)\\s*+\\())",
+ "end": "(\\))",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ }
+ },
+ "name": "meta.function-call.erlang",
+ "patterns": [
+ {
+ "begin": "((erlang)\\s*+(:)\\s*+)?(is_atom|is_binary|is_constant|is_float|is_function|is_integer|is_list|is_number|is_pid|is_port|is_reference|is_tuple|is_record|abs|element|hd|length|node|round|self|size|tl|trunc)\\s*+(\\()",
+ "beginCaptures": {
+ "2": {
+ "name": "entity.name.type.class.module.erlang"
+ },
+ "3": {
+ "name": "punctuation.separator.module-function.erlang"
+ },
+ "4": {
+ "name": "entity.name.function.guard.erlang"
+ },
+ "5": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ }
+ },
+ "end": "(?=\\))",
+ "patterns": [
+ {
+ "match": ",",
+ "name": "punctuation.separator.parameters.erlang"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "begin": "((([a-z][a-zA-Z\\d@_]*+|'[^']*+')|(_[a-zA-Z\\d@_]++|[A-Z][a-zA-Z\\d@_]*+))\\s*+(:)\\s*+)?(([a-z][a-zA-Z\\d@_]*+|'[^']*+')|(_[a-zA-Z\\d@_]++|[A-Z][a-zA-Z\\d@_]*+))\\s*+(\\()",
+ "beginCaptures": {
+ "3": {
+ "name": "entity.name.type.class.module.erlang"
+ },
+ "4": {
+ "name": "variable.other.erlang"
+ },
+ "5": {
+ "name": "punctuation.separator.module-function.erlang"
+ },
+ "7": {
+ "name": "entity.name.function.erlang"
+ },
+ "8": {
+ "name": "variable.other.erlang"
+ },
+ "9": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ }
+ },
+ "end": "(?=\\))",
+ "patterns": [
+ {
+ "match": ",",
+ "name": "punctuation.separator.parameters.erlang"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ }
+ ]
+ },
+ "import-export-directive": {
+ "patterns": [
+ {
+ "begin": "^\\s*+(-)\\s*+(import)\\s*+(\\()\\s*+([a-z][a-zA-Z\\d@_]*+|'[^']*+')\\s*+(,)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.import.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ },
+ "4": {
+ "name": "entity.name.type.class.module.erlang"
+ },
+ "5": {
+ "name": "punctuation.separator.parameters.erlang"
+ }
+ },
+ "end": "(\\))\\s*+(\\.)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "2": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "name": "meta.directive.import.erlang",
+ "patterns": [
+ {
+ "include": "#internal-function-list"
+ }
+ ]
+ },
+ {
+ "begin": "^\\s*+(-)\\s*+(export)\\s*+(\\()",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.export.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ }
+ },
+ "end": "(\\))\\s*+(\\.)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "2": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "name": "meta.directive.export.erlang",
+ "patterns": [
+ {
+ "include": "#internal-function-list"
+ }
+ ]
+ }
+ ]
+ },
+ "internal-expression-punctuation": {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.clause-head-body.erlang"
+ },
+ "2": {
+ "name": "punctuation.separator.clauses.erlang"
+ },
+ "3": {
+ "name": "punctuation.separator.expressions.erlang"
+ }
+ },
+ "match": "(->)|(;)|(,)"
+ },
+ "internal-function-list": {
+ "begin": "(\\[)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.definition.list.begin.erlang"
+ }
+ },
+ "end": "(\\])",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.list.end.erlang"
+ }
+ },
+ "name": "meta.structure.list.function.erlang",
+ "patterns": [
+ {
+ "begin": "([a-z][a-zA-Z\\d@_]*+|'[^']*+')\\s*+(/)",
+ "beginCaptures": {
+ "1": {
+ "name": "entity.name.function.erlang"
+ },
+ "2": {
+ "name": "punctuation.separator.function-arity.erlang"
+ }
+ },
+ "end": "(,)|(?=\\])",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.separator.list.erlang"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ "internal-function-parts": {
+ "patterns": [
+ {
+ "begin": "(?=\\()",
+ "end": "(->)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.separator.clause-head-body.erlang"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(\\()",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ }
+ },
+ "end": "(\\))",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ }
+ },
+ "patterns": [
+ {
+ "match": ",",
+ "name": "punctuation.separator.parameters.erlang"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "match": ",|;",
+ "name": "punctuation.separator.guards.erlang"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "match": ",",
+ "name": "punctuation.separator.expressions.erlang"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ "internal-record-body": {
+ "begin": "(\\{)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.definition.class.record.begin.erlang"
+ }
+ },
+ "end": "(\\})",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.class.record.end.erlang"
+ }
+ },
+ "name": "meta.structure.record.erlang",
+ "patterns": [
+ {
+ "begin": "(([a-z][a-zA-Z\\d@_]*+|'[^']*+')|(_))",
+ "beginCaptures": {
+ "2": {
+ "name": "variable.other.field.erlang"
+ },
+ "3": {
+ "name": "variable.language.omitted.field.erlang"
+ }
+ },
+ "end": "(,)|(?=\\})",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.separator.class.record.erlang"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ "internal-type-specifiers": {
+ "begin": "(/)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.separator.value-type.erlang"
+ }
+ },
+ "end": "(?=,|:|>>)",
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "storage.type.erlang"
+ },
+ "2": {
+ "name": "storage.modifier.signedness.erlang"
+ },
+ "3": {
+ "name": "storage.modifier.endianness.erlang"
+ },
+ "4": {
+ "name": "storage.modifier.unit.erlang"
+ },
+ "5": {
+ "name": "punctuation.separator.unit-specifiers.erlang"
+ },
+ "6": {
+ "name": "constant.numeric.integer.decimal.erlang"
+ },
+ "7": {
+ "name": "punctuation.separator.type-specifiers.erlang"
+ }
+ },
+ "match": "(integer|float|binary|bytes|bitstring|bits|utf8|utf16|utf32)|(signed|unsigned)|(big|little|native)|(unit)(:)(\\d++)|(-)"
+ }
+ ]
+ },
+ "keyword": {
+ "match": "\\b(after|begin|case|catch|cond|end|fun|if|let|of|try|receive|when|maybe|else)\\b",
+ "name": "keyword.control.erlang"
+ },
+ "language-constant": {
+ "match": "\\b(false|true|undefined)\\b",
+ "name": "constant.language"
+ },
+ "list": {
+ "begin": "(\\[)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.definition.list.begin.erlang"
+ }
+ },
+ "end": "(\\])",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.list.end.erlang"
+ }
+ },
+ "name": "meta.structure.list.erlang",
+ "patterns": [
+ {
+ "match": "\\||\\|\\||,",
+ "name": "punctuation.separator.list.erlang"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ "macro-directive": {
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.ifdef.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ },
+ "4": {
+ "name": "entity.name.function.macro.erlang"
+ },
+ "5": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "6": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "match": "^\\s*+(-)\\s*+(ifdef)\\s*+(\\()\\s*+([a-zA-z\\d@_]++)\\s*+(\\))\\s*+(\\.)",
+ "name": "meta.directive.ifdef.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.ifndef.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ },
+ "4": {
+ "name": "entity.name.function.macro.erlang"
+ },
+ "5": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "6": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "match": "^\\s*+(-)\\s*+(ifndef)\\s*+(\\()\\s*+([a-zA-z\\d@_]++)\\s*+(\\))\\s*+(\\.)",
+ "name": "meta.directive.ifndef.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.undef.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ },
+ "4": {
+ "name": "entity.name.function.macro.erlang"
+ },
+ "5": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "6": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "match": "^\\s*+(-)\\s*+(undef)\\s*+(\\()\\s*+([a-zA-z\\d@_]++)\\s*+(\\))\\s*+(\\.)",
+ "name": "meta.directive.undef.erlang"
+ }
+ ]
+ },
+ "macro-usage": {
+ "captures": {
+ "1": {
+ "name": "keyword.operator.macro.erlang"
+ },
+ "2": {
+ "name": "entity.name.function.macro.erlang"
+ }
+ },
+ "match": "(\\?\\??)\\s*+([a-zA-Z\\d@_]++)",
+ "name": "meta.macro-usage.erlang"
+ },
+ "module-directive": {
+ "captures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.module.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ },
+ "4": {
+ "name": "entity.name.type.class.module.definition.erlang"
+ },
+ "5": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "6": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "match": "^\\s*+(-)\\s*+(module)\\s*+(\\()\\s*+([a-z][a-zA-Z\\d@_]*+)\\s*+(\\))\\s*+(\\.)",
+ "name": "meta.directive.module.erlang"
+ },
+ "number": {
+ "begin": "(?=\\d)",
+ "end": "(?!\\d)",
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.integer-float.erlang"
+ },
+ "2": {
+ "name": "punctuation.separator.float-exponent.erlang"
+ }
+ },
+ "match": "\\d++(\\.)\\d++([eE][\\+\\-]?\\d++)?",
+ "name": "constant.numeric.float.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "2(#)([0-1]++_)*[0-1]++",
+ "name": "constant.numeric.integer.binary.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "3(#)([0-2]++_)*[0-2]++",
+ "name": "constant.numeric.integer.base-3.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "4(#)([0-3]++_)*[0-3]++",
+ "name": "constant.numeric.integer.base-4.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "5(#)([0-4]++_)*[0-4]++",
+ "name": "constant.numeric.integer.base-5.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "6(#)([0-5]++_)*[0-5]++",
+ "name": "constant.numeric.integer.base-6.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "7(#)([0-6]++_)*[0-6]++",
+ "name": "constant.numeric.integer.base-7.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "8(#)([0-7]++_)*[0-7]++",
+ "name": "constant.numeric.integer.octal.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "9(#)([0-8]++_)*[0-8]++",
+ "name": "constant.numeric.integer.base-9.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "10(#)(\\d++_)*\\d++",
+ "name": "constant.numeric.integer.decimal.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "11(#)([\\daA]++_)*[\\daA]++",
+ "name": "constant.numeric.integer.base-11.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "12(#)([\\da-bA-B]++_)*[\\da-bA-B]++",
+ "name": "constant.numeric.integer.base-12.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "13(#)([\\da-cA-C]++_)*[\\da-cA-C]++",
+ "name": "constant.numeric.integer.base-13.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "14(#)([\\da-dA-D]++_)*[\\da-dA-D]++",
+ "name": "constant.numeric.integer.base-14.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "15(#)([\\da-eA-E]++_)*[\\da-eA-E]++",
+ "name": "constant.numeric.integer.base-15.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "16(#)([\\da-fA-F]++_)*[\\da-fA-F]++",
+ "name": "constant.numeric.integer.hexadecimal.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "17(#)([\\da-gA-G]++_)*[\\da-gA-G]++",
+ "name": "constant.numeric.integer.base-17.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "18(#)([\\da-hA-H]++_)*[\\da-hA-H]++",
+ "name": "constant.numeric.integer.base-18.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "19(#)([\\da-iA-I]++_)*[\\da-iA-I]++",
+ "name": "constant.numeric.integer.base-19.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "20(#)([\\da-jA-J]++_)*[\\da-jA-J]++",
+ "name": "constant.numeric.integer.base-20.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "21(#)([\\da-kA-K]++_)*[\\da-kA-K]++",
+ "name": "constant.numeric.integer.base-21.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "22(#)([\\da-lA-L]++_)*[\\da-lA-L]++",
+ "name": "constant.numeric.integer.base-22.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "23(#)([\\da-mA-M]++_)*[\\da-mA-M]++",
+ "name": "constant.numeric.integer.base-23.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "24(#)([\\da-nA-N]++_)*[\\da-nA-N]++",
+ "name": "constant.numeric.integer.base-24.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "25(#)([\\da-oA-O]++_)*[\\da-oA-O]++",
+ "name": "constant.numeric.integer.base-25.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "26(#)([\\da-pA-P]++_)*[\\da-pA-P]++",
+ "name": "constant.numeric.integer.base-26.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "27(#)([\\da-qA-Q]++_)*[\\da-qA-Q]++",
+ "name": "constant.numeric.integer.base-27.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "28(#)([\\da-rA-R]++_)*[\\da-rA-R]++",
+ "name": "constant.numeric.integer.base-28.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "29(#)([\\da-sA-S]++_)*[\\da-sA-S]++",
+ "name": "constant.numeric.integer.base-29.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "30(#)([\\da-tA-T]++_)*[\\da-tA-T]++",
+ "name": "constant.numeric.integer.base-30.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "31(#)([\\da-uA-U]++_)*[\\da-uA-U]++",
+ "name": "constant.numeric.integer.base-31.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "32(#)([\\da-vA-V]++_)*[\\da-vA-V]++",
+ "name": "constant.numeric.integer.base-32.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "33(#)([\\da-wA-W]++_)*[\\da-wA-W]++",
+ "name": "constant.numeric.integer.base-33.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "34(#)([\\da-xA-X]++_)*[\\da-xA-X]++",
+ "name": "constant.numeric.integer.base-34.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "35(#)([\\da-yA-Y]++_)*[\\da-yA-Y]++",
+ "name": "constant.numeric.integer.base-35.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.separator.base-integer.erlang"
+ }
+ },
+ "match": "36(#)([\\da-zA-Z]++_)*[\\da-zA-Z]++",
+ "name": "constant.numeric.integer.base-36.erlang"
+ },
+ {
+ "match": "\\d++#([\\da-zA-Z]++_)*[\\da-zA-Z]++",
+ "name": "invalid.illegal.integer.erlang"
+ },
+ {
+ "match": "(\\d++_)*\\d++",
+ "name": "constant.numeric.integer.decimal.erlang"
+ }
+ ]
+ },
+ "parenthesized-expression": {
+ "begin": "(\\()",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.section.expression.begin.erlang"
+ }
+ },
+ "end": "(\\))",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.section.expression.end.erlang"
+ }
+ },
+ "name": "meta.expression.parenthesized",
+ "patterns": [
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ "record-directive": {
+ "begin": "^\\s*+(-)\\s*+(record)\\s*+(\\()\\s*+([a-z][a-zA-Z\\d@_]*+|'[^']*+')\\s*+(,)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.section.directive.begin.erlang"
+ },
+ "2": {
+ "name": "keyword.control.directive.import.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.parameters.begin.erlang"
+ },
+ "4": {
+ "name": "entity.name.type.class.record.definition.erlang"
+ },
+ "5": {
+ "name": "punctuation.separator.parameters.erlang"
+ }
+ },
+ "end": "(\\))\\s*+(\\.)",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.parameters.end.erlang"
+ },
+ "2": {
+ "name": "punctuation.section.directive.end.erlang"
+ }
+ },
+ "name": "meta.directive.record.erlang",
+ "patterns": [
+ {
+ "include": "#internal-record-body"
+ },
+ {
+ "include": "#comment"
+ }
+ ]
+ },
+ "record-usage": {
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "keyword.operator.record.erlang"
+ },
+ "2": {
+ "name": "entity.name.type.class.record.erlang"
+ },
+ "3": {
+ "name": "punctuation.separator.record-field.erlang"
+ },
+ "4": {
+ "name": "variable.other.field.erlang"
+ }
+ },
+ "match": "(#)\\s*+([a-z][a-zA-Z\\d@_]*+|'[^']*+')\\s*+(\\.)\\s*+([a-z][a-zA-Z\\d@_]*+|'[^']*+')",
+ "name": "meta.record-usage.erlang"
+ },
+ {
+ "begin": "(#)\\s*+([a-z][a-zA-Z\\d@_]*+|'[^']*+')",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.operator.record.erlang"
+ },
+ "2": {
+ "name": "entity.name.type.class.record.erlang"
+ }
+ },
+ "end": "(?<=\\})",
+ "name": "meta.record-usage.erlang",
+ "patterns": [
+ {
+ "include": "#internal-record-body"
+ }
+ ]
+ }
+ ]
+ },
+ "string": {
+ "begin": "(\")",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.definition.string.begin.erlang"
+ }
+ },
+ "end": "(\")",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.string.end.erlang"
+ }
+ },
+ "name": "string.quoted.double.erlang",
+ "patterns": [
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.definition.escape.erlang"
+ },
+ "3": {
+ "name": "punctuation.definition.escape.erlang"
+ }
+ },
+ "match": "(\\\\)([bdefnrstv\\\\'\"]|(\\^)[@-_a-z]|[0-7]{1,3}|x[\\da-fA-F]{2})",
+ "name": "constant.character.escape.erlang"
+ },
+ {
+ "match": "\\\\\\^?.?",
+ "name": "invalid.illegal.string.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.definition.placeholder.erlang"
+ },
+ "3": {
+ "name": "punctuation.separator.placeholder-parts.erlang"
+ },
+ "4": {
+ "name": "punctuation.separator.placeholder-parts.erlang"
+ },
+ "6": {
+ "name": "punctuation.separator.placeholder-parts.erlang"
+ },
+ "8": {
+ "name": "punctuation.separator.placeholder-parts.erlang"
+ },
+ "10": {
+ "name": "punctuation.separator.placeholder-parts.erlang"
+ },
+ "12": {
+ "name": "punctuation.separator.placeholder-parts.erlang"
+ }
+ },
+ "match": "(~)((\\-)?\\d++|(\\*))?((\\.)(\\d++|(\\*)))?((\\.)((\\*)|.))?[~cfegswpWPBX#bx\\+ni]",
+ "name": "constant.other.placeholder.erlang"
+ },
+ {
+ "captures": {
+ "1": {
+ "name": "punctuation.definition.placeholder.erlang"
+ },
+ "2": {
+ "name": "punctuation.separator.placeholder-parts.erlang"
+ }
+ },
+ "match": "(~)(\\*)?(\\d++)?[~du\\-#fsacl]",
+ "name": "constant.other.placeholder.erlang"
+ },
+ {
+ "match": "~[^\"]?",
+ "name": "invalid.illegal.string.erlang"
+ }
+ ]
+ },
+ "symbolic-operator": {
+ "match": "\\+\\+|\\+|--|-|\\*|/=|/|=/=|=:=|==|=<|=|<-|<|>=|>|!|::|\\?=",
+ "name": "keyword.operator.symbolic.erlang"
+ },
+ "textual-operator": {
+ "match": "\\b(andalso|band|and|bxor|xor|bor|orelse|or|bnot|not|bsl|bsr|div|rem)\\b",
+ "name": "keyword.operator.textual.erlang"
+ },
+ "tuple": {
+ "begin": "(\\{)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.definition.tuple.begin.erlang"
+ }
+ },
+ "end": "(\\})",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.tuple.end.erlang"
+ }
+ },
+ "name": "meta.structure.tuple.erlang",
+ "patterns": [
+ {
+ "match": ",",
+ "name": "punctuation.separator.tuple.erlang"
+ },
+ {
+ "include": "#everything-else"
+ }
+ ]
+ },
+ "variable": {
+ "captures": {
+ "1": {
+ "name": "variable.other.erlang"
+ },
+ "2": {
+ "name": "variable.language.omitted.erlang"
+ }
+ },
+ "match": "(_[a-zA-Z\\d@_]++|[A-Z][a-zA-Z\\d@_]*+)|(_)"
+ }
+ },
+ "scopeName": "source.erlang",
+ "uuid": "58EA597D-5158-4BF7-9FB2-B05135D1E166"
+}
\ No newline at end of file
diff --git a/src/Resources/Grammars/ocaml.json b/src/Resources/Grammars/ocaml.json
new file mode 100644
index 0000000000..26674d6eb5
--- /dev/null
+++ b/src/Resources/Grammars/ocaml.json
@@ -0,0 +1,2961 @@
+{
+ "name": "ocaml",
+ "scopeName": "source.ocaml",
+ "fileTypes": [".ml", ".mli"],
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "include": "#decl"
+ }
+ ],
+ "repository": {
+ "attribute": {
+ "begin": "(\\[)[[:space:]]*((?|~$])@{1,3}(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\]",
+ "beginCaptures": {
+ "1": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ },
+ "2": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributePayload"
+ }
+ ]
+ },
+ "attributeIdentifier": {
+ "match": "((?|~$])%(?![#\\-:!?.@*/&%^+<=>|~$]))((?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*))",
+ "captures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "punctuation.definition.tag"
+ }
+ }
+ },
+ "attributePayload": {
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]%|^%))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "((?|~$])[:\\?](?![#\\-:!?.@*/&%^+<=>|~$]))|(?<=[[:space:]])|(?=\\])",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#pathModuleExtended"
+ },
+ {
+ "include": "#pathRecord"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?=\\])",
+ "patterns": [
+ {
+ "include": "#signature"
+ },
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]\\?|^\\?))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?=\\])",
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]\\?|^\\?))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?=\\])|\\bwhen\\b",
+ "endCaptures": {
+ "1": {}
+ },
+ "patterns": [
+ {
+ "include": "#pattern"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]when|^when))(?![[:word:]]))",
+ "end": "(?=\\])",
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "include": "#term"
+ }
+ ]
+ },
+ "bindClassTerm": {
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]and|^and|[^[:word:]]class|^class|[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "(?|~$])(:)|(=)(?![#\\-:!?.@*/&%^+<=>|~$])|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]and|^and|[^[:word:]]class|^class|[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "(?=(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)[[:space:]]*,|[^[:space:][:lower:]%])|(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)|(?=\\btype\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "entity.name.function strong emphasis"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ }
+ ]
+ },
+ {
+ "begin": "\\[",
+ "end": "\\]",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "include": "#bindTermArgs"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?|~$])=(?![#\\-:!?.@*/&%^+<=>|~$])|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#literalClassType"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\band\\b|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ }
+ ]
+ },
+ "bindClassType": {
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]and|^and|[^[:word:]]class|^class|[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "(?|~$])(:)|(=)(?![#\\-:!?.@*/&%^+<=>|~$])|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]and|^and|[^[:word:]]class|^class|[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "(?=(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)[[:space:]]*,|[^[:space:][:lower:]%])|(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)|(?=\\btype\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "entity.name.function strong emphasis"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ }
+ ]
+ },
+ {
+ "begin": "\\[",
+ "end": "\\]",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "include": "#bindTermArgs"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?|~$])=(?![#\\-:!?.@*/&%^+<=>|~$])|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#literalClassType"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\band\\b|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#literalClassType"
+ }
+ ]
+ }
+ ]
+ },
+ "bindConstructor": {
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]exception|^exception))(?![[:word:]]))|(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]\\+=|^\\+=|[^#\\-:!?.@*/&%^+<=>|~$]=|^=|[^#\\-:!?.@*/&%^+<=>|~$]\\||^\\|))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(:)|(\\bof\\b)|((?|~$])\\|(?![#\\-:!?.@*/&%^+<=>|~$]))|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "punctuation.definition.tag"
+ },
+ "3": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ },
+ {
+ "match": "\\.\\.",
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ {
+ "match": "\\b(?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)\\b(?![[:space:]]*(?:\\.|\\([^\\*]))",
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ },
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))|(?:(?<=(?:[^[:word:]]of|^of))(?![[:word:]]))",
+ "end": "(?|~$])\\|(?![#\\-:!?.@*/&%^+<=>|~$])|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ }
+ ]
+ },
+ "bindSignature": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "(?|~$])=(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "endCaptures": {
+ "0": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pathModuleExtended"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\band\\b|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#signature"
+ }
+ ]
+ }
+ ]
+ },
+ "bindStructure": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]and|^and))(?![[:word:]]))|(?=[[:upper:]])",
+ "end": "(?|~$])(:(?!=))|(:?=)(?![#\\-:!?.@*/&%^+<=>|~$])|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|open|type|val)\\b)",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "match": "\\bmodule\\b",
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ },
+ {
+ "match": "(?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)",
+ "name": "entity.name.function strong emphasis"
+ },
+ {
+ "begin": "\\((?!\\))",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "begin": "(?|~$]):(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": "(?=\\))",
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#signature"
+ }
+ ]
+ },
+ {
+ "include": "#variableModule"
+ }
+ ]
+ },
+ {
+ "include": "#literalUnit"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\b(and)\\b|((?|~$])=(?![#\\-:!?.@*/&%^+<=>|~$]))|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#signature"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:=|^:=|[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\b(?:(and)|(with))\\b|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ },
+ "2": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#structure"
+ }
+ ]
+ }
+ ]
+ },
+ "bindTerm": {
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]!|^!))(?![#\\-:!?.@*/&%^+<=>|~$]))|(?:(?<=(?:[^[:word:]]and|^and|[^[:word:]]external|^external|[^[:word:]]let|^let|[^[:word:]]method|^method|[^[:word:]]val|^val))(?![[:word:]]))",
+ "end": "(\\bmodule\\b)|(\\bopen\\b)|(?|~$])(:)|((?|~$])=(?![#\\-:!?.@*/&%^+<=>|~$]))(?![#\\-:!?.@*/&%^+<=>|~$])|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "1": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ },
+ "2": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "3": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "4": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]!|^!))(?![#\\-:!?.@*/&%^+<=>|~$]))|(?:(?<=(?:[^[:word:]]and|^and|[^[:word:]]external|^external|[^[:word:]]let|^let|[^[:word:]]method|^method|[^[:word:]]val|^val))(?![[:word:]]))",
+ "end": "(?=\\b(?:module|open)\\b)|(?=(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)[[:space:]]*,|[^[:space:][:lower:]%])|(\\brec\\b)|((?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*))",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "2": {
+ "name": "entity.name.function strong emphasis"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ },
+ {
+ "include": "#comment"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]rec|^rec))(?![[:word:]]))",
+ "end": "((?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*))|(?=[^[:space:][:alpha:]])",
+ "endCaptures": {
+ "0": {
+ "name": "entity.name.function strong emphasis"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#bindTermArgs"
+ }
+ ]
+ },
+ {
+ "include": "#bindTermArgs"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]module|^module))(?![[:word:]]))",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "patterns": [
+ {
+ "include": "#declModule"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]open|^open))(?![[:word:]]))",
+ "end": "(?=\\bin\\b)|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "patterns": [
+ {
+ "include": "#pathModuleSimple"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?|~$])=(?![#\\-:!?.@*/&%^+<=>|~$])|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\btype\\b|(?=[^[:space:]])",
+ "endCaptures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ }
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "(?|~$])\\.(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "endCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#pattern"
+ }
+ ]
+ },
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\band\\b|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ }
+ ]
+ },
+ "bindTermArgs": {
+ "patterns": [
+ {
+ "begin": "~|\\?",
+ "end": ":|(?=[^[:space:]])",
+ "applyEndPatternLast": true,
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]~|^~|[^#\\-:!?.@*/&%^+<=>|~$]\\?|^\\?))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)|(?<=\\))",
+ "endCaptures": {
+ "0": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "begin": "\\((?!\\*)",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?<=\\()",
+ "end": ":|=",
+ "endCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "match": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ }
+ ]
+ },
+ {
+ "begin": "(?<=:)",
+ "end": "=|(?=\\))",
+ "endCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?=\\))",
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "include": "#pattern"
+ }
+ ]
+ },
+ "bindType": {
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]and|^and|[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "(?|~$])\\+=|=(?![#\\-:!?.@*/&%^+<=>|~$])|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ },
+ {
+ "include": "#pathType"
+ },
+ {
+ "match": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "name": "entity.name.function strong"
+ },
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]\\+=|^\\+=|[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\band\\b|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#bindConstructor"
+ }
+ ]
+ }
+ ]
+ },
+ "comment": {
+ "patterns": [
+ {
+ "include": "#attribute"
+ },
+ {
+ "include": "#extension"
+ },
+ {
+ "include": "#commentBlock"
+ },
+ {
+ "include": "#commentDoc"
+ }
+ ]
+ },
+ "commentBlock": {
+ "begin": "\\(\\*(?!\\*[^\\)])",
+ "end": "\\*\\)",
+ "name": "comment",
+ "contentName": "emphasis",
+ "patterns": [
+ {
+ "include": "#commentBlock"
+ },
+ {
+ "include": "#commentDoc"
+ }
+ ]
+ },
+ "commentDoc": {
+ "begin": "\\(\\*\\*",
+ "end": "\\*\\)",
+ "name": "comment",
+ "patterns": [
+ {
+ "match": "\\*"
+ },
+ {
+ "include": "#comment"
+ }
+ ]
+ },
+ "decl": {
+ "patterns": [
+ {
+ "include": "#declClass"
+ },
+ {
+ "include": "#declException"
+ },
+ {
+ "include": "#declInclude"
+ },
+ {
+ "include": "#declModule"
+ },
+ {
+ "include": "#declOpen"
+ },
+ {
+ "include": "#declTerm"
+ },
+ {
+ "include": "#declType"
+ }
+ ]
+ },
+ "declClass": {
+ "begin": "\\bclass\\b",
+ "end": ";;|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "entity.name.class constant.numeric markup.underline"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]class|^class))(?![[:word:]]))",
+ "end": "\\btype\\b|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "entity.name.class constant.numeric markup.underline"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#bindClassTerm"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "patterns": [
+ {
+ "include": "#bindClassType"
+ }
+ ]
+ }
+ ]
+ },
+ "declException": {
+ "begin": "\\bexception\\b",
+ "end": ";;|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword markup.underline"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ },
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "include": "#bindConstructor"
+ }
+ ]
+ },
+ "declInclude": {
+ "begin": "\\binclude\\b",
+ "end": ";;|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ },
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "include": "#signature"
+ }
+ ]
+ },
+ "declModule": {
+ "begin": "(?:(?<=(?:[^[:word:]]module|^module))(?![[:word:]]))|\\bmodule\\b",
+ "end": ";;|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename markup.underline"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]module|^module))(?![[:word:]]))",
+ "end": "(\\btype\\b)|(?=[[:upper:]])",
+ "endCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ },
+ {
+ "include": "#comment"
+ },
+ {
+ "match": "\\brec\\b",
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "patterns": [
+ {
+ "include": "#bindSignature"
+ }
+ ]
+ },
+ {
+ "begin": "(?=[[:upper:]])",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "patterns": [
+ {
+ "include": "#bindStructure"
+ }
+ ]
+ }
+ ]
+ },
+ "declOpen": {
+ "begin": "\\bopen\\b",
+ "end": ";;|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ },
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "include": "#pathModuleExtended"
+ }
+ ]
+ },
+ "declTerm": {
+ "begin": "\\b(?:(external|val)|(method)|(let))\\b(!?)",
+ "end": ";;|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "1": {
+ "name": "support.type markup.underline"
+ },
+ "2": {
+ "name": "storage.type markup.underline"
+ },
+ "3": {
+ "name": "keyword.control markup.underline"
+ },
+ "4": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "include": "#bindTerm"
+ }
+ ]
+ },
+ "declType": {
+ "begin": "(?:(?<=(?:[^[:word:]]type|^type))(?![[:word:]]))|\\btype\\b",
+ "end": ";;|(?=\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword markup.underline"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "include": "#bindType"
+ }
+ ]
+ },
+ "extension": {
+ "begin": "(\\[)((?|~$])%{1,3}(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\]",
+ "beginCaptures": {
+ "1": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ },
+ "2": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributePayload"
+ }
+ ]
+ },
+ "literal": {
+ "patterns": [
+ {
+ "include": "#termConstructor"
+ },
+ {
+ "include": "#literalArray"
+ },
+ {
+ "include": "#literalBoolean"
+ },
+ {
+ "include": "#literalCharacter"
+ },
+ {
+ "include": "#literalList"
+ },
+ {
+ "include": "#literalNumber"
+ },
+ {
+ "include": "#literalObjectTerm"
+ },
+ {
+ "include": "#literalString"
+ },
+ {
+ "include": "#literalRecord"
+ },
+ {
+ "include": "#literalUnit"
+ }
+ ]
+ },
+ "literalArray": {
+ "begin": "\\[\\|",
+ "end": "\\|\\]",
+ "captures": {
+ "0": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ },
+ "literalBoolean": {
+ "match": "\\bfalse|true\\b",
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ },
+ "literalCharacter": {
+ "begin": "(?|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename emphasis"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]with|^with))(?![[:word:]]))",
+ "end": "(:)|(=)|(;)|(?=\\})",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ },
+ "3": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "match": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename emphasis"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(;)|(=)|(?=\\})",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": ";|(?=\\})",
+ "endCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ }
+ ]
+ },
+ "literalString": {
+ "patterns": [
+ {
+ "begin": "\"",
+ "end": "\"",
+ "name": "string beginning.punctuation.definition.quote.markdown",
+ "patterns": [
+ {
+ "include": "#literalStringEscape"
+ }
+ ]
+ },
+ {
+ "begin": "(\\{)([_[:lower:]]*?)(\\|)",
+ "end": "(\\|)(\\2)(\\})",
+ "name": "string beginning.punctuation.definition.quote.markdown",
+ "patterns": [
+ {
+ "include": "#literalStringEscape"
+ }
+ ]
+ }
+ ]
+ },
+ "literalStringEscape": {
+ "match": "\\\\(?:[\\\\\"ntbr]|[[:digit:]][[:digit:]][[:digit:]]|x[[:xdigit:]][[:xdigit:]]|o[0-3][0-7][0-7])"
+ },
+ "literalUnit": {
+ "match": "\\(\\)",
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ },
+ "pathModuleExtended": {
+ "patterns": [
+ {
+ "include": "#pathModulePrefixExtended"
+ },
+ {
+ "match": "(?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)",
+ "name": "entity.name.class constant.numeric"
+ }
+ ]
+ },
+ "pathModulePrefixExtended": {
+ "begin": "(?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)(?=[[:space:]]*\\.|$|\\()",
+ "end": "(?![[:space:]\\.]|$|\\()",
+ "beginCaptures": {
+ "0": {
+ "name": "entity.name.class constant.numeric"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "begin": "\\(",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "patterns": [
+ {
+ "match": "((?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)(?=[[:space:]]*\\)))",
+ "name": "string.other.link variable.language variable.parameter emphasis"
+ },
+ {
+ "include": "#structure"
+ }
+ ]
+ },
+ {
+ "begin": "(?|~$])\\.(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": "((?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)(?=[[:space:]]*\\.|$))|((?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)(?=[[:space:]]*(?:$|\\()))|((?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)(?=[[:space:]]*\\)))|(?![[:space:]\\.[:upper:]]|$|\\()",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword strong"
+ }
+ },
+ "endCaptures": {
+ "1": {
+ "name": "entity.name.class constant.numeric"
+ },
+ "2": {
+ "name": "entity.name.function strong"
+ },
+ "3": {
+ "name": "string.other.link variable.language variable.parameter emphasis"
+ }
+ }
+ }
+ ]
+ },
+ "pathModulePrefixExtendedParens": {
+ "begin": "\\(",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "patterns": [
+ {
+ "match": "((?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)(?=[[:space:]]*\\)))",
+ "name": "string.other.link variable.language variable.parameter emphasis"
+ },
+ {
+ "include": "#structure"
+ }
+ ]
+ },
+ "pathModulePrefixSimple": {
+ "begin": "(?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)(?=[[:space:]]*\\.)",
+ "end": "(?![[:space:]\\.])",
+ "beginCaptures": {
+ "0": {
+ "name": "entity.name.class constant.numeric"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "begin": "(?|~$])\\.(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": "((?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)(?=[[:space:]]*\\.))|((?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)(?=[[:space:]]*))|(?![[:space:]\\.[:upper:]])",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword strong"
+ }
+ },
+ "endCaptures": {
+ "1": {
+ "name": "entity.name.class constant.numeric"
+ },
+ "2": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ }
+ }
+ }
+ ]
+ },
+ "pathModuleSimple": {
+ "patterns": [
+ {
+ "include": "#pathModulePrefixSimple"
+ },
+ {
+ "match": "(?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)",
+ "name": "entity.name.class constant.numeric"
+ }
+ ]
+ },
+ "pathRecord": {
+ "patterns": [
+ {
+ "begin": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "end": "(?=[^[:space:]\\.])(?!\\(\\*)",
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]\\.|^\\.))(?![#\\-:!?.@*/&%^+<=>|~$]))|(?|~$])\\.(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": "((?|~$])\\.(?![#\\-:!?.@*/&%^+<=>|~$]))|((?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|mutable|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*))|(?<=\\))|(?<=\\])",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword strong"
+ }
+ },
+ "endCaptures": {
+ "1": {
+ "name": "keyword strong"
+ },
+ "2": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pathModulePrefixSimple"
+ },
+ {
+ "begin": "\\((?!\\*)",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ },
+ {
+ "begin": "\\[",
+ "end": "\\]",
+ "captures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#pattern"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "pattern": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#patternArray"
+ },
+ {
+ "include": "#patternLazy"
+ },
+ {
+ "include": "#patternList"
+ },
+ {
+ "include": "#patternMisc"
+ },
+ {
+ "include": "#patternModule"
+ },
+ {
+ "include": "#patternRecord"
+ },
+ {
+ "include": "#literal"
+ },
+ {
+ "include": "#patternParens"
+ },
+ {
+ "include": "#patternType"
+ },
+ {
+ "include": "#variablePattern"
+ },
+ {
+ "include": "#termOperator"
+ }
+ ]
+ },
+ "patternArray": {
+ "begin": "\\[\\|",
+ "end": "\\|\\]",
+ "captures": {
+ "0": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#pattern"
+ }
+ ]
+ },
+ "patternLazy": {
+ "match": "lazy",
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "patternList": {
+ "begin": "\\[",
+ "end": "\\]",
+ "captures": {
+ "0": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#pattern"
+ }
+ ]
+ },
+ "patternMisc": {
+ "match": "((?|~$]),(?![#\\-:!?.@*/&%^+<=>|~$]))|([#\\-:!?.@*/&%^+<=>|~$]+)|\\b(as)\\b",
+ "captures": {
+ "1": {
+ "name": "string.regexp strong"
+ },
+ "2": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "3": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ }
+ },
+ "patternModule": {
+ "begin": "\\bmodule\\b",
+ "end": "(?=\\))",
+ "beginCaptures": {
+ "0": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#declModule"
+ }
+ ]
+ },
+ "patternParens": {
+ "begin": "\\((?!\\))",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "begin": "(?|~$]):(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": "(?=\\))",
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "include": "#pattern"
+ }
+ ]
+ },
+ "patternRecord": {
+ "begin": "\\{",
+ "end": "\\}",
+ "captures": {
+ "0": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong strong"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?<=\\{|;)",
+ "end": "(:)|(=)|(;)|(with)|(?=\\})",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ },
+ "3": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "4": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pathModulePrefixSimple"
+ },
+ {
+ "match": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename emphasis"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]with|^with))(?![[:word:]]))",
+ "end": "(:)|(=)|(;)|(?=\\})",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ },
+ "3": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "match": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename emphasis"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(;)|(=)|(?=\\})",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": ";|(?=\\})",
+ "endCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#pattern"
+ }
+ ]
+ }
+ ]
+ },
+ "patternType": {
+ "begin": "\\btype\\b",
+ "end": "(?=\\))",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#declType"
+ }
+ ]
+ },
+ "pragma": {
+ "begin": "(?|~$])#(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#literalNumber"
+ },
+ {
+ "include": "#literalString"
+ }
+ ]
+ },
+ "signature": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#signatureLiteral"
+ },
+ {
+ "include": "#signatureFunctor"
+ },
+ {
+ "include": "#pathModuleExtended"
+ },
+ {
+ "include": "#signatureParens"
+ },
+ {
+ "include": "#signatureRecovered"
+ },
+ {
+ "include": "#signatureConstraints"
+ }
+ ]
+ },
+ "signatureConstraints": {
+ "begin": "\\bwith\\b",
+ "end": "(?=\\))|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]with|^with))(?![[:word:]]))",
+ "end": "\\b(?:(module)|(type))\\b",
+ "endCaptures": {
+ "1": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ },
+ "2": {
+ "name": "keyword"
+ }
+ }
+ },
+ {
+ "include": "#declModule"
+ },
+ {
+ "include": "#declType"
+ }
+ ]
+ },
+ "signatureFunctor": {
+ "patterns": [
+ {
+ "begin": "\\bfunctor\\b",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]functor|^functor))(?![[:word:]]))",
+ "end": "(\\(\\))|(\\((?!\\)))",
+ "endCaptures": {
+ "1": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ },
+ "2": {
+ "name": "punctuation.definition.tag"
+ }
+ }
+ },
+ {
+ "begin": "(?<=\\()",
+ "end": "(:)|(\\))",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#variableModule"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\)",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#signature"
+ }
+ ]
+ },
+ {
+ "begin": "(?<=\\))",
+ "end": "(\\()|((?|~$])->(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.tag"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ }
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]->|^->))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "patterns": [
+ {
+ "include": "#signature"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "match": "(?|~$])->(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "name": "support.type strong"
+ }
+ ]
+ },
+ "signatureLiteral": {
+ "begin": "\\bsig\\b",
+ "end": "\\bend\\b",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag emphasis"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "include": "#decl"
+ }
+ ]
+ },
+ "signatureParens": {
+ "begin": "\\((?!\\))",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "begin": "(?|~$]):(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": "(?=\\))",
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#signature"
+ }
+ ]
+ },
+ {
+ "include": "#signature"
+ }
+ ]
+ },
+ "signatureRecovered": {
+ "patterns": [
+ {
+ "begin": "\\(|(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:|[^#\\-:!?.@*/&%^+<=>|~$]->|^->))(?![#\\-:!?.@*/&%^+<=>|~$]))|(?:(?<=(?:[^[:word:]]include|^include|[^[:word:]]open|^open))(?![[:word:]]))",
+ "end": "\\bmodule\\b|(?!$|[[:space:]]|\\bmodule\\b)",
+ "endCaptures": {
+ "0": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ }
+ }
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]module|^module))(?![[:word:]]))",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]module|^module))(?![[:word:]]))",
+ "end": "\\btype\\b",
+ "endCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ }
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]type|^type))(?![[:word:]]))",
+ "end": "\\bof\\b",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ }
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]of|^of))(?![[:word:]]))",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "patterns": [
+ {
+ "include": "#signature"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "structure": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#structureLiteral"
+ },
+ {
+ "include": "#structureFunctor"
+ },
+ {
+ "include": "#pathModuleExtended"
+ },
+ {
+ "include": "#structureParens"
+ }
+ ]
+ },
+ "structureFunctor": {
+ "patterns": [
+ {
+ "begin": "\\bfunctor\\b",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]functor|^functor))(?![[:word:]]))",
+ "end": "(\\(\\))|(\\((?!\\)))",
+ "endCaptures": {
+ "1": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ },
+ "2": {
+ "name": "punctuation.definition.tag"
+ }
+ }
+ },
+ {
+ "begin": "(?<=\\()",
+ "end": "(:)|(\\))",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#variableModule"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\)",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#signature"
+ }
+ ]
+ },
+ {
+ "begin": "(?<=\\))",
+ "end": "(\\()|((?|~$])->(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.definition.tag"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ }
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]->|^->))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "patterns": [
+ {
+ "include": "#structure"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "match": "(?|~$])->(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "name": "support.type strong"
+ }
+ ]
+ },
+ "structureLiteral": {
+ "begin": "\\bstruct\\b",
+ "end": "\\bend\\b",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag emphasis"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pragma"
+ },
+ {
+ "include": "#decl"
+ }
+ ]
+ },
+ "structureParens": {
+ "begin": "\\(",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#structureUnpack"
+ },
+ {
+ "include": "#structure"
+ }
+ ]
+ },
+ "structureUnpack": {
+ "begin": "\\bval\\b",
+ "end": "(?=\\))",
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ }
+ },
+ "term": {
+ "patterns": [
+ {
+ "include": "#termLet"
+ },
+ {
+ "include": "#termAtomic"
+ }
+ ]
+ },
+ "termAtomic": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#termConditional"
+ },
+ {
+ "include": "#termConstructor"
+ },
+ {
+ "include": "#termDelim"
+ },
+ {
+ "include": "#termFor"
+ },
+ {
+ "include": "#termFunction"
+ },
+ {
+ "include": "#literal"
+ },
+ {
+ "include": "#termMatch"
+ },
+ {
+ "include": "#termMatchRule"
+ },
+ {
+ "include": "#termPun"
+ },
+ {
+ "include": "#termOperator"
+ },
+ {
+ "include": "#termTry"
+ },
+ {
+ "include": "#termWhile"
+ },
+ {
+ "include": "#pathRecord"
+ }
+ ]
+ },
+ "termConditional": {
+ "match": "\\b(?:if|then|else)\\b",
+ "name": "keyword.control"
+ },
+ "termConstructor": {
+ "patterns": [
+ {
+ "include": "#pathModulePrefixSimple"
+ },
+ {
+ "match": "(?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)",
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong"
+ }
+ ]
+ },
+ "termDelim": {
+ "patterns": [
+ {
+ "begin": "\\((?!\\))",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ },
+ {
+ "begin": "\\bbegin\\b",
+ "end": "\\bend\\b",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#attributeIdentifier"
+ },
+ {
+ "include": "#term"
+ }
+ ]
+ }
+ ]
+ },
+ "termFor": {
+ "patterns": [
+ {
+ "begin": "\\bfor\\b",
+ "end": "\\bdone\\b",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]for|^for))(?![[:word:]]))",
+ "end": "(?|~$])=(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "endCaptures": {
+ "0": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#pattern"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "\\b(?:downto|to)\\b",
+ "endCaptures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]to|^to))(?![[:word:]]))",
+ "end": "\\bdo\\b",
+ "endCaptures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]do|^do))(?![[:word:]]))",
+ "end": "(?=\\bdone\\b)",
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "termFunction": {
+ "match": "\\b(?:(fun)|(function))\\b",
+ "captures": {
+ "1": {
+ "name": "storage.type"
+ },
+ "2": {
+ "name": "storage.type"
+ }
+ }
+ },
+ "termLet": {
+ "patterns": [
+ {
+ "begin": "(?:(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=|[^#\\-:!?.@*/&%^+<=>|~$]->|^->))(?![#\\-:!?.@*/&%^+<=>|~$]))|(?<=;|\\())(?=[[:space:]]|\\blet\\b)|(?:(?<=(?:[^[:word:]]begin|^begin|[^[:word:]]do|^do|[^[:word:]]else|^else|[^[:word:]]in|^in|[^[:word:]]struct|^struct|[^[:word:]]then|^then|[^[:word:]]try|^try))(?![[:word:]]))|(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]@@|^@@))(?![#\\-:!?.@*/&%^+<=>|~$]))[[:space:]]+",
+ "end": "\\b(?:(and)|(let))\\b|(?=[^[:space:]])(?!\\(\\*)",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ },
+ "2": {
+ "name": "storage.type markup.underline"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]and|^and|[^[:word:]]let|^let))(?![[:word:]]))|(let)",
+ "end": "\\b(?:(and)|(in))\\b|(?=\\}|\\)|\\]|\\b(?:end|class|exception|external|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "1": {
+ "name": "storage.type markup.underline"
+ }
+ },
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp markup.underline"
+ },
+ "2": {
+ "name": "storage.type markup.underline"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#bindTerm"
+ }
+ ]
+ }
+ ]
+ },
+ "termMatch": {
+ "begin": "\\bmatch\\b",
+ "end": "\\bwith\\b",
+ "captures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ },
+ "termMatchRule": {
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]fun|^fun|[^[:word:]]function|^function|[^[:word:]]with|^with))(?![[:word:]]))",
+ "end": "(?|~$])(\\|)|(->)(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "endCaptures": {
+ "1": {
+ "name": "support.type strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#attributeIdentifier"
+ },
+ {
+ "include": "#pattern"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^\\[#\\-:!?.@*/&%^+<=>|~$]\\||^\\|))(?![#\\-:!?.@*/&%^+<=>|~$]))|(?|~$])\\|(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": "(?|~$])(\\|)|(->)(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "beginCaptures": {
+ "0": {
+ "name": "support.type strong"
+ }
+ },
+ "endCaptures": {
+ "1": {
+ "name": "support.type strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#pattern"
+ },
+ {
+ "begin": "\\bwhen\\b",
+ "end": "(?=(?|~$])->(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "termOperator": {
+ "patterns": [
+ {
+ "begin": "(?|~$])#(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "entity.name.function"
+ }
+ }
+ },
+ {
+ "match": "<-",
+ "captures": {
+ "0": {
+ "name": "keyword.control strong"
+ }
+ }
+ },
+ {
+ "match": "(,|[#\\-:!?.@*/&%^+<=>|~$]+)|(;)",
+ "captures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "2": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ }
+ },
+ {
+ "match": "\\b(?:and|assert|asr|land|lazy|lsr|lxor|mod|new|or)\\b",
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ ]
+ },
+ "termPun": {
+ "begin": "(?|~$])\\?|~(?![#\\-:!?.@*/&%^+<=>|~$])",
+ "end": ":|(?=[^[:space:]:])",
+ "applyEndPatternLast": true,
+ "beginCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]\\?|^\\?|[^#\\-:!?.@*/&%^+<=>|~$]~|^~))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "endCaptures": {
+ "0": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ }
+ }
+ }
+ ]
+ },
+ "termTry": {
+ "begin": "\\btry\\b",
+ "end": "\\bwith\\b",
+ "captures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ },
+ "termWhile": {
+ "patterns": [
+ {
+ "begin": "\\bwhile\\b",
+ "end": "\\bdone\\b",
+ "beginCaptures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]while|^while))(?![[:word:]]))",
+ "end": "\\bdo\\b",
+ "endCaptures": {
+ "0": {
+ "name": "keyword.control"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]do|^do))(?![[:word:]]))",
+ "end": "(?=\\bdone\\b)",
+ "patterns": [
+ {
+ "include": "#term"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "type": {
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "match": "\\bnonrec\\b",
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ {
+ "include": "#pathModulePrefixExtended"
+ },
+ {
+ "include": "#typeLabel"
+ },
+ {
+ "include": "#typeObject"
+ },
+ {
+ "include": "#typeOperator"
+ },
+ {
+ "include": "#typeParens"
+ },
+ {
+ "include": "#typePolymorphicVariant"
+ },
+ {
+ "include": "#typeRecord"
+ },
+ {
+ "include": "#typeConstructor"
+ }
+ ]
+ },
+ "typeConstructor": {
+ "patterns": [
+ {
+ "begin": "(_)|((?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*))|(')((?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*))|(?<=[^\\*]\\)|\\])",
+ "end": "(?=\\((?!\\*)|\\*|:|,|=|\\.|>|-|\\{|\\[|\\+|\\}|\\)|\\]|;|\\|)|((?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*))[:space:]*(?!\\(\\*|[[:word:]])|(?=;;|\\}|\\)|\\]|\\b(?:end|and|class|exception|external|in|include|inherit|initializer|let|method|module|open|type|val)\\b)",
+ "beginCaptures": {
+ "1": {
+ "name": "comment constant.regexp meta.separator.markdown"
+ },
+ "3": {
+ "name": "string.other.link variable.language variable.parameter emphasis strong emphasis"
+ },
+ "4": {
+ "name": "keyword.control emphasis"
+ }
+ },
+ "endCaptures": {
+ "1": {
+ "name": "entity.name.function strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pathModulePrefixExtended"
+ }
+ ]
+ }
+ ]
+ },
+ "typeLabel": {
+ "patterns": [
+ {
+ "begin": "(\\??)((?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*))[[:space:]]*((?|~$]):(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(?=(?|~$])->(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "captures": {
+ "1": {
+ "name": "keyword strong emphasis"
+ },
+ "2": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename emphasis"
+ },
+ "3": {
+ "name": "keyword"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ }
+ ]
+ },
+ "typeModule": {
+ "begin": "\\bmodule\\b",
+ "end": "(?=\\))",
+ "beginCaptures": {
+ "0": {
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#pathModuleExtended"
+ },
+ {
+ "include": "#signatureConstraints"
+ }
+ ]
+ },
+ "typeObject": {
+ "begin": "<",
+ "end": ">",
+ "captures": {
+ "0": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong strong"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?<=<|;)",
+ "end": "(:)|(?=>)",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "3": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "4": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pathModulePrefixSimple"
+ },
+ {
+ "match": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename emphasis"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(;)|(?=>)",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ }
+ ]
+ },
+ "typeOperator": {
+ "patterns": [
+ {
+ "match": ",|;|[#\\-:!?.@*/&%^+<=>|~$]+",
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ }
+ ]
+ },
+ "typeParens": {
+ "begin": "\\(",
+ "end": "\\)",
+ "captures": {
+ "0": {
+ "name": "punctuation.definition.tag"
+ }
+ },
+ "patterns": [
+ {
+ "match": ",",
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ {
+ "include": "#typeModule"
+ },
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ "typePolymorphicVariant": {
+ "begin": "\\[",
+ "end": "\\]",
+ "patterns": []
+ },
+ "typeRecord": {
+ "begin": "\\{",
+ "end": "\\}",
+ "captures": {
+ "0": {
+ "name": "constant.language constant.numeric entity.other.attribute-name.id.css strong strong"
+ }
+ },
+ "patterns": [
+ {
+ "begin": "(?<=\\{|;)",
+ "end": "(:)|(=)|(;)|(with)|(?=\\})",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ },
+ "3": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "4": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#comment"
+ },
+ {
+ "include": "#pathModulePrefixSimple"
+ },
+ {
+ "match": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename emphasis"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^[:word:]]with|^with))(?![[:word:]]))",
+ "end": "(:)|(=)|(;)|(?=\\})",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp strong"
+ },
+ "2": {
+ "name": "support.type strong"
+ },
+ "3": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "match": "(?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*)",
+ "name": "markup.inserted constant.language support.constant.property-value entity.name.filename emphasis"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]:|^:))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": "(;)|(=)|(?=\\})",
+ "endCaptures": {
+ "1": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ },
+ "2": {
+ "name": "support.type strong"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ },
+ {
+ "begin": "(?:(?<=(?:[^#\\-:!?.@*/&%^+<=>|~$]=|^=))(?![#\\-:!?.@*/&%^+<=>|~$]))",
+ "end": ";|(?=\\})",
+ "endCaptures": {
+ "0": {
+ "name": "variable.other.class.js message.error variable.interpolation string.regexp"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#type"
+ }
+ ]
+ }
+ ]
+ },
+ "variableModule": {
+ "match": "(?:\\b(?=[[:upper:]])[[:alpha:]_][[:word:]']*)",
+ "captures": {
+ "0": {
+ "name": "string.other.link variable.language variable.parameter emphasis"
+ }
+ }
+ },
+ "variablePattern": {
+ "match": "(\\b_\\b)|((?:(?!\\b(?:and|'|as|asr|assert|\\*|begin|class|:|,|@|constraint|do|done|downto|else|end|=|exception|external|false|for|\\.|fun|function|functor|>|-|if|in|include|inherit|initializer|land|lazy|\\{|\\(|\\[|<|let|lor|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|#|object|of|open|or|%|\\+|private|\\?|\"|rec|\\\\|\\}|\\)|\\]|;|sig|/|struct|then|~|to|true|try|type|val|\\||virtual|when|while|with)\\b(?:[^']|$))\\b(?=[[:lower:]_])[[:alpha:]_][[:word:]']*))",
+ "captures": {
+ "1": {
+ "name": "comment constant.regexp meta.separator.markdown"
+ },
+ "2": {
+ "name": "string.other.link variable.language variable.parameter emphasis"
+ }
+ }
+ }
+ }
+}
diff --git a/src/Resources/Grammars/ocamllex.json b/src/Resources/Grammars/ocamllex.json
new file mode 100644
index 0000000000..80795ad9e0
--- /dev/null
+++ b/src/Resources/Grammars/ocamllex.json
@@ -0,0 +1,302 @@
+{
+ "name": "ocamllex",
+ "scopeName": "source.ocamllex",
+ "fileTypes": [ "mll" ],
+ "foldingStartMarker": "{",
+ "foldingStopMarker": "}",
+ "keyEquivalent": "^~O",
+ "patterns": [
+ {
+ "begin": "^\\s*({)",
+ "beginCaptures": {
+ "1": {
+ "name": "punctuation.section.embedded.ocaml.begin.ocamllex"
+ }
+ },
+ "end": "^\\s*(})",
+ "endCaptures": {
+ "1": {
+ "name": "punctuation.section.embedded.ocaml.end.ocamllex"
+ }
+ },
+ "name": "meta.embedded.ocaml",
+ "patterns": [
+ {
+ "include": "source.ocaml"
+ }
+ ]
+ },
+ {
+ "begin": "\\b(let)\\s+([a-z][a-zA-Z0-9'_]*)\\s+=",
+ "beginCaptures": {
+ "1": {
+ "name": "keyword.other.pattern-definition.ocamllex"
+ },
+ "2": {
+ "name": "entity.name.type.pattern.stupid-goddamn-hack.ocamllex"
+ }
+ },
+ "end": "^(?:\\s*let)|(?:\\s*(rule|$))",
+ "name": "meta.pattern-definition.ocaml",
+ "patterns": [
+ {
+ "include": "#match-patterns"
+ }
+ ]
+ },
+ {
+ "begin": "(rule|and)\\s+([a-z][a-zA-Z0-9_]*)\\s+(=)\\s+(parse)(?=\\s*$)|((?",
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.type-declaration.end.ocamlyacc"
+ }
+ },
+ "name": "meta.token.type-declaration.ocamlyacc",
+ "patterns": [
+ {
+ "include": "source.ocaml"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/src/Resources/Grammars/swift.json b/src/Resources/Grammars/swift.json
new file mode 100644
index 0000000000..c5629f30a7
--- /dev/null
+++ b/src/Resources/Grammars/swift.json
@@ -0,0 +1,3253 @@
+{
+ "comment": "See swift.tmbundle/grammar-test.swift for test cases.",
+ "fileTypes": ["swift"],
+ "firstLineMatch": "^#!/.*\\bswift",
+ "keyEquivalent": "^~S",
+ "scopeName": "source.swift",
+ "uuid": "8C743E89-9C8D-4833-8149-6500D82AA463",
+ "name": "Swift",
+ "patterns": [{ "include": "#root" }],
+
+ "repository": {
+ "async-throws": {
+ "match": "\\b(?:(throws\\s+async|rethrows\\s+async)|(throws|rethrows)|(async))\\b",
+ "captures": {
+ "1": { "name": "invalid.illegal.await-must-precede-throws.swift" },
+ "2": { "name": "storage.modifier.exception.swift" },
+ "3": { "name": "storage.modifier.async.swift" }
+ }
+ },
+
+ "attributes": {
+ "patterns": [
+ {
+ "name": "meta.attribute.available.swift",
+ "begin": "((@)available)(\\()",
+ "end": "\\)",
+ "beginCaptures": {
+ "1": { "name": "storage.modifier.attribute.swift" },
+ "2": { "name": "punctuation.definition.attribute.swift" },
+ "3": { "name": "punctuation.definition.arguments.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.arguments.end.swift" }
+ },
+ "patterns": [
+ {
+ "match": "\\b(swift|(?:iOS|macOS|OSX|watchOS|tvOS|visionOS|UIKitForMac)(?:ApplicationExtension)?)\\b(?:\\s+([0-9]+(?:\\.[0-9]+)*\\b))?",
+ "captures": {
+ "1": { "name": "keyword.other.platform.os.swift" },
+ "2": { "name": "constant.numeric.swift" }
+ }
+ },
+
+ {
+ "begin": "\\b(introduced|deprecated|obsoleted)\\s*(:)\\s*",
+ "end": "(?!\\G)",
+ "beginCaptures": {
+ "1": { "name": "keyword.other.swift" },
+ "2": { "name": "punctuation.separator.key-value.swift" }
+ },
+ "patterns": [
+ {
+ "name": "constant.numeric.swift",
+ "match": "\\b[0-9]+(?:\\.[0-9]+)*\\b"
+ }
+ ]
+ },
+
+ {
+ "begin": "\\b(message|renamed)\\s*(:)\\s*(?=\")",
+ "end": "(?!\\G)",
+ "beginCaptures": {
+ "1": { "name": "keyword.other.swift" },
+ "2": { "name": "punctuation.separator.key-value.swift" }
+ },
+ "patterns": [{ "include": "#literals" }]
+ },
+
+ {
+ "match": "(?:(\\*)|\\b(deprecated|unavailable|noasync)\\b)\\s*(.*?)(?=[,)])",
+ "captures": {
+ "1": { "name": "keyword.other.platform.all.swift" },
+ "2": { "name": "keyword.other.swift" },
+ "3": { "name": "invalid.illegal.character-not-allowed-here.swift" }
+ }
+ }
+ ]
+ },
+
+ {
+ "name": "meta.attribute.objc.swift",
+ "begin": "((@)objc)(\\()",
+ "end": "\\)",
+ "beginCaptures": {
+ "1": { "name": "storage.modifier.attribute.swift" },
+ "2": { "name": "punctuation.definition.attribute.swift" },
+ "3": { "name": "punctuation.definition.arguments.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.arguments.end.swift" }
+ },
+ "patterns": [
+ {
+ "name": "entity.name.function.swift",
+ "match": "\\w*(?::(?:\\w*:)*(\\w*))?",
+ "captures": {
+ "1": { "name": "invalid.illegal.missing-colon-after-selector-piece.swift" }
+ }
+ }
+ ]
+ },
+
+ {
+ "comment": "any other attribute",
+ "name": "meta.attribute.swift",
+ "begin": "(@)(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)",
+ "end": "(?!\\G\\()",
+ "beginCaptures": {
+ "0": { "name": "storage.modifier.attribute.swift" },
+ "1": { "name": "punctuation.definition.attribute.swift" },
+ "2": { "name": "punctuation.definition.identifier.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ {
+ "name": "meta.arguments.attribute.swift",
+ "begin": "\\(",
+ "end": "\\)",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.arguments.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.arguments.end.swift" }
+ },
+ "patterns": [{ "include": "#expressions" }]
+ }
+ ]
+ }
+ ]
+ },
+
+ "builtin-functions": {
+ "patterns": [
+ {
+ "comment": "Member functions in the standard library in Swift 3 which may be used with trailing closures and no parentheses",
+ "name": "support.function.swift",
+ "match": "(?<=\\.)(?:s(?:ort(?:ed)?|plit)|contains|index|partition|f(?:i(?:lter|rst)|orEach|latMap)|with(?:MutableCharacters|CString|U(?:nsafe(?:Mutable(?:BufferPointer|Pointer(?:s|To(?:Header|Elements)))|BufferPointer)|TF8Buffer))|m(?:in|a(?:p|x)))(?=\\s*[({])\\b"
+ },
+
+ {
+ "comment": "Member functions in the standard library in Swift 3",
+ "name": "support.function.swift",
+ "match": "(?<=\\.)(?:s(?:ymmetricDifference|t(?:oreBytes|arts|ride)|ortInPlace|u(?:ccessor|ffix|btract(?:ing|InPlace|WithOverflow)?)|quareRoot|amePosition)|h(?:oldsUnique(?:Reference|OrPinnedReference)|as(?:Suffix|Prefix))|ne(?:gate(?:d)?|xt)|c(?:o(?:untByEnumerating|py(?:Bytes)?)|lamp(?:ed)?|reate)|t(?:o(?:IntMax|Opaque|UIntMax)|ake(?:RetainedValue|UnretainedValue)|r(?:uncatingRemainder|a(?:nscodedLength|ilSurrogate)))|i(?:s(?:MutableAndUniquelyReferenced(?:OrPinned)?|S(?:trictSu(?:perset(?:Of)?|bset(?:Of)?)|u(?:perset(?:Of)?|bset(?:Of)?))|Continuation|T(?:otallyOrdered|railSurrogate)|Disjoint(?:With)?|Unique(?:Reference|lyReferenced(?:OrPinned)?)|Equal|Le(?:ss(?:ThanOrEqualTo)?|adSurrogate))|n(?:sert(?:ContentsOf)?|tersect(?:ion|InPlace)?|itialize(?:Memory|From)?|dex(?:Of|ForKey)))|o(?:verlaps|bjectAt)|d(?:i(?:stance(?:To)?|vide(?:d|WithOverflow)?)|e(?:s(?:cendant|troy)|code(?:CString)?|initialize|alloc(?:ate(?:Capacity)?)?)|rop(?:First|Last))|u(?:n(?:ion(?:InPlace)?|derestimateCount|wrappedOrError)|p(?:date(?:Value)?|percased))|join(?:ed|WithSeparator)|p(?:op(?:First|Last)|ass(?:Retained|Unretained)|re(?:decessor|fix))|e(?:scape(?:d)?|n(?:code|umerate(?:d)?)|lementsEqual|xclusiveOr(?:InPlace)?)|f(?:orm(?:Remainder|S(?:ymmetricDifference|quareRoot)|TruncatingRemainder|In(?:tersection|dex)|Union)|latten|rom(?:CString(?:RepairingIllFormedUTF8)?|Opaque))|w(?:i(?:thMemoryRebound|dth)|rite(?:To)?)|l(?:o(?:wercased|ad)|e(?:adSurrogate|xicographical(?:Compare|lyPrecedes)))|a(?:ss(?:ign(?:BackwardFrom|From)?|umingMemoryBound)|d(?:d(?:ing(?:Product)?|Product|WithOverflow)?|vanced(?:By)?)|utorelease|ppend(?:ContentsOf)?|lloc(?:ate)?|bs)|r(?:ound(?:ed)?|e(?:serveCapacity|tain|duce|place(?:Range|Subrange)?|verse(?:d)?|quest(?:NativeBuffer|UniqueMutableBackingBuffer)|lease|m(?:ove(?:Range|Subrange|Value(?:ForKey)?|First|Last|A(?:tIndex|ll))?|ainder(?:WithOverflow)?)))|ge(?:nerate|t(?:Objects|Element))|m(?:in(?:imum(?:Magnitude)?|Element)|ove(?:Initialize(?:Memory|BackwardFrom|From)?|Assign(?:From)?)?|ultipl(?:y(?:WithOverflow)?|ied)|easure|a(?:ke(?:Iterator|Description)|x(?:imum(?:Magnitude)?|Element)))|bindMemory)(?=\\s*\\()"
+ },
+
+ {
+ "comment": "Member functions in the standard library in Swift 2 only",
+ "name": "support.function.swift",
+ "match": "(?<=\\.)(?:s(?:uperclassMirror|amePositionIn|tartsWith)|nextObject|c(?:haracterAtIndex|o(?:untByEnumeratingWithState|pyWithZone)|ustom(?:Mirror|PlaygroundQuickLook))|is(?:EmptyInput|ASCII)|object(?:Enumerator|ForKey|AtIndex)|join|put|keyEnumerator|withUnsafeMutablePointerToValue|length|getMirror|m(?:oveInitializeAssignFrom|ember))(?=\\s*\\()"
+ }
+ ]
+ },
+
+ "builtin-global-functions": {
+ "patterns": [
+ {
+ "begin": "\\b(type)(\\()\\s*(of)(:)",
+ "end": "\\)",
+ "beginCaptures": {
+ "1": { "name": "support.function.dynamic-type.swift" },
+ "2": { "name": "punctuation.definition.arguments.begin.swift" },
+ "3": { "name": "support.variable.parameter.swift" },
+ "4": { "name": "punctuation.separator.argument-label.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.arguments.end.swift" }
+ },
+ "patterns": [{ "include": "#expressions" }]
+ },
+
+ {
+ "comment": "Global functions available in Swift 3 which may be used with trailing closures and no parentheses",
+ "name": "support.function.swift",
+ "match": "\\b(?:anyGenerator|autoreleasepool)(?=\\s*[({])\\b"
+ },
+
+ {
+ "comment": "Global functions available in Swift 3",
+ "name": "support.function.swift",
+ "match": "\\b(?:s(?:tride(?:of(?:Value)?)?|izeof(?:Value)?|equence|wap)|numericCast|transcode|is(?:UniquelyReferenced(?:NonObjC)?|KnownUniquelyReferenced)|zip|d(?:ump|ebugPrint)|unsafe(?:BitCast|Downcast|Unwrap|Address(?:Of)?)|pr(?:int|econdition(?:Failure)?)|fatalError|with(?:Unsafe(?:MutablePointer|Pointer)|ExtendedLifetime|VaList)|a(?:ssert(?:ionFailure)?|lignof(?:Value)?|bs)|re(?:peatElement|adLine)|getVaList|m(?:in|ax))(?=\\s*\\()"
+ },
+
+ {
+ "comment": "Global functions available in Swift 2 only",
+ "name": "support.function.swift",
+ "match": "\\b(?:s(?:ort|uffix|pli(?:ce|t))|insert|overlaps|d(?:istance|rop(?:First|Last))|join|prefix|extend|withUnsafe(?:MutablePointers|Pointers)|lazy|advance|re(?:flect|move(?:Range|Last|A(?:tIndex|ll))))(?=\\s*\\()"
+ }
+ ]
+ },
+
+ "builtin-properties": {
+ "patterns": [
+ {
+ "comment": "The simpler (?<=\\bProcess\\.|\\bCommandLine\\.) breaks VS Code / Atom, see https://github.com/textmate/swift.tmbundle/issues/29",
+ "name": "support.variable.swift",
+ "match": "(?<=^Process\\.|\\WProcess\\.|^CommandLine\\.|\\WCommandLine\\.)(arguments|argc|unsafeArgv)"
+ },
+
+ {
+ "comment": "Properties in the standard library in Swift 3",
+ "name": "support.variable.swift",
+ "match": "(?<=\\.)(?:s(?:t(?:artIndex|ri(?:ngValue|de))|i(?:ze|gn(?:BitIndex|ificand(?:Bit(?:Count|Pattern)|Width)?|alingNaN)?)|u(?:perclassMirror|mmary|bscriptBaseAddress))|h(?:eader|as(?:hValue|PointerRepresentation))|n(?:ulTerminatedUTF8|ext(?:Down|Up)|a(?:n|tiveOwner))|c(?:haracters|ount(?:TrailingZeros)?|ustom(?:Mirror|PlaygroundQuickLook)|apacity)|i(?:s(?:S(?:ign(?:Minus|aling(?:NaN)?)|ubnormal)|N(?:ormal|aN)|Canonical|Infinite|Zero|Empty|Finite|ASCII)|n(?:dices|finity)|dentity)|owner|de(?:scription|bugDescription)|u(?:n(?:safelyUnwrapped|icodeScalar(?:s)?|derestimatedCount)|tf(?:16|8(?:Start|C(?:String|odeUnitCount))?)|intValue|ppercaseString|lp(?:OfOne)?)|p(?:i|ointee)|e(?:ndIndex|lements|xponent(?:Bit(?:Count|Pattern))?)|value(?:s)?|keys|quietNaN|f(?:irst(?:ElementAddress(?:IfContiguous)?)?|loatingPointClass)|l(?:ittleEndian|owercaseString|eastNo(?:nzeroMagnitude|rmalMagnitude)|a(?:st|zy))|a(?:l(?:ignment|l(?:ocatedElementCount|Zeros))|rray(?:PropertyIsNativeTypeChecked)?)|ra(?:dix|wValue)|greatestFiniteMagnitude|m(?:in|emory|ax)|b(?:yteS(?:ize|wapped)|i(?:nade|tPattern|gEndian)|uffer|ase(?:Address)?))\\b"
+ },
+
+ {
+ "comment": "Properties in the standard library in Swift 2 only",
+ "name": "support.variable.swift",
+ "match": "(?<=\\.)(?:boolValue|disposition|end|objectIdentifier|quickLookObject|start|valueType)\\b"
+ },
+
+ {
+ "comment": "Enum cases in the standard library - note that there is some overlap between these and the properties",
+ "name": "support.variable.swift",
+ "match": "(?<=\\.)(?:s(?:calarValue|i(?:ze|gnalingNaN)|o(?:und|me)|uppressed|prite|et)|n(?:one|egative(?:Subnormal|Normal|Infinity|Zero))|c(?:ol(?:or|lection)|ustomized)|t(?:o(?:NearestOr(?:Even|AwayFromZero)|wardZero)|uple|ext)|i(?:nt|mage)|optional|d(?:ictionary|o(?:uble|wn))|u(?:Int|p|rl)|p(?:o(?:sitive(?:Subnormal|Normal|Infinity|Zero)|int)|lus)|e(?:rror|mptyInput)|view|quietNaN|float|a(?:ttributedString|wayFromZero)|r(?:ectangle|ange)|generated|minus|b(?:ool|ezierPath))\\b"
+ }
+ ]
+ },
+
+ "builtin-types": {
+ "comment": "Types provided in the standard library",
+ "patterns": [
+ { "include": "#builtin-types-builtin-class-type" },
+ { "include": "#builtin-types-builtin-enum-type" },
+ { "include": "#builtin-types-builtin-protocol-type" },
+ { "include": "#builtin-types-builtin-struct-type" },
+ { "include": "#builtin-types-builtin-typealias" },
+
+ {
+ "name": "support.type.any.swift",
+ "match": "\\bAny\\b"
+ }
+ ]
+ },
+
+ "builtin-types-builtin-class-type": {
+ "comment": "Builtin class types",
+ "name": "support.class.swift",
+ "match": "\\b(Managed(Buffer|ProtoBuffer)|NonObjectiveCBase|AnyGenerator)\\b"
+ },
+
+ "builtin-types-builtin-enum-type": {
+ "patterns": [
+ {
+ "comment": "CommandLine is an enum, but it acts like a constant",
+ "name": "support.constant.swift",
+ "match": "\\b(?:CommandLine|Process(?=\\.))\\b"
+ },
+
+ {
+ "comment": "The return type of a function that never returns",
+ "name": "support.constant.never.swift",
+ "match": "\\bNever\\b"
+ },
+
+ {
+ "comment": "Enum types in the standard library in Swift 3",
+ "name": "support.type.swift",
+ "match": "\\b(?:ImplicitlyUnwrappedOptional|Representation|MemoryLayout|FloatingPointClassification|SetIndexRepresentation|SetIteratorRepresentation|FloatingPointRoundingRule|UnicodeDecodingResult|Optional|DictionaryIndexRepresentation|AncestorRepresentation|DisplayStyle|PlaygroundQuickLook|Never|FloatingPointSign|Bit|DictionaryIteratorRepresentation)\\b"
+ },
+
+ {
+ "comment": "Enum types in the standard library in Swift 2 only",
+ "name": "support.type.swift",
+ "match": "\\b(?:MirrorDisposition|QuickLookObject)\\b"
+ }
+ ]
+ },
+
+ "builtin-types-builtin-protocol-type": {
+ "patterns": [
+ {
+ "name": "support.type.swift",
+ "match": "\\bActor\\b"
+ },
+
+ {
+ "comment": "Protocols in the standard library in Swift 3",
+ "name": "support.type.swift",
+ "match": "\\b(?:Ra(?:n(?:domAccess(?:Collection|Indexable)|geReplaceable(?:Collection|Indexable))|wRepresentable)|M(?:irrorPath|utable(?:Collection|Indexable))|Bi(?:naryFloatingPoint|twiseOperations|directional(?:Collection|Indexable))|S(?:tr(?:ideable|eamable)|igned(?:Number|Integer)|e(?:tAlgebra|quence))|Hashable|C(?:o(?:llection|mparable)|ustom(?:Reflectable|StringConvertible|DebugStringConvertible|PlaygroundQuickLookable|LeafReflectable)|VarArg)|TextOutputStream|I(?:n(?:teger(?:Arithmetic)?|dexable(?:Base)?)|teratorProtocol)|OptionSet|Un(?:signedInteger|icodeCodec)|E(?:quatable|rror|xpressibleBy(?:BooleanLiteral|String(?:Interpolation|Literal)|NilLiteral|IntegerLiteral|DictionaryLiteral|UnicodeScalarLiteral|ExtendedGraphemeClusterLiteral|FloatLiteral|ArrayLiteral))|FloatingPoint|L(?:osslessStringConvertible|azy(?:SequenceProtocol|CollectionProtocol))|A(?:nyObject|bsoluteValuable))\\b"
+ },
+
+ {
+ "comment": "Protocols in the standard library in Swift 2 only",
+ "name": "support.type.swift",
+ "match": "\\b(?:Ran(?:domAccessIndexType|geReplaceableCollectionType)|GeneratorType|M(?:irror(?:Type|PathType)|utable(?:Sliceable|CollectionType))|B(?:i(?:twiseOperationsType|directionalIndexType)|oolean(?:Type|LiteralConvertible))|S(?:tring(?:InterpolationConvertible|LiteralConvertible)|i(?:nkType|gned(?:NumberType|IntegerType))|e(?:tAlgebraType|quenceType)|liceable)|NilLiteralConvertible|C(?:ollectionType|VarArgType)|Inte(?:rvalType|ger(?:Type|LiteralConvertible|ArithmeticType))|O(?:utputStreamType|ptionSetType)|DictionaryLiteralConvertible|Un(?:signedIntegerType|icode(?:ScalarLiteralConvertible|CodecType))|E(?:rrorType|xten(?:sibleCollectionType|dedGraphemeClusterLiteralConvertible))|F(?:orwardIndexType|loat(?:ingPointType|LiteralConvertible))|A(?:nyCollectionType|rrayLiteralConvertible))\\b"
+ }
+ ]
+ },
+
+ "builtin-types-builtin-struct-type": {
+ "patterns": [
+ {
+ "comment": "Structs in the standard library in Swift 3",
+ "name": "support.type.swift",
+ "match": "\\b(?:R(?:e(?:peat(?:ed)?|versed(?:RandomAccess(?:Collection|Index)|Collection|Index))|an(?:domAccessSlice|ge(?:Replaceable(?:RandomAccessSlice|BidirectionalSlice|Slice)|Generator)?))|Generator(?:Sequence|OfOne)|M(?:irror|utable(?:Ran(?:domAccessSlice|geReplaceable(?:RandomAccessSlice|BidirectionalSlice|Slice))|BidirectionalSlice|Slice)|anagedBufferPointer)|B(?:idirectionalSlice|ool)|S(?:t(?:aticString|ri(?:ng|deT(?:hrough(?:Generator|Iterator)?|o(?:Generator|Iterator)?)))|et(?:I(?:ndex|terator))?|lice)|HalfOpenInterval|C(?:haracter(?:View)?|o(?:ntiguousArray|untable(?:Range|ClosedRange)|llectionOfOne)|OpaquePointer|losed(?:Range(?:I(?:ndex|terator))?|Interval)|VaListPointer)|I(?:n(?:t(?:16|8|32|64)?|d(?:ices|ex(?:ing(?:Generator|Iterator))?))|terator(?:Sequence|OverOne)?)|Zip2(?:Sequence|Iterator)|O(?:paquePointer|bjectIdentifier)|D(?:ictionary(?:I(?:ndex|terator)|Literal)?|ouble|efault(?:RandomAccessIndices|BidirectionalIndices|Indices))|U(?:n(?:safe(?:RawPointer|Mutable(?:RawPointer|BufferPointer|Pointer)|BufferPointer(?:Generator|Iterator)?|Pointer)|icodeScalar(?:View)?|foldSequence|managed)|TF(?:16(?:View)?|8(?:View)?|32)|Int(?:16|8|32|64)?)|Join(?:Generator|ed(?:Sequence|Iterator))|PermutationGenerator|E(?:numerate(?:Generator|Sequence|d(?:Sequence|Iterator))|mpty(?:Generator|Collection|Iterator))|Fl(?:oat(?:80)?|atten(?:Generator|BidirectionalCollection(?:Index)?|Sequence|Collection(?:Index)?|Iterator))|L(?:egacyChildren|azy(?:RandomAccessCollection|Map(?:RandomAccessCollection|Generator|BidirectionalCollection|Sequence|Collection|Iterator)|BidirectionalCollection|Sequence|Collection|Filter(?:Generator|BidirectionalCollection|Sequence|Collection|I(?:ndex|terator))))|A(?:ny(?:RandomAccessCollection|Generator|BidirectionalCollection|Sequence|Hashable|Collection|I(?:ndex|terator))|utoreleasingUnsafeMutablePointer|rray(?:Slice)?))\\b"
+ },
+
+ {
+ "comment": "Structs in the standard library in Swift 2 only",
+ "name": "support.type.swift",
+ "match": "\\b(?:R(?:everse(?:RandomAccess(?:Collection|Index)|Collection|Index)|awByte)|Map(?:Generator|Sequence|Collection)|S(?:inkOf|etGenerator)|Zip2Generator|DictionaryGenerator|Filter(?:Generator|Sequence|Collection(?:Index)?)|LazyForwardCollection|Any(?:RandomAccessIndex|BidirectionalIndex|Forward(?:Collection|Index)))\\b"
+ }
+ ]
+ },
+
+ "builtin-types-builtin-typealias": {
+ "patterns": [
+ {
+ "comment": "Typealiases in the standard library in Swift 3",
+ "name": "support.type.swift",
+ "match": "\\b(?:Raw(?:Significand|Exponent|Value)|B(?:ooleanLiteralType|uffer|ase)|S(?:t(?:orage|r(?:i(?:ngLiteralType|de)|eam(?:1|2)))|ubSequence)|NativeBuffer|C(?:hild(?:ren)?|Bool|S(?:hort|ignedChar)|odeUnit|Char(?:16|32)?|Int|Double|Unsigned(?:Short|Char|Int|Long(?:Long)?)|Float|WideChar|Long(?:Long)?)|I(?:n(?:t(?:Max|egerLiteralType)|d(?:ices|ex(?:Distance)?))|terator)|Distance|U(?:n(?:icodeScalar(?:Type|Index|View|LiteralType)|foldFirstSequence)|TF(?:16(?:Index|View)|8Index)|IntMax)|E(?:lement(?:s)?|x(?:tendedGraphemeCluster(?:Type|LiteralType)|ponent))|V(?:oid|alue)|Key|Float(?:32|LiteralType|64)|AnyClass)\\b"
+ },
+
+ {
+ "comment": "Typealiases in the standard library in Swift 2 only",
+ "name": "support.type.swift",
+ "match": "\\b(?:Generator|PlaygroundQuickLook|UWord|Word)\\b"
+ }
+ ]
+ },
+
+ "code-block": {
+ "begin": "\\{",
+ "end": "\\}",
+ "beginCaptures": {
+ "0": { "name": "punctuation.section.scope.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.section.scope.end.swift" }
+ },
+ "patterns": [{ "include": "$self" }]
+ },
+
+ "comments": {
+ "patterns": [
+ {
+ "name": "comment.line.number-sign.swift",
+ "match": "\\A^(#!).*$\\n?",
+ "captures": {
+ "1": { "name": "punctuation.definition.comment.swift" }
+ }
+ },
+
+ {
+ "name": "comment.block.documentation.swift",
+ "begin": "/\\*\\*(?!/)",
+ "end": "\\*/",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.comment.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.comment.end.swift" }
+ },
+ "patterns": [{ "include": "#comments-nested" }]
+ },
+
+ {
+ "name": "comment.block.documentation.playground.swift",
+ "begin": "/\\*:",
+ "end": "\\*/",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.comment.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.comment.end.swift" }
+ },
+ "patterns": [{ "include": "#comments-nested" }]
+ },
+
+ {
+ "name": "comment.block.swift",
+ "begin": "/\\*",
+ "end": "\\*/",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.comment.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.comment.end.swift" }
+ },
+ "patterns": [{ "include": "#comments-nested" }]
+ },
+
+ {
+ "name": "invalid.illegal.unexpected-end-of-block-comment.swift",
+ "match": "\\*/"
+ },
+
+ {
+ "begin": "(^[ \\t]+)?(?=//)",
+ "end": "(?!\\G)",
+ "beginCaptures": {
+ "1": { "name": "punctuation.whitespace.comment.leading.swift" }
+ },
+ "patterns": [
+ {
+ "name": "comment.line.triple-slash.documentation.swift",
+ "begin": "///",
+ "end": "$",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.comment.swift" }
+ }
+ },
+
+ {
+ "name": "comment.line.double-slash.documentation.swift",
+ "begin": "//:",
+ "end": "$",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.comment.swift" }
+ }
+ },
+
+ {
+ "name": "comment.line.double-slash.swift",
+ "begin": "//",
+ "end": "$",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.comment.swift" }
+ }
+ }
+ ]
+ }
+ ]
+ },
+
+ "comments-nested": {
+ "begin": "/\\*",
+ "end": "\\*/",
+ "patterns": [{ "include": "#comments-nested" }]
+ },
+
+ "compiler-control": {
+ "patterns": [
+ {
+ "contentName": "comment.block.preprocessor.swift",
+ "begin": "^\\s*(#)(if|elseif)\\s+(false)\\b.*?(?=$|//|/\\*)",
+ "end": "(?=^\\s*(#(elseif|else|endif)\\b))",
+ "beginCaptures": {
+ "0": { "name": "meta.preprocessor.conditional.swift" },
+ "1": { "name": "punctuation.definition.preprocessor.swift" },
+ "2": { "name": "keyword.control.import.preprocessor.conditional.swift" },
+ "3": { "name": "constant.language.boolean.swift" }
+ }
+ },
+
+ {
+ "name": "meta.preprocessor.conditional.swift",
+ "captures": {
+ "1": { "name": "punctuation.definition.preprocessor.swift" },
+ "2": { "name": "keyword.control.import.preprocessor.conditional.swift" }
+ },
+ "begin": "^\\s*(#)(if|elseif)\\s+",
+ "end": "(?=\\s*(?://|/\\*))|$",
+ "patterns": [
+ {
+ "name": "keyword.operator.logical.swift",
+ "match": "(&&|\\|\\|)"
+ },
+
+ {
+ "name": "constant.language.boolean.swift",
+ "match": "\\b(true|false)\\b"
+ },
+
+ {
+ "match": "\\b(arch)\\s*(\\()\\s*(?:(arm|arm64|powerpc64|powerpc64le|i386|x86_64|s390x)|\\w+)\\s*(\\))",
+ "captures": {
+ "1": { "name": "keyword.other.condition.swift" },
+ "2": { "name": "punctuation.definition.parameters.begin.swift" },
+ "3": { "name": "support.constant.platform.architecture.swift" },
+ "4": { "name": "punctuation.definition.parameters.end.swift" }
+ }
+ },
+
+ {
+ "match": "\\b(os)\\s*(\\()\\s*(?:(macOS|OSX|iOS|tvOS|watchOS|visionOS|Android|Linux|FreeBSD|Windows|PS4)|\\w+)\\s*(\\))",
+ "captures": {
+ "1": { "name": "keyword.other.condition.swift" },
+ "2": { "name": "punctuation.definition.parameters.begin.swift" },
+ "3": { "name": "support.constant.platform.os.swift" },
+ "4": { "name": "punctuation.definition.parameters.end.swift" }
+ }
+ },
+
+ {
+ "match": "\\b(canImport)\\s*(\\()([\\p{L}_][\\p{L}_\\p{N}\\p{M}]*)(\\))",
+ "captures": {
+ "1": { "name": "keyword.other.condition.swift" },
+ "2": { "name": "punctuation.definition.parameters.begin.swift" },
+ "3": { "name": "entity.name.type.module.swift" },
+ "4": { "name": "punctuation.definition.parameters.end.swift" }
+ }
+ },
+
+ {
+ "begin": "\\b(targetEnvironment)\\s*(\\()",
+ "end": "(\\))|$",
+ "beginCaptures": {
+ "1": { "name": "keyword.other.condition.swift" },
+ "2": { "name": "punctuation.definition.parameters.begin.swift" }
+ },
+ "endCaptures": {
+ "1": { "name": "punctuation.definition.parameters.end.swift" }
+ },
+ "patterns": [
+ {
+ "name": "support.constant.platform.environment.swift",
+ "match": "\\b(simulator|UIKitForMac)\\b"
+ }
+ ]
+ },
+
+ {
+ "begin": "\\b(swift|compiler)\\s*(\\()",
+ "end": "(\\))|$",
+ "beginCaptures": {
+ "1": { "name": "keyword.other.condition.swift" },
+ "2": { "name": "punctuation.definition.parameters.begin.swift" }
+ },
+ "endCaptures": {
+ "1": { "name": "punctuation.definition.parameters.end.swift" }
+ },
+ "patterns": [
+ {
+ "name": "keyword.operator.comparison.swift",
+ "match": ">=|<"
+ },
+
+ {
+ "name": "constant.numeric.swift",
+ "match": "\\b[0-9]+(?:\\.[0-9]+)*\\b"
+ }
+ ]
+ }
+ ]
+ },
+
+ {
+ "name": "meta.preprocessor.conditional.swift",
+ "match": "^\\s*(#)(else|endif)(.*?)(?=$|//|/\\*)",
+ "captures": {
+ "1": { "name": "punctuation.definition.preprocessor.swift" },
+ "2": { "name": "keyword.control.import.preprocessor.conditional.swift" },
+ "3": {
+ "patterns": [
+ {
+ "name": "invalid.illegal.character-not-allowed-here.swift",
+ "match": "\\S+"
+ }
+ ]
+ }
+ }
+ },
+
+ {
+ "name": "meta.preprocessor.sourcelocation.swift",
+ "match": "^\\s*(#)(sourceLocation)((\\()([^)]*)(\\)))(.*?)(?=$|//|/\\*)",
+ "captures": {
+ "1": { "name": "punctuation.definition.preprocessor.swift" },
+ "2": { "name": "keyword.control.import.preprocessor.sourcelocation.swift" },
+ "4": { "name": "punctuation.definition.parameters.begin.swift" },
+ "5": {
+ "patterns": [
+ {
+ "begin": "(file)\\s*(:)\\s*(?=\")",
+ "end": "(?!\\G)",
+ "beginCaptures": {
+ "1": { "name": "support.variable.parameter.swift" },
+ "2": { "name": "punctuation.separator.key-value.swift" }
+ },
+ "patterns": [{ "include": "#literals" }]
+ },
+
+ {
+ "match": "(line)\\s*(:)\\s*([0-9]+)",
+ "captures": {
+ "1": { "name": "support.variable.parameter.swift" },
+ "2": { "name": "punctuation.separator.key-value.swift" },
+ "3": { "name": "constant.numeric.integer.swift" }
+ }
+ },
+
+ {
+ "name": "punctuation.separator.parameters.swift",
+ "match": ","
+ },
+
+ {
+ "name": "invalid.illegal.character-not-allowed-here.swift",
+ "match": "\\S+"
+ }
+ ]
+ },
+ "6": { "name": "punctuation.definition.parameters.begin.swift" },
+ "7": {
+ "patterns": [
+ {
+ "name": "invalid.illegal.character-not-allowed-here.swift",
+ "match": "\\S+"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+
+ "conditionals": {
+ "patterns": [
+ {
+ "begin": "(?&|\\^~.])(->)(?![/=\\-+!*%<>&|\\^~.])",
+ "captures": {
+ "1": { "name": "keyword.operator.type.function.swift" }
+ }
+ },
+
+ {
+ "comment": "Swift 3: A & B",
+ "match": "(?&|\\^~.])(&)(?![/=\\-+!*%<>&|\\^~.])",
+ "captures": {
+ "1": { "name": "keyword.operator.type.composition.swift" }
+ }
+ },
+
+ {
+ "name": "keyword.operator.type.optional.swift",
+ "match": "[?!]"
+ },
+
+ {
+ "name": "keyword.operator.function.variadic-parameter.swift",
+ "match": "\\.\\.\\."
+ },
+
+ {
+ "comment": "Swift 2: protocol",
+ "name": "keyword.other.type.composition.swift",
+ "match": "\\bprotocol\\b"
+ },
+
+ {
+ "name": "keyword.other.type.metatype.swift",
+ "match": "(?<=\\.)(?:Protocol|Type)\\b"
+ },
+
+ { "include": "#declarations-available-types-tuple-type" },
+ { "include": "#declarations-available-types-collection-type" },
+ { "include": "#declarations-generic-argument-clause" }
+ ]
+ },
+
+ "declarations-available-types-collection-type": {
+ "comment": "array and dictionary types [Value] and [Key: Value]",
+ "begin": "\\[",
+ "end": "\\]|(?=[>){}])",
+ "beginCaptures": {
+ "0": { "name": "punctuation.section.collection-type.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.section.collection-type.end.swift" }
+ },
+ "patterns": [
+ { "include": "#declarations-available-types" },
+ { "include": "#literals-numeric" },
+
+ {
+ "name": "support.variable.inferred.swift",
+ "match": "\\b_\\b"
+ },
+
+ {
+ "name": "keyword.other.inline-array.swift",
+ "match": "(?<=\\s)\\bof\\b(?=\\s+[\\p{L}_\\d\\p{N}\\p{M}\\[(])"
+ },
+
+ {
+ "begin": ":",
+ "end": "(?=\\]|[>){}])",
+ "beginCaptures": {
+ "0": { "name": "punctuation.separator.key-value.swift" }
+ },
+ "patterns": [
+ {
+ "name": "invalid.illegal.extra-colon-in-dictionary-type.swift",
+ "match": ":"
+ },
+ { "include": "#declarations-available-types" }
+ ]
+ }
+ ]
+ },
+
+ "declarations-available-types-tuple-type": {
+ "begin": "\\(",
+ "end": "\\)|(?=[>\\]{}])",
+ "beginCaptures": {
+ "0": { "name": "punctuation.section.tuple-type.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.section.tuple-type.end.swift" }
+ },
+ "patterns": [{ "include": "#declarations-available-types" }]
+ },
+
+ "declarations-extension": {
+ "name": "meta.definition.type.$1.swift",
+ "begin": "\\b(extension)\\s+",
+ "end": "(?<=\\})",
+ "beginCaptures": {
+ "1": { "name": "storage.type.$1.swift" }
+ },
+ "patterns": [
+ {
+ "name": "entity.name.type.swift",
+ "begin": "\\G(?!\\s*[:{\\n])",
+ "end": "(?=\\s*[:{\\n])|(?!\\G)(?=\\s*where\\b)",
+ "patterns": [{ "include": "#declarations-available-types" }]
+ },
+ { "include": "#comments" },
+ {
+ "comment": "SE-0143: Conditional Conformances",
+ "include": "#declarations-generic-where-clause"
+ },
+ { "include": "#declarations-inheritance-clause" },
+
+ {
+ "name": "meta.definition.type.body.swift",
+ "begin": "\\{",
+ "end": "\\}",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.type.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.type.end.swift" }
+ },
+ "patterns": [{ "include": "$self" }]
+ }
+ ]
+ },
+
+ "declarations-function": {
+ "name": "meta.definition.function.swift",
+ "begin": "(?x)\n\\b\n(func)\n\\s+\n(\n (?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)\n | (?:\n (\n (? # operator-head\n [/=\\-+!*%<>&|^~?]\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n )\n (\n \\g\n | (? # operator-character\n [\\x{0300}-\\x{036F}]\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n )*\n )\n | ( \\. ( \\g | \\g | \\. )+ ) # Dot operators\n )\n)\n\\s*\n(?=\\(|<)",
+ "end": "(?<=\\})|$(?# functions in protocol declarations or generated interfaces have no body)",
+ "beginCaptures": {
+ "1": { "name": "storage.type.function.swift" },
+ "2": { "name": "entity.name.function.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-generic-parameter-clause" },
+ { "include": "#declarations-parameter-clause" },
+ { "include": "#declarations-function-result" },
+ { "include": "#async-throws" },
+ {
+ "comment": "Swift 3: generic constraints after the parameters and return type",
+ "include": "#declarations-generic-where-clause"
+ },
+
+ {
+ "name": "meta.definition.function.body.swift",
+ "begin": "(\\{)",
+ "end": "(\\})",
+ "beginCaptures": {
+ "1": { "name": "punctuation.section.function.begin.swift" }
+ },
+ "endCaptures": {
+ "1": { "name": "punctuation.section.function.end.swift" }
+ },
+ "patterns": [{ "include": "$self" }]
+ }
+ ]
+ },
+
+ "declarations-function-initializer": {
+ "name": "meta.definition.function.initializer.swift",
+ "begin": "(?&|\\^~.])(->)(?![/=\\-+!*%<>&|\\^~.])\\s*",
+ "end": "(?!\\G)(?=\\{|\\bwhere\\b|;|=)|$",
+ "beginCaptures": {
+ "1": { "name": "keyword.operator.function-result.swift" }
+ },
+ "patterns": [
+ {
+ "name": "storage.modifier.swift",
+ "match": "\\bsending\\b"
+ },
+ { "include": "#declarations-available-types" }
+ ]
+ },
+
+ "declarations-function-subscript": {
+ "name": "meta.definition.function.subscript.swift",
+ "begin": "(?|(?=[)\\]{}])",
+ "beginCaptures": {
+ "0": { "name": "punctuation.separator.generic-argument-clause.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.separator.generic-argument-clause.end.swift" }
+ },
+ "patterns": [
+ { "include": "#literals-numeric" },
+ { "include": "#declarations-available-types" }
+ ]
+ },
+
+ "declarations-generic-parameter-clause": {
+ "name": "meta.generic-parameter-clause.swift",
+ "begin": "<",
+ "end": ">|(?=[^\\w\\d:<>\\s,=&`])(?# characters besides these are never valid in a generic param list -- even if it's not really a valid clause, we should stop trying to parse it if we see one of them.)",
+ "beginCaptures": {
+ "0": { "name": "punctuation.separator.generic-parameter-clause.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.separator.generic-parameter-clause.end.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ {
+ "comment": "Swift 2: constraints inside the generic param list",
+ "include": "#declarations-generic-where-clause"
+ },
+
+ {
+ "name": "keyword.other.declaration-specifier.swift",
+ "match": "\\blet\\b"
+ },
+
+ {
+ "name": "keyword.control.loop.swift",
+ "match": "\\beach\\b"
+ },
+
+ {
+ "match": "\\b((?!\\d)\\w[\\w\\d]*)\\b",
+ "captures": {
+ "1": { "name": "variable.language.generic-parameter.swift" }
+ }
+ },
+
+ {
+ "name": "punctuation.separator.generic-parameters.swift",
+ "match": ","
+ },
+
+ {
+ "name": "meta.generic-parameter-constraint.swift",
+ "begin": "(:)\\s*",
+ "end": "(?=[,>]|(?!\\G)\\bwhere\\b)",
+ "beginCaptures": {
+ "1": { "name": "punctuation.separator.generic-parameter-constraint.swift" }
+ },
+ "patterns": [
+ {
+ "name": "entity.other.inherited-class.swift",
+ "begin": "\\G",
+ "end": "(?=[,>]|(?!\\G)\\bwhere\\b)",
+ "patterns": [
+ { "include": "#declarations-type-identifier" },
+ { "include": "#declarations-type-operators" }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+
+ "declarations-generic-where-clause": {
+ "name": "meta.generic-where-clause.swift",
+ "begin": "\\b(where)\\b\\s*",
+ "end": "(?!\\G)$|(?=[>{};\\n]|//|/\\*)",
+ "beginCaptures": {
+ "1": { "name": "keyword.other.generic-constraint-introducer.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-generic-where-clause-requirement-list" }
+ ]
+ },
+
+ "declarations-generic-where-clause-requirement-list": {
+ "begin": "\\G|,\\s*",
+ "end": "(?=[,>{};\\n]|//|/\\*)",
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#constraint" },
+ { "include": "#declarations-available-types" },
+
+ {
+ "name": "meta.generic-where-clause.same-type-requirement.swift",
+ "begin": "(?&|\\^~.])(==)(?![/=\\-+!*%<>&|\\^~.])",
+ "end": "(?=\\s*[,>{};\\n]|//|/\\*)",
+ "beginCaptures": {
+ "1": { "name": "keyword.operator.generic-constraint.same-type.swift" }
+ },
+ "patterns": [{ "include": "#declarations-available-types" }]
+ },
+
+ {
+ "name": "meta.generic-where-clause.conformance-requirement.swift",
+ "begin": "(?&|\\^~.])(:)(?![/=\\-+!*%<>&|\\^~.])",
+ "end": "(?=\\s*[,>{};\\n]|//|/\\*)",
+ "beginCaptures": {
+ "1": { "name": "keyword.operator.generic-constraint.conforms-to.swift" }
+ },
+ "patterns": [
+ {
+ "contentName": "entity.other.inherited-class.swift",
+ "begin": "\\G\\s*",
+ "end": "(?=\\s*[,>{};\\n]|//|/\\*)",
+ "patterns": [{ "include": "#declarations-available-types" }]
+ }
+ ]
+ }
+ ]
+ },
+
+ "declarations-import": {
+ "name": "meta.import.swift",
+ "begin": "(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)",
+ "captures": {
+ "1": { "name": "punctuation.definition.identifier.swift" },
+ "2": { "name": "punctuation.definition.identifier.swift" }
+ }
+ },
+
+ {
+ "name": "entity.name.type.swift",
+ "match": "(?x)\n(?<=\\G|\\.)\n\\$[0-9]+"
+ },
+
+ {
+ "name": "entity.name.type.swift",
+ "match": "(?x)\n(?<=\\G|\\.)\n(?:\n (\n (? # operator-head\n [/=\\-+!*%<>&|^~?]\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n )\n (\n \\g\n | (? # operator-character\n [\\x{0300}-\\x{036F}]\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n )*\n )\n | ( \\. ( \\g | \\g | \\. )+ ) # Dot operators\n)\n(?=\\.|;|$|//|/\\*|\\s)",
+ "captures": {
+ "1": {
+ "patterns": [
+ {
+ "name": "invalid.illegal.dot-not-allowed-here.swift",
+ "match": "\\."
+ }
+ ]
+ }
+ }
+ },
+
+ {
+ "name": "punctuation.separator.import.swift",
+ "match": "\\."
+ },
+
+ {
+ "name": "invalid.illegal.character-not-allowed-here.swift",
+ "begin": "(?!\\s*(;|$|//|/\\*))",
+ "end": "(?=\\s*(;|$|//|/\\*))"
+ }
+ ]
+ }
+ ]
+ },
+
+ "declarations-inheritance-clause": {
+ "name": "meta.inheritance-clause.swift",
+ "begin": "(:)(?=\\s*\\{)|(:)\\s*",
+ "end": "(?!\\G)$|(?=[={}]|(?!\\G)\\bwhere\\b)",
+ "beginCaptures": {
+ "1": { "name": "invalid.illegal.empty-inheritance-clause.swift" },
+ "2": { "name": "punctuation.separator.inheritance-clause.swift" }
+ },
+ "patterns": [
+ {
+ "begin": "\\bclass\\b",
+ "end": "(?=[={}]|(?!\\G)\\bwhere\\b)",
+ "beginCaptures": {
+ "0": { "name": "storage.type.class.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-inheritance-clause-more-types" }
+ ]
+ },
+
+ {
+ "begin": "\\G",
+ "end": "(?!\\G)$|(?=[={}]|(?!\\G)\\bwhere\\b)",
+ "patterns": [
+ { "include": "#attributes" },
+ { "include": "#comments" },
+ { "include": "#declarations-inheritance-clause-inherited-type" },
+ { "include": "#declarations-inheritance-clause-more-types" },
+ { "include": "#declarations-type-operators" }
+ ]
+ }
+ ]
+ },
+
+ "declarations-inheritance-clause-inherited-type": {
+ "name": "entity.other.inherited-class.swift",
+ "begin": "(?=[`\\p{L}_])",
+ "end": "(?!\\G)",
+ "patterns": [{ "include": "#declarations-type-identifier" }]
+ },
+
+ "declarations-inheritance-clause-more-types": {
+ "name": "meta.inheritance-list.more-types",
+ "begin": ",\\s*",
+ "end": "(?!\\G)(?!//|/\\*)|(?=[,={}]|(?!\\G)\\bwhere\\b)",
+ "patterns": [
+ { "include": "#attributes" },
+ { "include": "#comments" },
+ { "include": "#declarations-inheritance-clause-inherited-type" },
+ { "include": "#declarations-inheritance-clause-more-types" },
+ { "include": "#declarations-type-operators" }
+ ]
+ },
+
+ "declarations-macro": {
+ "name": "meta.definition.macro.swift",
+ "begin": "(?x)\n\\b\n(macro)\n\\s+\n((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\n\\s*\n(?=\\(|<|=)",
+ "end": "$|(?=;|//|/\\*|\\}|=)",
+ "beginCaptures": {
+ "1": { "name": "storage.type.function.swift" },
+ "2": { "name": "entity.name.function.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-generic-parameter-clause" },
+ { "include": "#declarations-parameter-clause" },
+ { "include": "#declarations-function-result" },
+ { "include": "#async-throws" },
+ {
+ "comment": "Swift 3: generic constraints after the parameters and return type",
+ "include": "#declarations-generic-where-clause"
+ }
+ ]
+ },
+
+ "declarations-operator": {
+ "name": "meta.definition.operator.swift",
+ "begin": "(?x)\n(?:\n \\b(prefix|infix|postfix)\n \\s+\n)?\n\\b\n(operator)\n\\s+\n(\n (\n (? # operator-head\n [/=\\-+!*%<>&|^~?]\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n )\n (\n \\g\n | \\. # Invalid dot\n | (? # operator-character\n [\\x{0300}-\\x{036F}]\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n )*+\n )\n | ( \\. ( \\g | \\g | \\. )++ ) # Dot operators\n)\n\\s*",
+ "end": "(;)|$\\n?|(?=//|/\\*)",
+ "beginCaptures": {
+ "1": { "name": "storage.modifier.swift" },
+ "2": { "name": "storage.type.function.operator.swift" },
+ "3": { "name": "entity.name.function.operator.swift" },
+ "4": {
+ "comment": "workaround for https://github.com/microsoft/vscode-textmate/issues/140#issuecomment-1793610346",
+ "name": "entity.name.function.operator.swift",
+ "patterns": [
+ {
+ "name": "invalid.illegal.dot-not-allowed-here.swift",
+ "match": "\\."
+ }
+ ]
+ }
+ },
+ "endCaptures": {
+ "1": { "name": "punctuation.terminator.statement.swift" }
+ },
+ "patterns": [
+ { "include": "#declarations-operator-swift2" },
+ { "include": "#declarations-operator-swift3" },
+
+ {
+ "name": "invalid.illegal.character-not-allowed-here.swift",
+ "match": "((?!$|;|//|/\\*)\\S)+"
+ }
+ ]
+ },
+
+ "declarations-operator-swift2": {
+ "begin": "\\G(\\{)",
+ "end": "(\\})",
+ "beginCaptures": {
+ "1": { "name": "punctuation.definition.operator.begin.swift" }
+ },
+ "endCaptures": {
+ "1": { "name": "punctuation.definition.operator.end.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+
+ {
+ "match": "\\b(associativity)\\s+(left|right)\\b",
+ "captures": {
+ "1": { "name": "storage.modifier.swift" },
+ "2": { "name": "keyword.other.operator.associativity.swift" }
+ }
+ },
+
+ {
+ "match": "\\b(precedence)\\s+([0-9]+)\\b",
+ "captures": {
+ "1": { "name": "storage.modifier.swift" },
+ "2": { "name": "constant.numeric.integer.swift" }
+ }
+ },
+
+ {
+ "match": "\\b(assignment)\\b",
+ "captures": {
+ "1": { "name": "storage.modifier.swift" }
+ }
+ }
+ ]
+ },
+
+ "declarations-operator-swift3": {
+ "match": "\\G(:)\\s*((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))",
+ "captures": {
+ "2": {
+ "name": "entity.other.inherited-class.swift",
+ "patterns": [{ "include": "#declarations-types-precedencegroup" }]
+ },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ }
+ },
+
+ "declarations-parameter-clause": {
+ "name": "meta.parameter-clause.swift",
+ "begin": "(\\()",
+ "end": "(\\))(?:\\s*(async)\\b)?",
+ "beginCaptures": {
+ "1": { "name": "punctuation.definition.parameters.begin.swift" }
+ },
+ "endCaptures": {
+ "1": { "name": "punctuation.definition.parameters.end.swift" },
+ "2": { "name": "storage.modifier.async.swift" }
+ },
+ "patterns": [{ "include": "#declarations-parameter-list" }]
+ },
+
+ "declarations-parameter-list": {
+ "patterns": [
+ {
+ "comment": "External parameter labels are considered part of the function name",
+ "match": "((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))(?=\\s*:)",
+ "captures": {
+ "1": { "name": "entity.name.function.swift" },
+ "2": { "name": "punctuation.definition.identifier.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "variable.parameter.function.swift" },
+ "5": { "name": "punctuation.definition.identifier.swift" },
+ "6": { "name": "punctuation.definition.identifier.swift" }
+ }
+ },
+
+ {
+ "comment": "If no external label is given, the name is both the external label and the internal variable name",
+ "match": "(((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)))(?=\\s*:)",
+ "captures": {
+ "1": { "name": "variable.parameter.function.swift" },
+ "2": { "name": "entity.name.function.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ }
+ },
+
+ {
+ "begin": ":\\s*(?!\\s)",
+ "end": "(?=[,)])",
+ "patterns": [
+ {
+ "name": "storage.modifier.swift",
+ "match": "\\bsending\\b"
+ },
+ { "include": "#declarations-available-types" },
+
+ {
+ "name": "invalid.illegal.extra-colon-in-parameter-list.swift",
+ "match": ":"
+ },
+
+ {
+ "comment": "a parameter's default value",
+ "begin": "=",
+ "end": "(?=[,)])",
+ "beginCaptures": {
+ "0": { "name": "keyword.operator.assignment.swift" }
+ },
+ "patterns": [{ "include": "#expressions" }]
+ }
+ ]
+ }
+ ]
+ },
+
+ "declarations-precedencegroup": {
+ "name": "meta.definition.precedencegroup.swift",
+ "begin": "\\b(precedencegroup)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*(?=\\{)",
+ "end": "(?!\\G)",
+ "beginCaptures": {
+ "1": { "name": "storage.type.precedencegroup.swift" },
+ "2": { "name": "entity.name.type.precedencegroup.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ {
+ "begin": "\\{",
+ "end": "\\}",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.precedencegroup.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.precedencegroup.end.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+
+ {
+ "match": "\\b(higherThan|lowerThan)\\s*:\\s*((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))",
+ "captures": {
+ "1": { "name": "storage.modifier.swift" },
+ "2": {
+ "name": "entity.other.inherited-class.swift",
+ "patterns": [{ "include": "#declarations-types-precedencegroup" }]
+ },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ }
+ },
+
+ {
+ "match": "\\b(associativity)\\b(?:\\s*:\\s*(right|left|none)\\b)?",
+ "captures": {
+ "1": { "name": "storage.modifier.swift" },
+ "2": { "name": "keyword.other.operator.associativity.swift" }
+ }
+ },
+
+ {
+ "match": "\\b(assignment)\\b(?:\\s*:\\s*(true|false)\\b)?",
+ "captures": {
+ "1": { "name": "storage.modifier.swift" },
+ "2": { "name": "constant.language.boolean.swift" }
+ }
+ }
+ ]
+ }
+ ]
+ },
+
+ "declarations-protocol": {
+ "name": "meta.definition.type.protocol.swift",
+ "begin": "\\b(protocol)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))",
+ "end": "(?<=\\})",
+ "beginCaptures": {
+ "1": { "name": "storage.type.$1.swift" },
+ "2": { "name": "entity.name.type.$1.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-inheritance-clause" },
+ {
+ "comment": "SE-0142: Permit where clauses to constrain associated types",
+ "include": "#declarations-generic-where-clause"
+ },
+
+ {
+ "name": "meta.definition.type.body.swift",
+ "begin": "\\{",
+ "end": "\\}",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.type.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.type.end.swift" }
+ },
+ "patterns": [
+ { "include": "#declarations-protocol-protocol-method" },
+ { "include": "#declarations-protocol-protocol-initializer" },
+ { "include": "#declarations-protocol-associated-type" },
+ { "include": "$self" }
+ ]
+ }
+ ]
+ },
+
+ "declarations-protocol-associated-type": {
+ "name": "meta.definition.associatedtype.swift",
+ "begin": "\\b(associatedtype)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*",
+ "end": "(?!\\G)$|(?=[;}]|$)",
+ "beginCaptures": {
+ "1": { "name": "keyword.other.declaration-specifier.swift" },
+ "2": { "name": "variable.language.associatedtype.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ { "include": "#declarations-inheritance-clause" },
+ {
+ "comment": "SE-0142: Permit where clauses to constrain associated types",
+ "include": "#declarations-generic-where-clause"
+ },
+ { "include": "#declarations-typealias-assignment" }
+ ]
+ },
+
+ "declarations-protocol-protocol-initializer": {
+ "name": "meta.definition.function.initializer.swift",
+ "begin": "(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k)\n | (?:\n (\n (? # operator-head\n [/=\\-+!*%<>&|^~?]\n | [\\x{00A1}-\\x{00A7}]\n | [\\x{00A9}\\x{00AB}]\n | [\\x{00AC}\\x{00AE}]\n | [\\x{00B0}-\\x{00B1}\\x{00B6}\\x{00BB}\\x{00BF}\\x{00D7}\\x{00F7}]\n | [\\x{2016}-\\x{2017}\\x{2020}-\\x{2027}]\n | [\\x{2030}-\\x{203E}]\n | [\\x{2041}-\\x{2053}]\n | [\\x{2055}-\\x{205E}]\n | [\\x{2190}-\\x{23FF}]\n | [\\x{2500}-\\x{2775}]\n | [\\x{2794}-\\x{2BFF}]\n | [\\x{2E00}-\\x{2E7F}]\n | [\\x{3001}-\\x{3003}]\n | [\\x{3008}-\\x{3030}]\n )\n (\n \\g\n | (? # operator-character\n [\\x{0300}-\\x{036F}]\n | [\\x{1DC0}-\\x{1DFF}]\n | [\\x{20D0}-\\x{20FF}]\n | [\\x{FE00}-\\x{FE0F}]\n | [\\x{FE20}-\\x{FE2F}]\n | [\\x{E0100}-\\x{E01EF}]\n )\n )*\n )\n | ( \\. ( \\g | \\g | \\. )+ ) # Dot operators\n )\n )\n\\s*\n(?=\\(|<)",
+ "end": "$|(?=;|//|/\\*|\\})",
+ "beginCaptures": {
+ "1": { "name": "storage.type.function.swift" },
+ "2": { "name": "entity.name.function.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-generic-parameter-clause" },
+ { "include": "#declarations-parameter-clause" },
+ { "include": "#declarations-function-result" },
+ { "include": "#async-throws" },
+ {
+ "comment": "Swift 3: generic constraints after the parameters and return type",
+ "include": "#declarations-generic-where-clause"
+ },
+
+ {
+ "name": "invalid.illegal.function-body-not-allowed-in-protocol.swift",
+ "begin": "\\{",
+ "end": "\\}",
+ "beginCaptures": {
+ "0": { "name": "punctuation.section.function.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.section.function.end.swift" }
+ },
+ "patterns": [{ "include": "$self" }]
+ }
+ ]
+ },
+
+ "declarations-type": {
+ "patterns": [
+ {
+ "name": "meta.definition.type.$1.swift",
+ "begin": "\\b(class(?!\\s+(?:func|var|let)\\b)|struct|actor)\\b\\s*((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))",
+ "end": "(?<=\\})",
+ "beginCaptures": {
+ "1": { "name": "storage.type.$1.swift" },
+ "2": { "name": "entity.name.type.$1.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-generic-parameter-clause" },
+ {
+ "comment": "Swift 3: generic constraints after the generic param list",
+ "include": "#declarations-generic-where-clause"
+ },
+ { "include": "#declarations-inheritance-clause" },
+
+ {
+ "name": "meta.definition.type.body.swift",
+ "begin": "\\{",
+ "end": "\\}",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.type.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.type.end.swift" }
+ },
+ "patterns": [{ "include": "$self" }]
+ }
+ ]
+ },
+ { "include": "#declarations-type-enum" }
+ ]
+ },
+
+ "declarations-type-enum": {
+ "name": "meta.definition.type.$1.swift",
+ "begin": "\\b(enum)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))",
+ "end": "(?<=\\})",
+ "beginCaptures": {
+ "1": { "name": "storage.type.$1.swift" },
+ "2": { "name": "entity.name.type.$1.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-generic-parameter-clause" },
+ {
+ "comment": "Swift 3: generic constraints after the generic param list",
+ "include": "#declarations-generic-where-clause"
+ },
+ { "include": "#declarations-inheritance-clause" },
+
+ {
+ "name": "meta.definition.type.body.swift",
+ "begin": "\\{",
+ "end": "\\}",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.type.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.type.end.swift" }
+ },
+ "patterns": [
+ { "include": "#declarations-type-enum-enum-case-clause" },
+ { "include": "$self" }
+ ]
+ }
+ ]
+ },
+
+ "declarations-type-enum-associated-values": {
+ "begin": "\\G\\(",
+ "end": "\\)",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.parameters.begin.swift" }
+ },
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.parameters.end.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+
+ {
+ "begin": "(?x)\n(?:(_)|((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\n\\s+\n(((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\n\\s*(:)",
+ "end": "(?=[,)\\]])",
+ "beginCaptures": {
+ "1": { "name": "entity.name.function.swift" },
+ "2": { "name": "invalid.illegal.distinct-labels-not-allowed.swift" },
+ "5": { "name": "variable.parameter.function.swift" },
+ "7": { "name": "punctuation.separator.argument-label.swift" }
+ },
+ "patterns": [{ "include": "#declarations-available-types" }]
+ },
+
+ {
+ "begin": "(((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\\s*(:)",
+ "end": "(?=[,)\\]])",
+ "beginCaptures": {
+ "1": { "name": "entity.name.function.swift" },
+ "2": { "name": "variable.parameter.function.swift" },
+ "4": { "name": "punctuation.separator.argument-label.swift" }
+ },
+ "patterns": [{ "include": "#declarations-available-types" }]
+ },
+
+ {
+ "comment": "an element without a label (i.e. anything else)",
+ "begin": "(?![,)\\]])(?=\\S)",
+ "end": "(?=[,)\\]])",
+ "patterns": [
+ { "include": "#declarations-available-types" },
+
+ {
+ "name": "invalid.illegal.extra-colon-in-parameter-list.swift",
+ "match": ":"
+ }
+ ]
+ }
+ ]
+ },
+
+ "declarations-type-enum-enum-case": {
+ "begin": "(?x)((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*",
+ "end": "(?<=\\))|(?![=(])",
+ "beginCaptures": {
+ "1": { "name": "variable.other.enummember.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-type-enum-associated-values" },
+ { "include": "#declarations-type-enum-raw-value-assignment" }
+ ]
+ },
+
+ "declarations-type-enum-enum-case-clause": {
+ "begin": "\\b(case)\\b\\s*",
+ "end": "(?=[;}])|(?!\\G)(?!//|/\\*)(?=[^\\s,])",
+ "beginCaptures": {
+ "1": { "name": "storage.type.enum.case.swift" }
+ },
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-type-enum-enum-case" },
+ { "include": "#declarations-type-enum-more-cases" }
+ ]
+ },
+
+ "declarations-type-enum-more-cases": {
+ "name": "meta.enum-case.more-cases",
+ "begin": ",\\s*",
+ "end": "(?!\\G)(?!//|/\\*)(?=[;}]|[^\\s,])",
+ "patterns": [
+ { "include": "#comments" },
+ { "include": "#declarations-type-enum-enum-case" },
+ { "include": "#declarations-type-enum-more-cases" }
+ ]
+ },
+
+ "declarations-type-enum-raw-value-assignment": {
+ "begin": "(=)\\s*",
+ "end": "(?!\\G)",
+ "beginCaptures": {
+ "1": { "name": "keyword.operator.assignment.swift" }
+ },
+ "patterns": [{ "include": "#comments" }, { "include": "#literals" }]
+ },
+
+ "declarations-type-identifier": {
+ "begin": "((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*",
+ "end": "(?!<)",
+ "beginCaptures": {
+ "1": {
+ "name": "meta.type-name.swift",
+ "patterns": [{ "include": "#builtin-types" }]
+ },
+ "2": { "name": "punctuation.definition.identifier.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ {
+ "begin": "(?=<)",
+ "end": "(?!\\G)",
+ "patterns": [{ "include": "#declarations-generic-argument-clause" }]
+ }
+ ]
+ },
+
+ "declarations-type-operators": {
+ "patterns": [
+ {
+ "comment": "Swift 3: A & B",
+ "match": "(?&|\\^~.])(&)(?![/=\\-+!*%<>&|\\^~.])",
+ "captures": {
+ "1": { "name": "keyword.operator.type.composition.swift" }
+ }
+ },
+
+ {
+ "comment": "SE-0390: Noncopyable structs and enums",
+ "match": "(?&|\\^~.])(~)(?![/=\\-+!*%<>&|\\^~.])",
+ "captures": {
+ "1": { "name": "keyword.operator.type.requirement-suppression.swift" }
+ }
+ }
+ ]
+ },
+
+ "declarations-typealias": {
+ "name": "meta.definition.typealias.swift",
+ "begin": "\\b(typealias)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))\\s*",
+ "end": "(?!\\G)$|(?=;|//|/\\*|$)",
+ "beginCaptures": {
+ "1": { "name": "keyword.other.declaration-specifier.swift" },
+ "2": { "name": "entity.name.type.typealias.swift" },
+ "3": { "name": "punctuation.definition.identifier.swift" },
+ "4": { "name": "punctuation.definition.identifier.swift" }
+ },
+ "patterns": [
+ {
+ "begin": "\\G(?=<)",
+ "end": "(?!\\G)",
+ "patterns": [{ "include": "#declarations-generic-parameter-clause" }]
+ },
+ { "include": "#declarations-typealias-assignment" }
+ ]
+ },
+
+ "declarations-typealias-assignment": {
+ "begin": "(=)\\s*",
+ "end": "(?!\\G)$|(?=;|//|/\\*|$)",
+ "beginCaptures": {
+ "1": { "name": "keyword.operator.assignment.swift" }
+ },
+ "patterns": [{ "include": "#declarations-available-types" }]
+ },
+
+ "declarations-typed-variable-declaration": {
+ "begin": "(?x)\n\\b(?:(async)\\s+)?(let|var)\\b\\s+\n(?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k