Compare commits
10 commits
fd2aa80894
...
9c5ad363a4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c5ad363a4 | ||
|
|
44db42da1e | ||
|
|
1577ee5525 | ||
|
|
915b695e88 | ||
|
|
a468e33e82 | ||
|
|
fae53d6fd3 | ||
|
|
2d6ec2e445 | ||
|
|
7128e58692 | ||
|
|
c598350fc5 | ||
|
|
7d2a2dd547 |
1311 changed files with 402922 additions and 118 deletions
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
|
@ -41,7 +41,7 @@ jobs:
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install system dependencies
|
- name: Install system dependencies
|
||||||
run: sudo apt-get update && sudo apt-get install -y dpkg-dev
|
run: sudo apt-get update && sudo apt-get install -y dpkg-dev rpm
|
||||||
|
|
||||||
- name: Derive release version
|
- name: Derive release version
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
@ -61,6 +61,10 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
run: ./scripts/build-deb.sh "${VERSION}"
|
run: ./scripts/build-deb.sh "${VERSION}"
|
||||||
|
|
||||||
|
- name: Build rpm package
|
||||||
|
shell: bash
|
||||||
|
run: ./scripts/build-rpm.sh "${VERSION}"
|
||||||
|
|
||||||
- name: Bundle tarball
|
- name: Bundle tarball
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -74,6 +78,7 @@ jobs:
|
||||||
sha256sum \
|
sha256sum \
|
||||||
"${BIN_NAME}-${GITHUB_REF_NAME}-x86_64-unknown-linux-gnu.tar.gz" \
|
"${BIN_NAME}-${GITHUB_REF_NAME}-x86_64-unknown-linux-gnu.tar.gz" \
|
||||||
"target/debian/${BIN_NAME}_${VERSION}_amd64.deb" \
|
"target/debian/${BIN_NAME}_${VERSION}_amd64.deb" \
|
||||||
|
"target/rpm/${BIN_NAME}-${VERSION}-1.x86_64.rpm" \
|
||||||
> "${BIN_NAME}-${GITHUB_REF_NAME}-checksums.txt"
|
> "${BIN_NAME}-${GITHUB_REF_NAME}-checksums.txt"
|
||||||
|
|
||||||
- name: Publish release assets
|
- name: Publish release assets
|
||||||
|
|
@ -84,3 +89,4 @@ jobs:
|
||||||
vcom-${{ github.ref_name }}-x86_64-unknown-linux-gnu.tar.gz
|
vcom-${{ github.ref_name }}-x86_64-unknown-linux-gnu.tar.gz
|
||||||
vcom-${{ github.ref_name }}-checksums.txt
|
vcom-${{ github.ref_name }}-checksums.txt
|
||||||
target/debian/vcom_${{ env.VERSION }}_amd64.deb
|
target/debian/vcom_${{ env.VERSION }}_amd64.deb
|
||||||
|
target/rpm/vcom-${{ env.VERSION }}-1.x86_64.rpm
|
||||||
|
|
|
||||||
2
PKGBUILD
2
PKGBUILD
|
|
@ -1,5 +1,5 @@
|
||||||
pkgname=vcom
|
pkgname=vcom
|
||||||
pkgver=0.2.4
|
pkgver=0.2.6
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Terminal file manager inspired by Midnight Commander"
|
pkgdesc="Terminal file manager inspired by Midnight Commander"
|
||||||
arch=("x86_64" "aarch64")
|
arch=("x86_64" "aarch64")
|
||||||
|
|
|
||||||
53
README.md
53
README.md
|
|
@ -15,6 +15,14 @@
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
## Quick install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/vrubelroman/vcom/main/scripts/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
This single command installs vcom with Nerd Font and image preview support on any Linux distribution.
|
||||||
|
|
||||||
## Build and run
|
## Build and run
|
||||||
|
|
||||||
Run directly:
|
Run directly:
|
||||||
|
|
@ -36,13 +44,13 @@ go build -o vcom ./cmd/vcom
|
||||||
Run directly from the flake:
|
Run directly from the flake:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix run github:vrubelroman/vcom?ref=v0.2.4
|
nix run github:vrubelroman/vcom?ref=v0.2.6
|
||||||
```
|
```
|
||||||
|
|
||||||
Install into user profile:
|
Install into user profile:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix profile add github:vrubelroman/vcom?ref=v0.2.4
|
nix profile add github:vrubelroman/vcom?ref=v0.2.6
|
||||||
```
|
```
|
||||||
|
|
||||||
The Nix package wraps `vcom` with `ueberzugpp` in `PATH`, so image preview works in non-`kitty` terminals out of the box.
|
The Nix package wraps `vcom` with `ueberzugpp` in `PATH`, so image preview works in non-`kitty` terminals out of the box.
|
||||||
|
|
@ -52,11 +60,15 @@ The Nix package wraps `vcom` with `ueberzugpp` in `PATH`, so image preview works
|
||||||
Download and install the latest release:
|
Download and install the latest release:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sL https://github.com/vrubelroman/vcom/releases/download/v0.2.4/vcom_0.2.4_amd64.deb -o /tmp/vcom_0.2.4_amd64.deb
|
curl -sL https://github.com/vrubelroman/vcom/releases/download/v0.2.6/vcom_0.2.6_amd64.deb -o /tmp/vcom_0.2.6_amd64.deb
|
||||||
sudo apt install /tmp/vcom_0.2.4_amd64.deb
|
sudo apt install /tmp/vcom_0.2.6_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
The Debian package declares `ueberzug` (or `ueberzugpp` where available) as a dependency for image preview outside `kitty`.
|
The Debian package recommends `ueberzugpp` for image preview outside `kitty` (optional). To install it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install pipx && pipx ensurepath && pipx install ueberzugpp
|
||||||
|
```
|
||||||
|
|
||||||
### Arch Linux
|
### Arch Linux
|
||||||
|
|
||||||
|
|
@ -68,6 +80,27 @@ makepkg -si
|
||||||
|
|
||||||
The Arch package depends on `ueberzugpp`, so non-`kitty` image preview is installed together with `vcom`.
|
The Arch package depends on `ueberzugpp`, so non-`kitty` image preview is installed together with `vcom`.
|
||||||
|
|
||||||
|
### Fedora / RHEL
|
||||||
|
|
||||||
|
Download and install the latest RPM:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sL https://github.com/vrubelroman/vcom/releases/download/v0.2.6/vcom-0.2.6-1.x86_64.rpm -o /tmp/vcom.rpm
|
||||||
|
sudo dnf install /tmp/vcom.rpm
|
||||||
|
```
|
||||||
|
|
||||||
|
Or on RHEL/CentOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo rpm -ivh /tmp/vcom.rpm
|
||||||
|
```
|
||||||
|
|
||||||
|
The RPM package recommends `ueberzugpp` for image preview outside `kitty` (optional):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dnf install ueberzugpp
|
||||||
|
```
|
||||||
|
|
||||||
## Font requirement (icons)
|
## Font requirement (icons)
|
||||||
|
|
||||||
For file icons, `vcom` expects a Nerd Font in your terminal profile.
|
For file icons, `vcom` expects a Nerd Font in your terminal profile.
|
||||||
|
|
@ -183,19 +216,21 @@ Built-in themes (press `t` to open theme selector or set `ui.theme` in config):
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
Pushing a tag like `v0.2.4` triggers GitHub Actions release workflow (`.github/workflows/release.yml`) which:
|
Pushing a tag like `v0.2.6` triggers GitHub Actions release workflow (`.github/workflows/release.yml`) which:
|
||||||
|
|
||||||
- runs tests
|
- runs tests
|
||||||
- vendors Go modules
|
- vendors Go modules
|
||||||
- builds release binary
|
- builds release binary
|
||||||
- builds Debian package
|
- builds Debian package
|
||||||
|
- builds RPM package
|
||||||
- publishes release assets
|
- publishes release assets
|
||||||
|
|
||||||
Release artifacts:
|
Release artifacts:
|
||||||
|
|
||||||
- `vcom-v0.2.4-x86_64-unknown-linux-gnu.tar.gz`
|
- `vcom-v0.2.6-x86_64-unknown-linux-gnu.tar.gz`
|
||||||
- `vcom_0.2.4_amd64.deb`
|
- `vcom_0.2.6_amd64.deb`
|
||||||
- `vcom-v0.2.4-checksums.txt`
|
- `vcom-0.2.6-1.x86_64.rpm`
|
||||||
|
- `vcom-v0.2.6-checksums.txt`
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
lib = pkgs.lib;
|
lib = pkgs.lib;
|
||||||
packageBase = pkgs.buildGoModule {
|
packageBase = pkgs.buildGoModule {
|
||||||
pname = "vcom";
|
pname = "vcom";
|
||||||
version = "0.2.4";
|
version = "0.2.6";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
vendorHash = null;
|
vendorHash = null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,6 @@ type BehaviorConfig struct {
|
||||||
ConfirmOverwrite bool `toml:"confirm_overwrite"`
|
ConfirmOverwrite bool `toml:"confirm_overwrite"`
|
||||||
CalculateDirSizeOnSpace bool `toml:"calculate_dir_size_on_space"`
|
CalculateDirSizeOnSpace bool `toml:"calculate_dir_size_on_space"`
|
||||||
FollowSymlinks bool `toml:"follow_symlinks"`
|
FollowSymlinks bool `toml:"follow_symlinks"`
|
||||||
AutoRefresh bool `toml:"auto_refresh"`
|
|
||||||
AutoRefreshInterval int `toml:"auto_refresh_interval"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Default() Config {
|
func Default() Config {
|
||||||
|
|
@ -113,8 +111,6 @@ func Default() Config {
|
||||||
ConfirmOverwrite: true,
|
ConfirmOverwrite: true,
|
||||||
CalculateDirSizeOnSpace: true,
|
CalculateDirSizeOnSpace: true,
|
||||||
FollowSymlinks: false,
|
FollowSymlinks: false,
|
||||||
AutoRefresh: true,
|
|
||||||
AutoRefreshInterval: 5,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -209,14 +205,6 @@ func (c *Config) Validate() error {
|
||||||
if c.UI.CenterWidthPercent < 20 || c.UI.CenterWidthPercent > 60 {
|
if c.UI.CenterWidthPercent < 20 || c.UI.CenterWidthPercent > 60 {
|
||||||
return errors.New("ui.center_width_percent must be between 20 and 60")
|
return errors.New("ui.center_width_percent must be between 20 and 60")
|
||||||
}
|
}
|
||||||
if c.Behavior.AutoRefresh {
|
|
||||||
if c.Behavior.AutoRefreshInterval < 1 {
|
|
||||||
c.Behavior.AutoRefreshInterval = 5
|
|
||||||
}
|
|
||||||
if c.Behavior.AutoRefreshInterval > 60 {
|
|
||||||
return errors.New("behavior.auto_refresh_interval must be between 1 and 60")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch strings.ToLower(strings.TrimSpace(c.Browser.Sort.By)) {
|
switch strings.ToLower(strings.TrimSpace(c.Browser.Sort.By)) {
|
||||||
case "", "name":
|
case "", "name":
|
||||||
c.Browser.Sort.By = "name"
|
c.Browser.Sort.By = "name"
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ func BuildPreview(entry Entry, options PreviewOptions) Preview {
|
||||||
}
|
}
|
||||||
|
|
||||||
data := buffer.Bytes()
|
data := buffer.Bytes()
|
||||||
if format, dimensions, ok := detectImage(data); ok {
|
if format, dimensions, ok := DetectImage(data); ok {
|
||||||
preview.Kind = PreviewKindImage
|
preview.Kind = PreviewKindImage
|
||||||
preview.Metadata.ImageFormat = format
|
preview.Metadata.ImageFormat = format
|
||||||
preview.Metadata.ImageSize = dimensions
|
preview.Metadata.ImageSize = dimensions
|
||||||
|
|
@ -671,7 +671,7 @@ func buildVideoPreview(entry Entry, options PreviewOptions, base Preview) Previe
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectImage(data []byte) (string, string, bool) {
|
func DetectImage(data []byte) (string, string, bool) {
|
||||||
cfg, format, err := image.DecodeConfig(bytes.NewReader(data))
|
cfg, format, err := image.DecodeConfig(bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", false
|
return "", "", false
|
||||||
|
|
|
||||||
|
|
@ -485,6 +485,11 @@ func (c *SSHClient) CopyFileFromRemote(remotePath, localPath string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DownloadFile downloads a remote file to a local path via SFTP.
|
||||||
|
func (c *SSHClient) DownloadFile(remotePath, localPath string) error {
|
||||||
|
return c.CopyFileFromRemote(remotePath, localPath)
|
||||||
|
}
|
||||||
|
|
||||||
// CopyDirToRemote recursively copies a local directory to a remote path.
|
// CopyDirToRemote recursively copies a local directory to a remote path.
|
||||||
func (c *SSHClient) CopyDirToRemote(localDir, remoteDir string) error {
|
func (c *SSHClient) CopyDirToRemote(localDir, remoteDir string) error {
|
||||||
return c.copyDirToRemote(localDir, remoteDir, nil, nil)
|
return c.copyDirToRemote(localDir, remoteDir, nil, nil)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
"vcom/internal/theme"
|
"vcom/internal/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "v0.2.4"
|
const version = "v0.2.6"
|
||||||
|
|
||||||
type modalKind int
|
type modalKind int
|
||||||
|
|
||||||
|
|
@ -93,6 +93,7 @@ type themeSelectorState struct {
|
||||||
type previewMsg struct {
|
type previewMsg struct {
|
||||||
entryPath string
|
entryPath string
|
||||||
preview vfs.Preview
|
preview vfs.Preview
|
||||||
|
remoteImageTemp string // temp file path for downloaded remote image, cleared on change
|
||||||
}
|
}
|
||||||
|
|
||||||
type dirSizeMsg struct {
|
type dirSizeMsg struct {
|
||||||
|
|
@ -167,7 +168,6 @@ type copyDoneMsg struct {
|
||||||
|
|
||||||
type dismissNoticeMsg struct{}
|
type dismissNoticeMsg struct{}
|
||||||
type dismissYankFlashMsg struct{}
|
type dismissYankFlashMsg struct{}
|
||||||
type tickMsg struct{}
|
|
||||||
type externalOpenMsg struct {
|
type externalOpenMsg struct {
|
||||||
path string
|
path string
|
||||||
err error
|
err error
|
||||||
|
|
@ -263,6 +263,7 @@ type Model struct {
|
||||||
ssh *sshState
|
ssh *sshState
|
||||||
preSSHPath string // original path before entering SSH mode
|
preSSHPath string // original path before entering SSH mode
|
||||||
themeSelector *themeSelectorState // nil when not in theme selector dialog
|
themeSelector *themeSelectorState // nil when not in theme selector dialog
|
||||||
|
remoteImageTemp string // temp file of downloaded remote image, cleaned on change/quit
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewModel(cfg config.Config, configPath string) (Model, error) {
|
func NewModel(cfg config.Config, configPath string) (Model, error) {
|
||||||
|
|
@ -334,9 +335,6 @@ func NewModel(cfg config.Config, configPath string) (Model, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) Init() tea.Cmd {
|
func (m Model) Init() tea.Cmd {
|
||||||
if m.cfg.Behavior.AutoRefresh {
|
|
||||||
return tea.Batch(m.loadPreviewCmd(), autoRefreshTickCmd(m.cfg.Behavior.AutoRefreshInterval))
|
|
||||||
}
|
|
||||||
return m.loadPreviewCmd()
|
return m.loadPreviewCmd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -354,6 +352,16 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
log.Printf("[EVENT] previewMsg: path=%s kind=%s", msg.entryPath, msg.preview.Kind)
|
log.Printf("[EVENT] previewMsg: path=%s kind=%s", msg.entryPath, msg.preview.Kind)
|
||||||
if selected, ok := m.activePane().Selected(); ok && selected.Path == msg.entryPath {
|
if selected, ok := m.activePane().Selected(); ok && selected.Path == msg.entryPath {
|
||||||
m.applyPreview(msg.preview)
|
m.applyPreview(msg.preview)
|
||||||
|
// Track remote image temp file for cleanup
|
||||||
|
if msg.remoteImageTemp != "" {
|
||||||
|
if m.remoteImageTemp != "" && m.remoteImageTemp != msg.remoteImageTemp {
|
||||||
|
os.Remove(m.remoteImageTemp)
|
||||||
|
}
|
||||||
|
m.remoteImageTemp = msg.remoteImageTemp
|
||||||
|
} else if msg.preview.Kind != vfs.PreviewKindImage && m.remoteImageTemp != "" {
|
||||||
|
os.Remove(m.remoteImageTemp)
|
||||||
|
m.remoteImageTemp = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if m.selectMode && !m.viewMode && msg.preview.Kind != vfs.PreviewKindText {
|
if m.selectMode && !m.viewMode && msg.preview.Kind != vfs.PreviewKindText {
|
||||||
m.selectMode = false
|
m.selectMode = false
|
||||||
|
|
@ -923,20 +931,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
m.syncPreviewContent()
|
m.syncPreviewContent()
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case tickMsg:
|
|
||||||
if !m.cfg.Behavior.AutoRefresh ||
|
|
||||||
m.busy ||
|
|
||||||
m.copyJob != nil ||
|
|
||||||
m.archiveJob != nil ||
|
|
||||||
m.modal.kind != modalNone ||
|
|
||||||
m.filterMode ||
|
|
||||||
m.cursorMode || m.visualMode ||
|
|
||||||
m.viewMode {
|
|
||||||
return m, autoRefreshTickCmd(m.cfg.Behavior.AutoRefreshInterval)
|
|
||||||
}
|
|
||||||
m.autoRefreshPanes()
|
|
||||||
return m, tea.Batch(autoRefreshTickCmd(m.cfg.Behavior.AutoRefreshInterval), m.loadPreviewCmd())
|
|
||||||
|
|
||||||
case externalOpenMsg:
|
case externalOpenMsg:
|
||||||
if msg.err != nil {
|
if msg.err != nil {
|
||||||
m.status = fmt.Sprintf("Open failed: %v", msg.err)
|
m.status = fmt.Sprintf("Open failed: %v", msg.err)
|
||||||
|
|
@ -1110,7 +1104,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
m.filterMode = false
|
m.filterMode = false
|
||||||
m.status = "Filter cleared"
|
m.status = "Filter cleared"
|
||||||
return m, nil
|
return m, nil
|
||||||
case key.Matches(msg, m.keys.Confirm):
|
case msg.String() == "enter":
|
||||||
log.Printf("[KEY] Filter: Enter — query=%s", m.filterQuery)
|
log.Printf("[KEY] Filter: Enter — query=%s", m.filterQuery)
|
||||||
m.filterMode = false
|
m.filterMode = false
|
||||||
m.filterInput.Blur()
|
m.filterInput.Blur()
|
||||||
|
|
@ -1162,6 +1156,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
m.saveSession()
|
m.saveSession()
|
||||||
m.cleanupArchiveMounts()
|
m.cleanupArchiveMounts()
|
||||||
m.cleanupImageOverlay()
|
m.cleanupImageOverlay()
|
||||||
|
m.cleanupRemoteImageTemp()
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case key.Matches(msg, m.keys.Help):
|
case key.Matches(msg, m.keys.Help):
|
||||||
log.Printf("[KEY] Help — open help modal")
|
log.Printf("[KEY] Help — open help modal")
|
||||||
|
|
@ -1409,7 +1404,7 @@ func (m Model) handleModalKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
m.modal = modalState{}
|
m.modal = modalState{}
|
||||||
m.status = "Cancelled"
|
m.status = "Cancelled"
|
||||||
return m, nil
|
return m, nil
|
||||||
case key.Matches(msg, m.keys.Confirm):
|
case msg.String() == "enter":
|
||||||
value := strings.TrimSpace(m.modal.input.Value())
|
value := strings.TrimSpace(m.modal.input.Value())
|
||||||
if value == "" {
|
if value == "" {
|
||||||
if m.modal.kind == modalMkdir {
|
if m.modal.kind == modalMkdir {
|
||||||
|
|
@ -1608,7 +1603,7 @@ func (m Model) handleModalKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
}
|
}
|
||||||
m.status = "Cancelled"
|
m.status = "Cancelled"
|
||||||
return m, nil
|
return m, nil
|
||||||
case key.Matches(msg, m.keys.Confirm):
|
case msg.String() == "enter":
|
||||||
// If a test is already in progress, ignore
|
// If a test is already in progress, ignore
|
||||||
if m.ssh != nil && m.ssh.testingConn {
|
if m.ssh != nil && m.ssh.testingConn {
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
@ -1773,35 +1768,32 @@ func (m *Model) reloadPane(id PaneID, preserve string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) refreshAllPanes(status string) (tea.Model, tea.Cmd) {
|
func (m *Model) refreshAllPanes(status string) (tea.Model, tea.Cmd) {
|
||||||
leftSelected := selectedName(&m.left)
|
log.Printf("[PANEL] refreshAllPanes")
|
||||||
rightSelected := selectedName(&m.right)
|
for _, id := range []PaneID{PaneLeft, PaneRight} {
|
||||||
log.Printf("[PANEL] refreshAllPanes: left=%s right=%s", leftSelected, rightSelected)
|
pane := m.paneByID(id)
|
||||||
if err := m.reloadPane(PaneLeft, leftSelected); err != nil {
|
if name := selectedName(pane); name != "" {
|
||||||
|
pane.SaveCursor(pane.Path, name)
|
||||||
|
}
|
||||||
|
preserve := pane.LoadCursor(pane.Path)
|
||||||
|
var err error
|
||||||
|
if pane.InRemote() {
|
||||||
|
err = m.reloadRemotePane(id, preserve)
|
||||||
|
} else if pane.InArchive() {
|
||||||
|
err = m.reloadPane(id, preserve)
|
||||||
|
} else {
|
||||||
|
err = m.reloadPane(id, preserve)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[REFRESH] pane=%s path=%s err=%v", id, pane.Path, err)
|
||||||
m.status = err.Error()
|
m.status = err.Error()
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
if err := m.reloadPane(PaneRight, rightSelected); err != nil {
|
|
||||||
m.status = err.Error()
|
|
||||||
return m, nil
|
|
||||||
}
|
}
|
||||||
m.status = status
|
m.status = status
|
||||||
return m, m.loadPreviewCmd()
|
return m, m.loadPreviewCmd()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) autoRefreshPanes() {
|
|
||||||
for _, id := range []PaneID{PaneLeft, PaneRight} {
|
|
||||||
pane := m.paneByID(id)
|
|
||||||
if pane.InRemote() || pane.InArchive() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if name := selectedName(pane); name != "" {
|
|
||||||
pane.SaveCursor(pane.Path, name)
|
|
||||||
}
|
|
||||||
if err := m.reloadPane(id, pane.LoadCursor(pane.Path)); err != nil {
|
|
||||||
log.Printf("[REFRESH] pane=%s path=%s err=%v", id, pane.Path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) moveCursor(delta int) {
|
func (m *Model) moveCursor(delta int) {
|
||||||
// When a filter query is active on this pane, move through filtered entries
|
// When a filter query is active on this pane, move through filtered entries
|
||||||
|
|
@ -2237,7 +2229,8 @@ func (m Model) loadPreviewCmd() tea.Cmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remote preview: read file via SFTP and build a text preview
|
// Remote preview: read file via SFTP, detect kind, build preview.
|
||||||
|
// Images are downloaded to a local temp file so the overlay (ueberzugpp/kitty) can read them.
|
||||||
if mount, ok := m.activePane().CurrentRemote(); ok {
|
if mount, ok := m.activePane().CurrentRemote(); ok {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
rc, err := mount.Client.ReadFile(selected.Path)
|
rc, err := mount.Client.ReadFile(selected.Path)
|
||||||
|
|
@ -2253,8 +2246,8 @@ func (m Model) loadPreviewCmd() tea.Cmd {
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
maxBytes := int64(m.cfg.Preview.MaxPreviewBytes)
|
maxBytes := int64(m.cfg.Preview.MaxPreviewBytes)
|
||||||
limited := io.LimitReader(rc, maxBytes)
|
sample := io.LimitReader(rc, maxBytes)
|
||||||
raw, readErr := io.ReadAll(limited)
|
raw, readErr := io.ReadAll(sample)
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
return previewMsg{
|
return previewMsg{
|
||||||
entryPath: selected.Path,
|
entryPath: selected.Path,
|
||||||
|
|
@ -2265,6 +2258,66 @@ func (m Model) loadPreviewCmd() tea.Cmd {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meta := vfs.Metadata{
|
||||||
|
Path: selected.Path,
|
||||||
|
Size: selected.Size,
|
||||||
|
SizeKnown: true,
|
||||||
|
Extension: selected.Extension,
|
||||||
|
Permissions: vfs.Permissions(selected.Mode),
|
||||||
|
ModifiedAt: vfs.ShortTime(selected.ModifiedAt),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect image by magic bytes
|
||||||
|
if format, dims, isImage := vfs.DetectImage(raw); isImage {
|
||||||
|
// Download full image to temp
|
||||||
|
tmpDir := filepath.Join(os.TempDir(), "vcom-remote-images")
|
||||||
|
os.MkdirAll(tmpDir, 0o700)
|
||||||
|
tmpFile, tmpErr := os.CreateTemp(tmpDir, "vcom-img-*"+filepath.Ext(selected.Path))
|
||||||
|
if tmpErr == nil {
|
||||||
|
tmpPath := tmpFile.Name()
|
||||||
|
tmpFile.Close()
|
||||||
|
if dlErr := mount.Client.DownloadFile(selected.Path, tmpPath); dlErr == nil {
|
||||||
|
meta.Path = tmpPath
|
||||||
|
return previewMsg{
|
||||||
|
entryPath: selected.Path,
|
||||||
|
remoteImageTemp: tmpPath,
|
||||||
|
preview: vfs.Preview{
|
||||||
|
Kind: vfs.PreviewKindImage,
|
||||||
|
Title: selected.DisplayName(),
|
||||||
|
Body: fmt.Sprintf("%s (%s)\n%s", format, dims, vfs.HumanSize(selected.Size)),
|
||||||
|
Metadata: meta,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Remove(tmpPath)
|
||||||
|
}
|
||||||
|
// Download failed — show as image with metadata only
|
||||||
|
return previewMsg{
|
||||||
|
entryPath: selected.Path,
|
||||||
|
preview: vfs.Preview{
|
||||||
|
Kind: vfs.PreviewKindImage,
|
||||||
|
Title: selected.DisplayName(),
|
||||||
|
Body: fmt.Sprintf("%s (%s)\n(remote — could not download for overlay)", format, dims),
|
||||||
|
Metadata: meta,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect binary (non-image)
|
||||||
|
if vfs.IsBinarySample(raw) {
|
||||||
|
return previewMsg{
|
||||||
|
entryPath: selected.Path,
|
||||||
|
preview: vfs.Preview{
|
||||||
|
Kind: vfs.PreviewKindBinary,
|
||||||
|
Title: selected.DisplayName(),
|
||||||
|
Body: fmt.Sprintf("Binary file\n%s • %s", vfs.HumanSize(selected.Size), selected.Extension),
|
||||||
|
Metadata: meta,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text preview
|
||||||
body := string(raw)
|
body := string(raw)
|
||||||
if int64(len(raw)) >= maxBytes {
|
if int64(len(raw)) >= maxBytes {
|
||||||
body += "\n\n[... truncated ...]"
|
body += "\n\n[... truncated ...]"
|
||||||
|
|
@ -2276,14 +2329,7 @@ func (m Model) loadPreviewCmd() tea.Cmd {
|
||||||
Title: selected.DisplayName(),
|
Title: selected.DisplayName(),
|
||||||
Body: body,
|
Body: body,
|
||||||
PlainBody: body,
|
PlainBody: body,
|
||||||
Metadata: vfs.Metadata{
|
Metadata: meta,
|
||||||
Path: selected.Path,
|
|
||||||
Size: selected.Size,
|
|
||||||
SizeKnown: true,
|
|
||||||
Extension: selected.Extension,
|
|
||||||
Permissions: vfs.Permissions(selected.Mode),
|
|
||||||
ModifiedAt: vfs.ShortTime(selected.ModifiedAt),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2358,7 +2404,9 @@ func (m *Model) handleTransfer(kind fileOpKind) (tea.Model, tea.Cmd) {
|
||||||
kind, m.active, srcPane.Path, dstPane.Path, srcHasRemote, dstHasRemote, sources)
|
kind, m.active, srcPane.Path, dstPane.Path, srcHasRemote, dstHasRemote, sources)
|
||||||
|
|
||||||
// Check for existing targets (fast — one Stat per top-level item)
|
// Check for existing targets (fast — one Stat per top-level item)
|
||||||
|
// Skip when target is remote — local fs check doesn't apply.
|
||||||
existingTargets := 0
|
existingTargets := 0
|
||||||
|
if !dstHasRemote {
|
||||||
for _, sourcePath := range sources {
|
for _, sourcePath := range sources {
|
||||||
targetPath := filepath.Join(targetDir, filepath.Base(sourcePath))
|
targetPath := filepath.Join(targetDir, filepath.Base(sourcePath))
|
||||||
exists, err := vfs.PathExists(targetPath)
|
exists, err := vfs.PathExists(targetPath)
|
||||||
|
|
@ -2371,6 +2419,7 @@ func (m *Model) handleTransfer(kind fileOpKind) (tea.Model, tea.Cmd) {
|
||||||
existingTargets++
|
existingTargets++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
overwrite := existingTargets > 0
|
overwrite := existingTargets > 0
|
||||||
if existingTargets > 0 && !m.cfg.Behavior.ConfirmOverwrite {
|
if existingTargets > 0 && !m.cfg.Behavior.ConfirmOverwrite {
|
||||||
overwrite = true
|
overwrite = true
|
||||||
|
|
@ -4352,12 +4401,7 @@ func renderCopyProgressModal(job copyJobState, palette theme.Palette, width int)
|
||||||
}
|
}
|
||||||
lines = append(lines, barAndPct...)
|
lines = append(lines, barAndPct...)
|
||||||
}
|
}
|
||||||
bgStyle := lipgloss.NewStyle().Foreground(palette.Accent).Background(palette.Panel)
|
lines = append(lines, spacer, renderModalNoteLine("Background / b, Cancel / c", contentWidth, palette, mutedStyle))
|
||||||
cancelStyle := lipgloss.NewStyle().Foreground(palette.CancelButton).Background(palette.Panel)
|
|
||||||
bgAndCancel := lipgloss.NewStyle().Width(contentWidth).Background(palette.Panel).Render(
|
|
||||||
bgStyle.Render("Background / b") + " " + cancelStyle.Render("Cancel / c"),
|
|
||||||
)
|
|
||||||
lines = append(lines, spacer, bgAndCancel)
|
|
||||||
if job.background {
|
if job.background {
|
||||||
lines = append(lines, mutedStyle.Render("Transfer continues in background"))
|
lines = append(lines, mutedStyle.Render("Transfer continues in background"))
|
||||||
}
|
}
|
||||||
|
|
@ -4436,13 +4480,7 @@ func renderArchiveProgressModal(job archiveJobState, palette theme.Palette, widt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = append(lines,
|
lines = append(lines, spacer, renderModalNoteLine("Background / b, Cancel / c", contentWidth, palette, mutedStyle))
|
||||||
spacer,
|
|
||||||
lipgloss.NewStyle().Width(contentWidth).Background(palette.Panel).Render(
|
|
||||||
lipgloss.NewStyle().Foreground(palette.Info).Background(palette.Panel).Render("Background / b")+" "+
|
|
||||||
lipgloss.NewStyle().Foreground(palette.CancelButton).Background(palette.Panel).Render("Cancel / c"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if job.background {
|
if job.background {
|
||||||
switch job.kind {
|
switch job.kind {
|
||||||
case "extract":
|
case "extract":
|
||||||
|
|
@ -4933,12 +4971,6 @@ func dismissYankFlashCmd(delay time.Duration) tea.Cmd {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func autoRefreshTickCmd(seconds int) tea.Cmd {
|
|
||||||
return tea.Tick(time.Duration(seconds)*time.Second, func(time.Time) tea.Msg {
|
|
||||||
return tickMsg{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) startCopyJob(kind fileOpKind, sourcePaths []string, targetDir string, overwrite bool, stats vfs.TransferStats) tea.Cmd {
|
func (m *Model) startCopyJob(kind fileOpKind, sourcePaths []string, targetDir string, overwrite bool, stats vfs.TransferStats) tea.Cmd {
|
||||||
m.nextCopyJob++
|
m.nextCopyJob++
|
||||||
jobID := m.nextCopyJob
|
jobID := m.nextCopyJob
|
||||||
|
|
@ -5819,6 +5851,13 @@ func (m *Model) cleanupImageOverlay() {
|
||||||
m.overlay.stop()
|
m.overlay.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) cleanupRemoteImageTemp() {
|
||||||
|
if m.remoteImageTemp != "" {
|
||||||
|
os.Remove(m.remoteImageTemp)
|
||||||
|
m.remoteImageTemp = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Model) hoverIndexFor(pane PaneID) int {
|
func (m *Model) hoverIndexFor(pane PaneID) int {
|
||||||
if m.hover.ok && m.hover.pane == pane {
|
if m.hover.ok && m.hover.pane == pane {
|
||||||
return m.hover.index
|
return m.hover.index
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
Maintainer: Roman Vrubel <roman@vrubel.dev>
|
Maintainer: Roman Vrubel <roman@vrubel.dev>
|
||||||
Depends: ueberzug | ueberzugpp
|
Recommends: ueberzugpp
|
||||||
Description: Terminal file manager inspired by Midnight Commander
|
Description: Terminal file manager inspired by Midnight Commander
|
||||||
A two-pane terminal file manager with inspect mode and text previews.
|
A two-pane terminal file manager with inspect mode and text previews.
|
||||||
EOF
|
EOF
|
||||||
|
|
|
||||||
62
scripts/build-rpm.sh
Executable file
62
scripts/build-rpm.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "usage: $0 <version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
version="$1"
|
||||||
|
pkgname="vcom"
|
||||||
|
outdir="target/rpm"
|
||||||
|
buildroot="${outdir}/BUILDROOT"
|
||||||
|
rpmbuild_dir="${outdir}/rpmbuild"
|
||||||
|
|
||||||
|
rm -rf "$outdir"
|
||||||
|
mkdir -p \
|
||||||
|
"${buildroot}/usr/bin" \
|
||||||
|
"${buildroot}/usr/share/doc/vcom" \
|
||||||
|
"${buildroot}/usr/share/licenses/vcom" \
|
||||||
|
"${rpmbuild_dir}"/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
||||||
|
|
||||||
|
install -Dm755 "target/release/vcom" "${buildroot}/usr/bin/vcom"
|
||||||
|
install -Dm644 "README.md" "${buildroot}/usr/share/doc/vcom/README.md"
|
||||||
|
install -Dm644 "vcom.toml" "${buildroot}/usr/share/doc/vcom/vcom.toml"
|
||||||
|
install -Dm644 "LICENSE" "${buildroot}/usr/share/licenses/vcom/LICENSE"
|
||||||
|
|
||||||
|
cat > "${rpmbuild_dir}/SPECS/vcom.spec" <<EOF
|
||||||
|
Name: vcom
|
||||||
|
Version: ${version}
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: Terminal file manager inspired by Midnight Commander
|
||||||
|
License: GPLv3
|
||||||
|
URL: https://github.com/vrubelroman/vcom
|
||||||
|
|
||||||
|
BuildArch: x86_64
|
||||||
|
Recommends: ueberzugpp
|
||||||
|
|
||||||
|
%description
|
||||||
|
A two-pane terminal file manager with inspect mode and text previews.
|
||||||
|
|
||||||
|
%files
|
||||||
|
%dir /usr/share/doc/vcom
|
||||||
|
%dir /usr/share/licenses/vcom
|
||||||
|
/usr/bin/vcom
|
||||||
|
/usr/share/doc/vcom/README.md
|
||||||
|
/usr/share/doc/vcom/vcom.toml
|
||||||
|
/usr/share/licenses/vcom/LICENSE
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rpmbuild \
|
||||||
|
--define "_topdir $(realpath "${rpmbuild_dir}")" \
|
||||||
|
--define "_buildroot ${buildroot}" \
|
||||||
|
--buildroot "$(realpath "${buildroot}")" \
|
||||||
|
-bb \
|
||||||
|
--noclean \
|
||||||
|
"${rpmbuild_dir}/SPECS/vcom.spec"
|
||||||
|
|
||||||
|
rpm_path=$(find "${rpmbuild_dir}/RPMS" -name "*.rpm" | head -1)
|
||||||
|
cp "$rpm_path" "${outdir}/${pkgname}-${version}-1.x86_64.rpm"
|
||||||
|
echo "Built: ${outdir}/${pkgname}-${version}-1.x86_64.rpm"
|
||||||
192
scripts/install.sh
Executable file
192
scripts/install.sh
Executable file
|
|
@ -0,0 +1,192 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO="vrubelroman/vcom"
|
||||||
|
FONT_DIR="$HOME/.local/share/fonts/JetBrainsMonoNerd"
|
||||||
|
FONT_URL="https://github.com/ryanoasis/nerd-fonts/releases/latest/download/JetBrainsMono.zip"
|
||||||
|
|
||||||
|
BOLD="\033[1m"
|
||||||
|
GREEN="\033[32m"
|
||||||
|
YELLOW="\033[33m"
|
||||||
|
RESET="\033[0m"
|
||||||
|
|
||||||
|
info() { echo -e "${GREEN}→${RESET} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}→${RESET} $*"; }
|
||||||
|
header() { echo -e "\n${BOLD}== $* ==${RESET}"; }
|
||||||
|
|
||||||
|
# ---------- detect distro ----------
|
||||||
|
detect_distro() {
|
||||||
|
if [ -f /etc/os-release ]; then
|
||||||
|
. /etc/os-release
|
||||||
|
echo "${ID}"
|
||||||
|
elif command -v nixos-version >/dev/null 2>&1; then
|
||||||
|
echo "nixos"
|
||||||
|
else
|
||||||
|
echo "unknown"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- latest release tag ----------
|
||||||
|
latest_tag() {
|
||||||
|
curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null \
|
||||||
|
| grep '"tag_name":' | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- nerd font ----------
|
||||||
|
install_nerd_font() {
|
||||||
|
header "Nerd Font"
|
||||||
|
if fc-list 2>/dev/null | grep -qi "JetBrainsMono.*Nerd"; then
|
||||||
|
info "JetBrainsMono Nerd Font already installed"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
DISTRO=$(detect_distro)
|
||||||
|
case "$DISTRO" in
|
||||||
|
arch|manjaro|endeavouros)
|
||||||
|
info "Installing via pacman..."
|
||||||
|
sudo pacman -S --noconfirm ttf-jetbrains-mono-nerd 2>/dev/null || true
|
||||||
|
;;
|
||||||
|
nixos|nix)
|
||||||
|
if command -v nix >/dev/null 2>&1; then
|
||||||
|
info "Installing via nix profile..."
|
||||||
|
nix profile install nixpkgs#nerd-fonts.jetbrains-mono 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if fc-list 2>/dev/null | grep -qi "JetBrainsMono.*Nerd"; then
|
||||||
|
info "JetBrainsMono Nerd Font installed via package manager"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
info "Downloading JetBrainsMono Nerd Font..."
|
||||||
|
mkdir -p "$FONT_DIR"
|
||||||
|
curl -fsSL "$FONT_URL" -o /tmp/JetBrainsMono.zip
|
||||||
|
unzip -oq /tmp/JetBrainsMono.zip -d "$FONT_DIR"
|
||||||
|
rm -f /tmp/JetBrainsMono.zip
|
||||||
|
if command -v fc-cache >/dev/null 2>&1; then
|
||||||
|
fc-cache -fv "$FONT_DIR" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
info "Nerd Font installed to $FONT_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- ueberzugpp ----------
|
||||||
|
install_ueberzugpp() {
|
||||||
|
header "ueberzugpp (image preview)"
|
||||||
|
if command -v ueberzugpp >/dev/null 2>&1; then
|
||||||
|
info "ueberzugpp already installed"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
DISTRO=$(detect_distro)
|
||||||
|
case "$DISTRO" in
|
||||||
|
arch|manjaro|endeavouros)
|
||||||
|
sudo pacman -S --noconfirm ueberzugpp 2>/dev/null && return || true
|
||||||
|
;;
|
||||||
|
fedora|rhel|centos|almalinux|rocky)
|
||||||
|
sudo dnf install -y ueberzugpp 2>/dev/null && return || true
|
||||||
|
;;
|
||||||
|
nixos|nix)
|
||||||
|
info "ueberzugpp is bundled with vcom on Nix — skipping"
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if ! command -v pipx >/dev/null 2>&1; then
|
||||||
|
info "Installing pipx..."
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq pipx 2>/dev/null || \
|
||||||
|
sudo dnf install -y pipx 2>/dev/null || \
|
||||||
|
sudo zypper install -y pipx 2>/dev/null || true
|
||||||
|
pipx ensurepath 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if command -v pipx >/dev/null 2>&1; then
|
||||||
|
pipx install ueberzugpp 2>/dev/null && return || true
|
||||||
|
fi
|
||||||
|
warn "Could not install ueberzugpp — image preview may not work outside Kitty"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- vcom ----------
|
||||||
|
install_vcom() {
|
||||||
|
header "vcom"
|
||||||
|
TAG=$(latest_tag)
|
||||||
|
if [ -z "$TAG" ]; then
|
||||||
|
warn "Could not determine latest version from GitHub"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
VER="${TAG#v}"
|
||||||
|
DISTRO=$(detect_distro)
|
||||||
|
|
||||||
|
case "$DISTRO" in
|
||||||
|
debian|ubuntu|linuxmint|pop|elementary|zorin|kali|raspbian)
|
||||||
|
DEB="vcom_${VER}_amd64.deb"
|
||||||
|
URL="https://github.com/${REPO}/releases/download/${TAG}/${DEB}"
|
||||||
|
info "Downloading $DEB for $DISTRO..."
|
||||||
|
curl -fsSL "$URL" -o "/tmp/${DEB}"
|
||||||
|
sudo apt install -y "/tmp/${DEB}"
|
||||||
|
rm -f "/tmp/${DEB}"
|
||||||
|
;;
|
||||||
|
fedora|rhel|centos|almalinux|rocky)
|
||||||
|
RPM="vcom-${VER}-1.x86_64.rpm"
|
||||||
|
URL="https://github.com/${REPO}/releases/download/${TAG}/${RPM}"
|
||||||
|
info "Downloading $RPM for $DISTRO..."
|
||||||
|
curl -fsSL "$URL" -o "/tmp/${RPM}"
|
||||||
|
sudo dnf install -y "/tmp/${RPM}" 2>/dev/null || \
|
||||||
|
sudo rpm -ivh "/tmp/${RPM}"
|
||||||
|
rm -f "/tmp/${RPM}"
|
||||||
|
;;
|
||||||
|
opensuse*|sles)
|
||||||
|
RPM="vcom-${VER}-1.x86_64.rpm"
|
||||||
|
URL="https://github.com/${REPO}/releases/download/${TAG}/${RPM}"
|
||||||
|
info "Downloading $RPM for $DISTRO..."
|
||||||
|
curl -fsSL "$URL" -o "/tmp/${RPM}"
|
||||||
|
sudo zypper install -y "/tmp/${RPM}"
|
||||||
|
rm -f "/tmp/${RPM}"
|
||||||
|
;;
|
||||||
|
arch|manjaro|endeavouros)
|
||||||
|
TAR="vcom-${TAG}-x86_64-unknown-linux-gnu.tar.gz"
|
||||||
|
URL="https://github.com/${REPO}/releases/download/${TAG}/${TAR}"
|
||||||
|
info "Downloading $TAR for $DISTRO..."
|
||||||
|
curl -fsSL "$URL" -o "/tmp/${TAR}"
|
||||||
|
mkdir -p "$HOME/.local/bin"
|
||||||
|
tar -xzf "/tmp/${TAR}" -C "$HOME/.local/bin"
|
||||||
|
rm -f "/tmp/${TAR}"
|
||||||
|
if ! echo "$PATH" | grep -q "$HOME/.local/bin"; then
|
||||||
|
warn "Add ~/.local/bin to your PATH:"
|
||||||
|
echo ' export PATH="$HOME/.local/bin:$PATH"'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
nixos|nix)
|
||||||
|
info "Installing via nix profile..."
|
||||||
|
nix profile add "github:${REPO}?ref=${TAG}"
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
TAR="vcom-${TAG}-x86_64-unknown-linux-gnu.tar.gz"
|
||||||
|
URL="https://github.com/${REPO}/releases/download/${TAG}/${TAR}"
|
||||||
|
info "Unknown distro ($DISTRO) — installing binary tarball"
|
||||||
|
curl -fsSL "$URL" -o "/tmp/${TAR}"
|
||||||
|
mkdir -p "$HOME/.local/bin"
|
||||||
|
tar -xzf "/tmp/${TAR}" -C "$HOME/.local/bin"
|
||||||
|
rm -f "/tmp/${TAR}"
|
||||||
|
warn "Add ~/.local/bin to your PATH if not already:"
|
||||||
|
echo ' export PATH="$HOME/.local/bin:$PATH"'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
info "vcom ${TAG} installed successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- main ----------
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}vcom installer${RESET}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
|
echo "error: curl is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v unzip >/dev/null 2>&1; then
|
||||||
|
echo "error: unzip is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
install_nerd_font
|
||||||
|
install_ueberzugpp
|
||||||
|
install_vcom
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
info "Done! Run: vcom"
|
||||||
1
src/vcom-0.2.5.tar.gz
Symbolic link
1
src/vcom-0.2.5.tar.gz
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/home/vrubel/projects/vcom/vcom-0.2.5.tar.gz
|
||||||
0
src/vcom-0.2.5/.codex
Normal file
0
src/vcom-0.2.5/.codex
Normal file
86
src/vcom-0.2.5/.github/workflows/release.yml
vendored
Normal file
86
src/vcom-0.2.5/.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
BIN_NAME: vcom
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: GOFLAGS=-mod=vendor go test ./...
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y dpkg-dev
|
||||||
|
|
||||||
|
- name: Derive release version
|
||||||
|
shell: bash
|
||||||
|
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Vendor Go modules
|
||||||
|
run: go mod vendor
|
||||||
|
|
||||||
|
- name: Build release binary
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p target/release
|
||||||
|
GOFLAGS=-mod=vendor CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o "target/release/${BIN_NAME}" ./cmd/vcom
|
||||||
|
|
||||||
|
- name: Build deb package
|
||||||
|
shell: bash
|
||||||
|
run: ./scripts/build-deb.sh "${VERSION}"
|
||||||
|
|
||||||
|
- name: Bundle tarball
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
tar -C target/release -czf "${BIN_NAME}-${GITHUB_REF_NAME}-x86_64-unknown-linux-gnu.tar.gz" "${BIN_NAME}"
|
||||||
|
|
||||||
|
- name: Generate checksums
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
sha256sum \
|
||||||
|
"${BIN_NAME}-${GITHUB_REF_NAME}-x86_64-unknown-linux-gnu.tar.gz" \
|
||||||
|
"target/debian/${BIN_NAME}_${VERSION}_amd64.deb" \
|
||||||
|
> "${BIN_NAME}-${GITHUB_REF_NAME}-checksums.txt"
|
||||||
|
|
||||||
|
- name: Publish release assets
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
generate_release_notes: true
|
||||||
|
files: |
|
||||||
|
vcom-${{ github.ref_name }}-x86_64-unknown-linux-gnu.tar.gz
|
||||||
|
vcom-${{ github.ref_name }}-checksums.txt
|
||||||
|
target/debian/vcom_${{ env.VERSION }}_amd64.deb
|
||||||
2
src/vcom-0.2.5/.gitignore
vendored
Normal file
2
src/vcom-0.2.5/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
target/
|
||||||
|
vcom
|
||||||
674
src/vcom-0.2.5/LICENSE
Normal file
674
src/vcom-0.2.5/LICENSE
Normal file
|
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
34
src/vcom-0.2.5/PKGBUILD
Normal file
34
src/vcom-0.2.5/PKGBUILD
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
pkgname=vcom
|
||||||
|
pkgver=0.2.5
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="Terminal file manager inspired by Midnight Commander"
|
||||||
|
arch=("x86_64" "aarch64")
|
||||||
|
url="https://github.com/vrubelroman/vcom"
|
||||||
|
license=("MIT")
|
||||||
|
depends=("glibc" "ueberzugpp")
|
||||||
|
makedepends=("go")
|
||||||
|
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
|
||||||
|
sha256sums=("SKIP")
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "$srcdir/$pkgname-$pkgver"
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
export GOFLAGS="-mod=vendor -trimpath"
|
||||||
|
go build -ldflags="-s -w" -o "target/release/vcom" ./cmd/vcom
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
cd "$srcdir/$pkgname-$pkgver"
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
export GOFLAGS="-mod=vendor"
|
||||||
|
go test ./...
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "$srcdir/$pkgname-$pkgver"
|
||||||
|
|
||||||
|
install -Dm755 "target/release/vcom" "$pkgdir/usr/bin/vcom"
|
||||||
|
install -Dm644 "README.md" "$pkgdir/usr/share/doc/vcom/README.md"
|
||||||
|
install -Dm644 "vcom.toml" "$pkgdir/usr/share/doc/vcom/vcom.toml"
|
||||||
|
install -Dm644 "LICENSE" "$pkgdir/usr/share/licenses/vcom/LICENSE"
|
||||||
|
}
|
||||||
204
src/vcom-0.2.5/README.md
Normal file
204
src/vcom-0.2.5/README.md
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
# vcom
|
||||||
|
|
||||||
|
`vcom` is a two-pane terminal file manager with a fast built-in info/preview panel for the active selection.
|
||||||
|
|
||||||
|
## Why vcom
|
||||||
|
|
||||||
|
- Two-pane workflow focused on keyboard speed
|
||||||
|
- Built-in preview/info pane for the active selection
|
||||||
|
- Asynchronous copy/move with progress and background mode
|
||||||
|
- Theme support and configurable layout/columns
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Build and run
|
||||||
|
|
||||||
|
Run directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/vcom
|
||||||
|
```
|
||||||
|
|
||||||
|
Build local binary:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -o vcom ./cmd/vcom
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### NixOS / Nix
|
||||||
|
|
||||||
|
Run directly from the flake:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run github:vrubelroman/vcom?ref=v0.2.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Install into user profile:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix profile add github:vrubelroman/vcom?ref=v0.2.5
|
||||||
|
```
|
||||||
|
|
||||||
|
The Nix package wraps `vcom` with `ueberzugpp` in `PATH`, so image preview works in non-`kitty` terminals out of the box.
|
||||||
|
|
||||||
|
### Debian / Ubuntu
|
||||||
|
|
||||||
|
Download and install the latest release:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sL https://github.com/vrubelroman/vcom/releases/download/v0.2.5/vcom_0.2.4_amd64.deb -o /tmp/vcom_0.2.4_amd64.deb
|
||||||
|
sudo apt install /tmp/vcom_0.2.4_amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
The Debian package declares `ueberzug` (or `ueberzugpp` where available) as a dependency for image preview outside `kitty`.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
A `PKGBUILD` is included in the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
makepkg -si
|
||||||
|
```
|
||||||
|
|
||||||
|
The Arch package depends on `ueberzugpp`, so non-`kitty` image preview is installed together with `vcom`.
|
||||||
|
|
||||||
|
## Font requirement (icons)
|
||||||
|
|
||||||
|
For file icons, `vcom` expects a Nerd Font in your terminal profile.
|
||||||
|
|
||||||
|
Default behavior is `ui.icon_mode = "auto"`:
|
||||||
|
|
||||||
|
- if a Nerd Font is detected, `vcom` uses Nerd icons
|
||||||
|
- if not, `vcom` falls back to ASCII icons automatically
|
||||||
|
|
||||||
|
You can force behavior in config:
|
||||||
|
|
||||||
|
- `ui.icon_mode = "nerd"`: always use Nerd icons
|
||||||
|
- `ui.icon_mode = "ascii"`: always use ASCII icons
|
||||||
|
|
||||||
|
### Installing a Nerd Font
|
||||||
|
|
||||||
|
**Ubuntu / Debian:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget -qO /tmp/JetBrainsMono.zip https://github.com/ryanoasis/nerd-fonts/releases/latest/download/JetBrainsMono.zip
|
||||||
|
mkdir -p ~/.local/share/fonts/JetBrainsMonoNerd
|
||||||
|
unzip -o /tmp/JetBrainsMono.zip -d ~/.local/share/fonts/JetBrainsMonoNerd
|
||||||
|
fc-cache -fv
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arch Linux:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo pacman -S ttf-jetbrains-mono-nerd
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via AUR helper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yay -S nerd-fonts-jetbrains-mono
|
||||||
|
```
|
||||||
|
|
||||||
|
**NixOS / Nix:**
|
||||||
|
|
||||||
|
Add to your `/etc/nixos/configuration.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
fonts.packages = with pkgs; [ nerd-fonts.jetbrains-mono ];
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install imperatively:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix profile install nixpkgs#nerd-fonts.jetbrains-mono
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuring terminal to use the installed Nerd Font
|
||||||
|
|
||||||
|
After installing, set `JetBrainsMono Nerd Font` (or another Nerd Font) as the terminal font:
|
||||||
|
|
||||||
|
- **GNOME Terminal:** `Preferences → Profile → Text → Custom font` → choose `JetBrainsMono Nerd Font`
|
||||||
|
- **Konsole:** `Settings → Edit Current Profile → Appearance` → choose a Nerd Font profile
|
||||||
|
- **Alacritty:** set `font.normal.family: "JetBrainsMono Nerd Font"` in `~/.config/alacritty/alacritty.yml`
|
||||||
|
- **Kitty:** set `font_family JetBrainsMono Nerd Font` in `~/.config/kitty/kitty.conf`
|
||||||
|
- **Foot:** set `font=JetBrainsMono Nerd Font:size=11` in `~/.config/foot/foot.ini`
|
||||||
|
- **WezTerm:** set `font = wezterm.font("JetBrainsMono Nerd Font")` in `~/.config/wezterm/wezterm.lua`
|
||||||
|
- **Windows Terminal:** `Settings → Profiles → Defaults → Appearance → Font face` → choose `JetBrainsMono Nerd Font`
|
||||||
|
|
||||||
|
Preview mode (`F9` / `i`) temporarily replaces the inactive pane and shows:
|
||||||
|
|
||||||
|
- directory listing preview
|
||||||
|
- text file preview with syntax highlighting
|
||||||
|
- image metadata (format + dimensions)
|
||||||
|
- safe fallback for binary files
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Optional config lookup order:
|
||||||
|
|
||||||
|
1. `-config /path/to/vcom.toml`
|
||||||
|
2. `./vcom.toml`
|
||||||
|
3. `./config/vcom.toml`
|
||||||
|
4. `$XDG_CONFIG_HOME/vcom/vcom.toml`
|
||||||
|
5. `~/.config/vcom/vcom.toml`
|
||||||
|
|
||||||
|
Reference config: [vcom.toml](/home/vrubel/projects/vcom/vcom.toml)
|
||||||
|
|
||||||
|
Icon mode example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[ui]
|
||||||
|
icon_mode = "auto" # auto | nerd | ascii
|
||||||
|
```
|
||||||
|
|
||||||
|
## Themes
|
||||||
|
|
||||||
|
Built-in themes (press `t` to open theme selector or set `ui.theme` in config):
|
||||||
|
|
||||||
|
- `catppuccin-mocha` (default)
|
||||||
|
- `catppuccin-macchiato`
|
||||||
|
- `catppuccin-lavender`
|
||||||
|
- `tokyo-night`
|
||||||
|
- `gruvbox-dark`
|
||||||
|
- `nord`
|
||||||
|
- `one-dark`
|
||||||
|
- `everforest`
|
||||||
|
- `github-dark`
|
||||||
|
- `ayu-dark`
|
||||||
|
- `breeze`
|
||||||
|
- `cyberpunk`
|
||||||
|
- `dracula`
|
||||||
|
- `eldritch`
|
||||||
|
- `kanagawa`
|
||||||
|
- `kanagawa-paper`
|
||||||
|
- `rose-pine`
|
||||||
|
- `solarized-dark`
|
||||||
|
- `vesper`
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
Pushing a tag like `v0.2.5` triggers GitHub Actions release workflow (`.github/workflows/release.yml`) which:
|
||||||
|
|
||||||
|
- runs tests
|
||||||
|
- vendors Go modules
|
||||||
|
- builds release binary
|
||||||
|
- builds Debian package
|
||||||
|
- publishes release assets
|
||||||
|
|
||||||
|
Release artifacts:
|
||||||
|
|
||||||
|
- `vcom-v0.2.5-x86_64-unknown-linux-gnu.tar.gz`
|
||||||
|
- `vcom_0.2.4_amd64.deb`
|
||||||
|
- `vcom-v0.2.5-checksums.txt`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- File creation time depends on filesystem/OS support; unavailable values are shown as `n/a`.
|
||||||
|
|
||||||
|
Architecture notes: [docs/architecture.md](/home/vrubel/projects/vcom/docs/architecture.md)
|
||||||
196
src/vcom-0.2.5/docs/architecture.md
Normal file
196
src/vcom-0.2.5/docs/architecture.md
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
# Architecture
|
||||||
|
|
||||||
|
## Why this shape
|
||||||
|
|
||||||
|
The project should not become a giant `main.go` that mixes rendering, key handling, filesystem I/O and business rules. The architecture is split so the UI remains reactive and file operations stay isolated.
|
||||||
|
|
||||||
|
## High-level modules
|
||||||
|
|
||||||
|
- `cmd/vcom`
|
||||||
|
Entry point, config loading, program startup.
|
||||||
|
- `internal/config`
|
||||||
|
Config schema, defaults, search paths and TOML parsing.
|
||||||
|
- `internal/fs`
|
||||||
|
Filesystem model, directory scanning, previews and file operations.
|
||||||
|
- `internal/theme`
|
||||||
|
Theme presets and style tokens.
|
||||||
|
- `internal/ui`
|
||||||
|
Bubble Tea model, key map, pane rendering, modal flow and layout.
|
||||||
|
|
||||||
|
## State model
|
||||||
|
|
||||||
|
The Bubble Tea root model owns:
|
||||||
|
|
||||||
|
- terminal dimensions
|
||||||
|
- full config
|
||||||
|
- active browser pane
|
||||||
|
- left and right pane state
|
||||||
|
- center preview state
|
||||||
|
- transient modal state
|
||||||
|
- busy/status state for async work
|
||||||
|
|
||||||
|
This keeps the Elm-style update loop simple:
|
||||||
|
|
||||||
|
1. key or resize event arrives
|
||||||
|
2. model decides whether to mutate local state or start async work
|
||||||
|
3. async work returns a typed message
|
||||||
|
4. view renders from plain state
|
||||||
|
|
||||||
|
## Pane model
|
||||||
|
|
||||||
|
Each side pane stores:
|
||||||
|
|
||||||
|
- current path
|
||||||
|
- scanned entries
|
||||||
|
- cursor index
|
||||||
|
- scroll offset
|
||||||
|
- cached directory sizes for entries calculated on demand
|
||||||
|
|
||||||
|
This is intentionally independent from rendering details, so the pane can be unit-tested later without Lip Gloss.
|
||||||
|
|
||||||
|
## Preview pipeline
|
||||||
|
|
||||||
|
The center pane is driven only by the current selection from the active side pane.
|
||||||
|
|
||||||
|
Preview strategies:
|
||||||
|
|
||||||
|
- directory preview
|
||||||
|
Shows child entries and summary data.
|
||||||
|
- text preview
|
||||||
|
Reads up to a configured byte limit and displays text safely.
|
||||||
|
- image preview
|
||||||
|
Detects dimensions and format through standard Go image decoders.
|
||||||
|
- binary fallback
|
||||||
|
Avoids dumping junk bytes into the terminal.
|
||||||
|
|
||||||
|
Metadata shown above the preview:
|
||||||
|
|
||||||
|
- kind
|
||||||
|
- full path
|
||||||
|
- size
|
||||||
|
- created time
|
||||||
|
- modified time
|
||||||
|
- permissions
|
||||||
|
|
||||||
|
Directory size is expensive, so it is not calculated eagerly. Pressing `Space` starts an async scan and updates both:
|
||||||
|
|
||||||
|
- side-pane size column
|
||||||
|
- preview metadata widget
|
||||||
|
|
||||||
|
## Configuration design
|
||||||
|
|
||||||
|
TOML is used because:
|
||||||
|
|
||||||
|
- it is readable in terminal workflows
|
||||||
|
- comments are first-class
|
||||||
|
- optional settings can stay commented out for manual enable/disable
|
||||||
|
|
||||||
|
The example config enables only MC-like columns by default:
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- `size`
|
||||||
|
- `modified`
|
||||||
|
|
||||||
|
Additional columns are left commented out so users can literally uncomment them:
|
||||||
|
|
||||||
|
- `created`
|
||||||
|
- `permissions`
|
||||||
|
- `extension`
|
||||||
|
|
||||||
|
Other useful config areas:
|
||||||
|
|
||||||
|
- startup paths for left and right panes
|
||||||
|
- theme preset
|
||||||
|
- hidden file visibility
|
||||||
|
- sort field and reverse mode
|
||||||
|
- center pane width ratio
|
||||||
|
- confirmation behavior
|
||||||
|
- preview byte limits
|
||||||
|
- text wrapping in preview
|
||||||
|
- compact path display
|
||||||
|
|
||||||
|
## Rendering strategy
|
||||||
|
|
||||||
|
The side panes are custom-rendered rather than built on top of the generic table bubble.
|
||||||
|
|
||||||
|
Reasoning:
|
||||||
|
|
||||||
|
- MC-like directory panes are not just tables
|
||||||
|
- we need tight control over selection styling, truncation and empty states
|
||||||
|
- the center pane uses a different rendering model than the side panes
|
||||||
|
|
||||||
|
Bubble usage:
|
||||||
|
|
||||||
|
- `bubbletea`
|
||||||
|
event loop and async commands
|
||||||
|
- `bubbles/key`
|
||||||
|
declarative key bindings
|
||||||
|
- `bubbles/textinput`
|
||||||
|
mkdir modal
|
||||||
|
- `bubbles/viewport`
|
||||||
|
preview scrolling surface
|
||||||
|
- `lipgloss`
|
||||||
|
all layout and styling
|
||||||
|
|
||||||
|
## Operations
|
||||||
|
|
||||||
|
The first operational slice is intentionally MC-core:
|
||||||
|
|
||||||
|
- copy selected entry to passive pane directory
|
||||||
|
- move selected entry to passive pane directory
|
||||||
|
- create directory in active pane
|
||||||
|
- delete selected entry
|
||||||
|
- refresh active pane
|
||||||
|
|
||||||
|
These operations are dispatched asynchronously to avoid freezing the TUI on large directories.
|
||||||
|
|
||||||
|
Overwrite handling is decided before the async operation begins:
|
||||||
|
|
||||||
|
- if the target does not exist, the operation runs directly
|
||||||
|
- if the target exists and overwrite confirmation is enabled, the UI opens a modal
|
||||||
|
- if overwrite confirmation is disabled, the operation replaces the target immediately
|
||||||
|
|
||||||
|
## Theme system
|
||||||
|
|
||||||
|
Themes are token-based rather than style-object based.
|
||||||
|
|
||||||
|
Each preset defines semantic colors:
|
||||||
|
|
||||||
|
- background
|
||||||
|
- panel
|
||||||
|
- panel inactive
|
||||||
|
- border
|
||||||
|
- border active
|
||||||
|
- text
|
||||||
|
- muted text
|
||||||
|
- accent
|
||||||
|
- selection
|
||||||
|
- warning
|
||||||
|
- danger
|
||||||
|
- footer key
|
||||||
|
|
||||||
|
This allows future user-defined themes without touching UI logic.
|
||||||
|
|
||||||
|
The current UI also supports runtime theme cycling, but config remains the source of truth for default startup appearance.
|
||||||
|
|
||||||
|
## Extension points
|
||||||
|
|
||||||
|
The architecture is designed to accept later additions without a rewrite:
|
||||||
|
|
||||||
|
- multi-select and batch operations
|
||||||
|
- file viewer/editor integration
|
||||||
|
- terminal image protocols
|
||||||
|
- tab history per pane
|
||||||
|
- bookmarks and quick-jump
|
||||||
|
- sort modes
|
||||||
|
- archive browsing
|
||||||
|
- search/filter overlay
|
||||||
|
- pluggable preview providers
|
||||||
|
|
||||||
|
## Constraints worth keeping
|
||||||
|
|
||||||
|
- never calculate directory sizes during normal navigation
|
||||||
|
- never read full large files into memory for preview
|
||||||
|
- keep filesystem code out of `View()`
|
||||||
|
- keep styling decisions out of `internal/fs`
|
||||||
|
- prefer typed Tea messages over stringly-typed status events
|
||||||
128
src/vcom-0.2.5/docs/project-description.md
Normal file
128
src/vcom-0.2.5/docs/project-description.md
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# vcom: описание проекта и реализованного функционала
|
||||||
|
|
||||||
|
`vcom` — терминальный файловый менеджер в стиле Midnight Commander, написанный на Go на базе Bubble Tea.
|
||||||
|
|
||||||
|
## 1. Основная концепция
|
||||||
|
|
||||||
|
Приложение работает в двухпанельном режиме:
|
||||||
|
- левая файловая панель,
|
||||||
|
- правая файловая панель.
|
||||||
|
|
||||||
|
Активная панель управляется с клавиатуры и мыши, неактивная сохраняет своё состояние (путь, позицию курсора).
|
||||||
|
|
||||||
|
## 2. Реализованные режимы интерфейса
|
||||||
|
|
||||||
|
- Двухпанельный браузер директорий.
|
||||||
|
- Режим Info/Preview (`i`): неактивная панель временно заменяется превью выбранного элемента из активной панели.
|
||||||
|
- Режим выделения текста в превью (`Ctrl+t`) для текстовых файлов.
|
||||||
|
- Модальные окна (подтверждения, прогресс операций, help, уведомления).
|
||||||
|
|
||||||
|
## 3. Навигация и просмотр
|
||||||
|
|
||||||
|
- Перемещение по списку: `j/k`, `Up/Down`, `PgUp/PgDn`.
|
||||||
|
- Переключение активной панели: `Tab`, `h`, `l`.
|
||||||
|
- Вход в директорию: `Enter` / `Right`.
|
||||||
|
- Переход в родительскую директорию: `Backspace` / `Left`.
|
||||||
|
- Обновление панелей: `r`.
|
||||||
|
- Внешний просмотр файла: `F3` (`$PAGER` при наличии).
|
||||||
|
- Внешнее редактирование файла: `F4` (`$VISUAL/$EDITOR` или fallback-редакторы).
|
||||||
|
|
||||||
|
## 4. Операции с файлами и директориями
|
||||||
|
|
||||||
|
- `F5` — копирование.
|
||||||
|
- `F6` — перемещение.
|
||||||
|
- `F7` — создание директории.
|
||||||
|
- `F8` — удаление.
|
||||||
|
|
||||||
|
Операции copy/move реализованы с:
|
||||||
|
- предварительным диалогом подтверждения,
|
||||||
|
- подсчётом объёма и количества файлов,
|
||||||
|
- прогрессом по байтам и по количеству файлов,
|
||||||
|
- возможностью отправить операцию в фон (`b`),
|
||||||
|
- уведомлением о завершении фоновой операции.
|
||||||
|
|
||||||
|
Подтверждение overwrite учитывается для существующих целей.
|
||||||
|
|
||||||
|
## 5. Мультивыделение
|
||||||
|
|
||||||
|
Реализовано выделение элементов с клавиатуры:
|
||||||
|
- `Shift+Up/Shift+Down` (а также `Shift+K/Shift+J`) добавляют/снимают выделение на текущем проходе.
|
||||||
|
- Повторный проход по уже выделенному элементу снимает его выделение (toggle).
|
||||||
|
- `Esc` очищает выделение активной панели.
|
||||||
|
|
||||||
|
Если есть выделенные элементы, `F5/F6/F8` применяются ко всему выделенному набору.
|
||||||
|
Если выделения нет — операция применяется к текущему элементу под курсором.
|
||||||
|
|
||||||
|
## 6. Работа мыши
|
||||||
|
|
||||||
|
- ЛКМ: выбор элемента и активация панели.
|
||||||
|
- Двойной ЛКМ: открытие элемента.
|
||||||
|
- ПКМ: переключение режима Info/Preview для выбранного элемента.
|
||||||
|
- Колесо мыши: прокрутка списка; в preview-области — прокрутка содержимого превью.
|
||||||
|
|
||||||
|
## 7. Help-окно
|
||||||
|
|
||||||
|
`F1` или `?` открывает справку по управлению.
|
||||||
|
|
||||||
|
Особенности help:
|
||||||
|
- логические блоки (Navigation, View and Panels, Dialogs and Transfers, Mouse),
|
||||||
|
- цветовое оформление заголовков и элементов на основе активной темы,
|
||||||
|
- закрытие по `F1`, `?`, `Esc`, `Enter`, `q`.
|
||||||
|
|
||||||
|
## 8. Модальные окна и закрытие
|
||||||
|
|
||||||
|
Во всех модальных окнах поддержано закрытие по `q`.
|
||||||
|
|
||||||
|
Поведение в прогрессе copy/move:
|
||||||
|
- `q` не прерывает операцию,
|
||||||
|
- окно закрывается,
|
||||||
|
- операция продолжается в фоне.
|
||||||
|
|
||||||
|
## 9. Визуальные доработки
|
||||||
|
|
||||||
|
- Убрана верхняя title-строка приложения.
|
||||||
|
- Убраны текстовые лейблы `LEFT/RIGHT` в заголовках панелей.
|
||||||
|
- Убрана строка `CONTENT` в preview-панели.
|
||||||
|
- Путь активной панели сделан жирным и в цвете `TextFile` текущей темы.
|
||||||
|
- Подсветка курсора отображается только в активной панели.
|
||||||
|
- Выделенные (marked) элементы подсвечиваются цветом `Danger` темы по всей строке.
|
||||||
|
|
||||||
|
## 10. Конфигурация
|
||||||
|
|
||||||
|
Поддерживается TOML-конфиг (`vcom.toml`), включая:
|
||||||
|
- стартовые директории,
|
||||||
|
- визуальные параметры UI,
|
||||||
|
- набор и видимость колонок,
|
||||||
|
- сортировку,
|
||||||
|
- поведение превью,
|
||||||
|
- поведение подтверждений и операций.
|
||||||
|
|
||||||
|
## 11. Технологический стек
|
||||||
|
|
||||||
|
- Go
|
||||||
|
- Bubble Tea (`github.com/charmbracelet/bubbletea`)
|
||||||
|
- Bubbles
|
||||||
|
- Lip Gloss
|
||||||
|
- TOML (`pelletier/go-toml/v2`)
|
||||||
|
|
||||||
|
## 12. Сборка и запуск
|
||||||
|
|
||||||
|
Локально:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/vcom
|
||||||
|
```
|
||||||
|
|
||||||
|
Сборка бинаря:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -o vcom ./cmd/vcom
|
||||||
|
```
|
||||||
|
|
||||||
|
Nix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run .
|
||||||
|
```
|
||||||
|
|
||||||
|
Для запуска тегов из GitHub-репозитория рекомендуется использовать версии с `v0.1.1` и выше.
|
||||||
BIN
src/vcom-0.2.5/docs/screen.png
Normal file
BIN
src/vcom-0.2.5/docs/screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 209 KiB |
BIN
src/vcom-0.2.5/docs/screen2.png
Normal file
BIN
src/vcom-0.2.5/docs/screen2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 358 KiB |
BIN
src/vcom-0.2.5/docs/screen3.png
Normal file
BIN
src/vcom-0.2.5/docs/screen3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 327 KiB |
61
src/vcom-0.2.5/flake.lock
generated
Normal file
61
src/vcom-0.2.5/flake.lock
generated
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776548001,
|
||||||
|
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
59
src/vcom-0.2.5/flake.nix
Normal file
59
src/vcom-0.2.5/flake.nix
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
description = "vcom terminal file manager";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
lib = pkgs.lib;
|
||||||
|
packageBase = pkgs.buildGoModule {
|
||||||
|
pname = "vcom";
|
||||||
|
version = "0.2.5";
|
||||||
|
src = ./.;
|
||||||
|
vendorHash = null;
|
||||||
|
|
||||||
|
subPackages = [ "cmd/vcom" ];
|
||||||
|
|
||||||
|
ldflags = [
|
||||||
|
"-s"
|
||||||
|
"-w"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Terminal file manager inspired by Midnight Commander";
|
||||||
|
homepage = "https://github.com/vrubelroman/vcom";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "vcom";
|
||||||
|
platforms = platforms.linux;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
package = pkgs.symlinkJoin {
|
||||||
|
name = "vcom";
|
||||||
|
paths = [ packageBase ];
|
||||||
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||||
|
postBuild = ''
|
||||||
|
wrapProgram "$out/bin/vcom" \
|
||||||
|
--prefix PATH : "${lib.makeBinPath [ pkgs.ueberzugpp ]}"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
packages.default = package;
|
||||||
|
|
||||||
|
apps.default = {
|
||||||
|
type = "app";
|
||||||
|
program = "${package}/bin/vcom";
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
go
|
||||||
|
gopls
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
36
src/vcom-0.2.5/go.mod
Normal file
36
src/vcom-0.2.5/go.mod
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
module vcom
|
||||||
|
|
||||||
|
go 1.26.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alecthomas/chroma/v2 v2.23.1
|
||||||
|
github.com/charmbracelet/bubbles v0.21.0
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
|
golang.org/x/sys v0.43.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||||
|
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
|
github.com/pkg/sftp v1.13.10 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
|
golang.org/x/crypto v0.50.0 // indirect
|
||||||
|
golang.org/x/text v0.36.0 // indirect
|
||||||
|
)
|
||||||
63
src/vcom-0.2.5/go.sum
Normal file
63
src/vcom-0.2.5/go.sum
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
|
||||||
|
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||||
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||||
|
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
|
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||||
|
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||||
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
|
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
|
||||||
|
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||||
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
273
src/vcom-0.2.5/internal/config/config.go
Normal file
273
src/vcom-0.2.5/internal/config/config.go
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
toml "github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Startup StartupConfig `toml:"startup"`
|
||||||
|
UI UIConfig `toml:"ui"`
|
||||||
|
Browser BrowserConfig `toml:"browser"`
|
||||||
|
Preview PreviewConfig `toml:"preview"`
|
||||||
|
Behavior BehaviorConfig `toml:"behavior"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartupConfig struct {
|
||||||
|
LeftPath string `toml:"left_path"`
|
||||||
|
RightPath string `toml:"right_path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UIConfig struct {
|
||||||
|
AppTitle string `toml:"app_title"`
|
||||||
|
Theme string `toml:"theme"`
|
||||||
|
IconMode string `toml:"icon_mode"`
|
||||||
|
ShowTitleBar bool `toml:"show_title_bar"`
|
||||||
|
ShowFooter bool `toml:"show_footer"`
|
||||||
|
Border string `toml:"border"`
|
||||||
|
PathDisplay string `toml:"path_display"`
|
||||||
|
PaneGap int `toml:"pane_gap"`
|
||||||
|
CenterWidthPercent int `toml:"center_width_percent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BrowserConfig struct {
|
||||||
|
ShowHidden bool `toml:"show_hidden"`
|
||||||
|
DirsFirst bool `toml:"dirs_first"`
|
||||||
|
HumanReadableSize bool `toml:"human_readable_size"`
|
||||||
|
Sort SortConfig `toml:"sort"`
|
||||||
|
Columns BrowserColumnsConfig `toml:"columns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortConfig struct {
|
||||||
|
By string `toml:"by"`
|
||||||
|
Reverse bool `toml:"reverse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BrowserColumnsConfig struct {
|
||||||
|
Name bool `toml:"name"`
|
||||||
|
Size bool `toml:"size"`
|
||||||
|
Modified bool `toml:"modified"`
|
||||||
|
Created bool `toml:"created"`
|
||||||
|
Permissions bool `toml:"permissions"`
|
||||||
|
Extension bool `toml:"extension"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreviewConfig struct {
|
||||||
|
ShowMetadata bool `toml:"show_metadata"`
|
||||||
|
WrapText bool `toml:"wrap_text"`
|
||||||
|
MaxPreviewBytes int64 `toml:"max_preview_bytes"`
|
||||||
|
DirectoryPreviewLimit int `toml:"directory_preview_limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BehaviorConfig struct {
|
||||||
|
ConfirmDelete bool `toml:"confirm_delete"`
|
||||||
|
ConfirmOverwrite bool `toml:"confirm_overwrite"`
|
||||||
|
CalculateDirSizeOnSpace bool `toml:"calculate_dir_size_on_space"`
|
||||||
|
FollowSymlinks bool `toml:"follow_symlinks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Default() Config {
|
||||||
|
return Config{
|
||||||
|
Startup: StartupConfig{},
|
||||||
|
UI: UIConfig{
|
||||||
|
AppTitle: "vcom",
|
||||||
|
Theme: "catppuccin-mocha",
|
||||||
|
IconMode: "auto",
|
||||||
|
ShowTitleBar: true,
|
||||||
|
ShowFooter: true,
|
||||||
|
Border: "rounded",
|
||||||
|
PathDisplay: "short",
|
||||||
|
PaneGap: 1,
|
||||||
|
CenterWidthPercent: 30,
|
||||||
|
},
|
||||||
|
Browser: BrowserConfig{
|
||||||
|
ShowHidden: true,
|
||||||
|
DirsFirst: true,
|
||||||
|
HumanReadableSize: true,
|
||||||
|
Sort: SortConfig{
|
||||||
|
By: "name",
|
||||||
|
Reverse: false,
|
||||||
|
},
|
||||||
|
Columns: BrowserColumnsConfig{
|
||||||
|
Name: true,
|
||||||
|
Size: true,
|
||||||
|
Modified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Preview: PreviewConfig{
|
||||||
|
ShowMetadata: true,
|
||||||
|
WrapText: false,
|
||||||
|
MaxPreviewBytes: 64 * 1024,
|
||||||
|
DirectoryPreviewLimit: 80,
|
||||||
|
},
|
||||||
|
Behavior: BehaviorConfig{
|
||||||
|
ConfirmDelete: true,
|
||||||
|
ConfirmOverwrite: true,
|
||||||
|
CalculateDirSizeOnSpace: true,
|
||||||
|
FollowSymlinks: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load(explicitPath string) (Config, string, error) {
|
||||||
|
cfg := Default()
|
||||||
|
|
||||||
|
path, found, err := resolvePath(explicitPath)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, "", err
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return cfg, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, "", fmt.Errorf("read %s: %w", path, err)
|
||||||
|
}
|
||||||
|
if err := toml.Unmarshal(data, &cfg); err != nil {
|
||||||
|
return Config{}, "", fmt.Errorf("parse %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
return Config{}, "", fmt.Errorf("validate %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Save(cfg Config, path string) (string, error) {
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := strings.TrimSpace(path)
|
||||||
|
if targetPath == "" {
|
||||||
|
var err error
|
||||||
|
targetPath, err = DefaultUserPath()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absPath, err := filepath.Abs(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("resolve %s: %w", targetPath, err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Dir(absPath), 0o755); err != nil {
|
||||||
|
return "", fmt.Errorf("mkdir %s: %w", filepath.Dir(absPath), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := toml.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("marshal config: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(absPath, data, 0o644); err != nil {
|
||||||
|
return "", fmt.Errorf("write %s: %w", absPath, err)
|
||||||
|
}
|
||||||
|
return absPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
if c.UI.Theme == "" {
|
||||||
|
return errors.New("ui.theme must not be empty")
|
||||||
|
}
|
||||||
|
switch strings.ToLower(strings.TrimSpace(c.UI.IconMode)) {
|
||||||
|
case "", "auto":
|
||||||
|
c.UI.IconMode = "auto"
|
||||||
|
case "nerd", "ascii":
|
||||||
|
default:
|
||||||
|
return errors.New("ui.icon_mode must be one of: auto, nerd, ascii")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(c.UI.AppTitle) == "" {
|
||||||
|
c.UI.AppTitle = "vcom"
|
||||||
|
}
|
||||||
|
if c.Preview.MaxPreviewBytes <= 0 {
|
||||||
|
return errors.New("preview.max_preview_bytes must be > 0")
|
||||||
|
}
|
||||||
|
if c.Preview.DirectoryPreviewLimit <= 0 {
|
||||||
|
return errors.New("preview.directory_preview_limit must be > 0")
|
||||||
|
}
|
||||||
|
if !c.Browser.Columns.Name {
|
||||||
|
return errors.New("browser.columns.name must stay enabled")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(c.UI.PathDisplay) == "" {
|
||||||
|
c.UI.PathDisplay = "short"
|
||||||
|
}
|
||||||
|
if c.UI.PaneGap < 0 || c.UI.PaneGap > 4 {
|
||||||
|
return errors.New("ui.pane_gap must be between 0 and 4")
|
||||||
|
}
|
||||||
|
if c.UI.CenterWidthPercent < 20 || c.UI.CenterWidthPercent > 60 {
|
||||||
|
return errors.New("ui.center_width_percent must be between 20 and 60")
|
||||||
|
}
|
||||||
|
switch strings.ToLower(strings.TrimSpace(c.Browser.Sort.By)) {
|
||||||
|
case "", "name":
|
||||||
|
c.Browser.Sort.By = "name"
|
||||||
|
case "modified", "size", "created", "extension":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("browser.sort.by must be one of: name, modified, size, created, extension")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvePath(explicitPath string) (string, bool, error) {
|
||||||
|
var candidates []string
|
||||||
|
|
||||||
|
if explicitPath != "" {
|
||||||
|
candidates = append(candidates, explicitPath)
|
||||||
|
} else {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", false, fmt.Errorf("resolve home dir: %w", err)
|
||||||
|
}
|
||||||
|
xdgDir := os.Getenv("XDG_CONFIG_HOME")
|
||||||
|
if xdgDir == "" {
|
||||||
|
xdgDir = filepath.Join(homeDir, ".config")
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates = append(candidates,
|
||||||
|
"vcom.toml",
|
||||||
|
filepath.Join("config", "vcom.toml"),
|
||||||
|
filepath.Join(xdgDir, "vcom", "vcom.toml"),
|
||||||
|
filepath.Join(homeDir, ".config", "vcom", "vcom.toml"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
if candidate == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
absPath, err := filepath.Abs(candidate)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, fmt.Errorf("resolve %s: %w", candidate, err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(absPath); err == nil {
|
||||||
|
return absPath, true, nil
|
||||||
|
} else if !isMissingPathError(err) {
|
||||||
|
return "", false, fmt.Errorf("stat %s: %w", absPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMissingPathError(err error) bool {
|
||||||
|
return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultUserPath() (string, error) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("resolve home dir: %w", err)
|
||||||
|
}
|
||||||
|
xdgDir := os.Getenv("XDG_CONFIG_HOME")
|
||||||
|
if xdgDir == "" {
|
||||||
|
xdgDir = filepath.Join(homeDir, ".config")
|
||||||
|
}
|
||||||
|
return filepath.Join(xdgDir, "vcom", "vcom.toml"), nil
|
||||||
|
}
|
||||||
84
src/vcom-0.2.5/internal/config/session.go
Normal file
84
src/vcom-0.2.5/internal/config/session.go
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
toml "github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionState stores the UI state that should be restored on next launch.
|
||||||
|
type SessionState struct {
|
||||||
|
ActivePane string `toml:"active_pane"`
|
||||||
|
Left PaneSession `toml:"left"`
|
||||||
|
Right PaneSession `toml:"right"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaneSession stores per-pane session state (path and selected entry name).
|
||||||
|
type PaneSession struct {
|
||||||
|
Path string `toml:"path"`
|
||||||
|
EntryName string `toml:"entry_name"`
|
||||||
|
CursorMemory map[string]string `toml:"cursor_memory"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultSessionPath returns the path to the session file.
|
||||||
|
func DefaultSessionPath() (string, error) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("resolve home dir: %w", err)
|
||||||
|
}
|
||||||
|
xdgDir := os.Getenv("XDG_CONFIG_HOME")
|
||||||
|
if xdgDir == "" {
|
||||||
|
xdgDir = filepath.Join(homeDir, ".config")
|
||||||
|
}
|
||||||
|
return filepath.Join(xdgDir, "vcom", "session.toml"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadSession reads the session state from disk.
|
||||||
|
// Returns (SessionState, nil) on success, (empty, error) if file doesn't exist.
|
||||||
|
func LoadSession() (SessionState, error) {
|
||||||
|
path, err := DefaultSessionPath()
|
||||||
|
if err != nil {
|
||||||
|
return SessionState{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return SessionState{}, nil
|
||||||
|
}
|
||||||
|
return SessionState{}, fmt.Errorf("read %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var s SessionState
|
||||||
|
if err := toml.Unmarshal(data, &s); err != nil {
|
||||||
|
return SessionState{}, fmt.Errorf("parse %s: %w", path, err)
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveSession writes the session state to disk.
|
||||||
|
func SaveSession(s SessionState) error {
|
||||||
|
path, err := DefaultSessionPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
absPath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolve %s: %w", path, err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Dir(absPath), 0o755); err != nil {
|
||||||
|
return fmt.Errorf("mkdir %s: %w", filepath.Dir(absPath), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := toml.Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal session: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(absPath, data, 0o644); err != nil {
|
||||||
|
return fmt.Errorf("write %s: %w", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
656
src/vcom-0.2.5/internal/fs/archive.go
Normal file
656
src/vcom-0.2.5/internal/fs/archive.go
Normal file
|
|
@ -0,0 +1,656 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExtractArchiveToTemp(sourcePath string) (string, error) {
|
||||||
|
// Count total files for progress reporting
|
||||||
|
totalFiles, totalBytes := countArchiveEntries(sourcePath)
|
||||||
|
|
||||||
|
tempDir, err := os.MkdirTemp("", "vcom-archive-")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cleanupOnErr := func(extractErr error) (string, error) {
|
||||||
|
_ = os.RemoveAll(tempDir)
|
||||||
|
return "", extractErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use background context for temp extraction (no cancellation needed)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
sourceLower := strings.ToLower(sourcePath)
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(sourceLower, ".zip"):
|
||||||
|
if err := extractZipArchive(ctx, sourcePath, tempDir, nil, totalFiles, totalBytes); err != nil {
|
||||||
|
return cleanupOnErr(err)
|
||||||
|
}
|
||||||
|
case strings.HasSuffix(sourceLower, ".tar"):
|
||||||
|
if err := extractTarArchive(ctx, sourcePath, tempDir, false, nil, totalFiles, totalBytes); err != nil {
|
||||||
|
return cleanupOnErr(err)
|
||||||
|
}
|
||||||
|
case strings.HasSuffix(sourceLower, ".tar.gz"), strings.HasSuffix(sourceLower, ".tgz"):
|
||||||
|
if err := extractTarArchive(ctx, sourcePath, tempDir, true, nil, totalFiles, totalBytes); err != nil {
|
||||||
|
return cleanupOnErr(err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return cleanupOnErr(fmt.Errorf("archive format is not supported: %s", filepath.Ext(sourcePath)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractArchiveToDir extracts an archive to the specified target directory.
|
||||||
|
// Unlike ExtractArchiveToTemp, it extracts directly to targetDir without
|
||||||
|
// creating a temporary directory. The progress callback is called after each
|
||||||
|
// file is extracted; it may be nil. Cancellation is supported via ctx.
|
||||||
|
func ExtractArchiveToDir(ctx context.Context, sourcePath, targetDir string, progress func(CopyProgress)) error {
|
||||||
|
totalFiles, totalBytes := countArchiveEntries(sourcePath)
|
||||||
|
sourceLower := strings.ToLower(sourcePath)
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(sourceLower, ".zip"):
|
||||||
|
return extractZipArchive(ctx, sourcePath, targetDir, progress, totalFiles, totalBytes)
|
||||||
|
case strings.HasSuffix(sourceLower, ".tar"):
|
||||||
|
return extractTarArchive(ctx, sourcePath, targetDir, false, progress, totalFiles, totalBytes)
|
||||||
|
case strings.HasSuffix(sourceLower, ".tar.gz"), strings.HasSuffix(sourceLower, ".tgz"):
|
||||||
|
return extractTarArchive(ctx, sourcePath, targetDir, true, progress, totalFiles, totalBytes)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("archive format is not supported: %s", filepath.Ext(sourcePath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// countArchiveEntries counts the total number of files and total uncompressed
|
||||||
|
// bytes in an archive without extracting. Used for progress reporting.
|
||||||
|
func countArchiveEntries(sourcePath string) (int64, int64) {
|
||||||
|
sourceLower := strings.ToLower(sourcePath)
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(sourceLower, ".zip"):
|
||||||
|
return countZipEntries(sourcePath)
|
||||||
|
case strings.HasSuffix(sourceLower, ".tar"), strings.HasSuffix(sourceLower, ".tar.gz"), strings.HasSuffix(sourceLower, ".tgz"):
|
||||||
|
return countTarEntries(sourcePath)
|
||||||
|
default:
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func countZipEntries(sourcePath string) (int64, int64) {
|
||||||
|
r, err := zip.OpenReader(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
var files, bytes int64
|
||||||
|
for _, f := range r.File {
|
||||||
|
if !f.FileInfo().IsDir() {
|
||||||
|
files++
|
||||||
|
bytes += int64(f.UncompressedSize64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files, bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func countTarEntries(sourcePath string) (int64, int64) {
|
||||||
|
f, err := os.Open(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var reader io.Reader = f
|
||||||
|
if strings.HasSuffix(strings.ToLower(sourcePath), ".tar.gz") || strings.HasSuffix(strings.ToLower(sourcePath), ".tgz") {
|
||||||
|
gr, err := gzip.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
defer gr.Close()
|
||||||
|
reader = gr
|
||||||
|
}
|
||||||
|
|
||||||
|
tarReader := tar.NewReader(reader)
|
||||||
|
var files, bytes int64
|
||||||
|
for {
|
||||||
|
hdr, err := tarReader.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
|
||||||
|
files++
|
||||||
|
bytes += hdr.Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files, bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractZipArchive(ctx context.Context, sourcePath string, targetDir string, progress func(CopyProgress), totalFiles, totalBytes int64) error {
|
||||||
|
reader, err := zip.OpenReader(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
var filesDone int64
|
||||||
|
for _, file := range reader.File {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
relPath, ok := safeArchivePath(file.Name)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fullPath := filepath.Join(targetDir, relPath)
|
||||||
|
|
||||||
|
if file.FileInfo().IsDir() {
|
||||||
|
if err := os.MkdirAll(fullPath, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
src, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeArchiveFile(fullPath, src, file.Mode()); err != nil {
|
||||||
|
src.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
src.Close()
|
||||||
|
|
||||||
|
filesDone++
|
||||||
|
if progress != nil {
|
||||||
|
progress(CopyProgress{
|
||||||
|
FilesDone: int(filesDone),
|
||||||
|
FilesTotal: int(totalFiles),
|
||||||
|
BytesDone: 0,
|
||||||
|
BytesTotal: totalBytes,
|
||||||
|
Stage: "Extracting data",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractTarArchive(ctx context.Context, sourcePath string, targetDir string, gzipped bool, progress func(CopyProgress), totalFiles, totalBytes int64) error {
|
||||||
|
file, err := os.Open(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var reader io.Reader = file
|
||||||
|
if gzipped {
|
||||||
|
gzipReader, err := gzip.NewReader(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer gzipReader.Close()
|
||||||
|
reader = gzipReader
|
||||||
|
}
|
||||||
|
|
||||||
|
tarReader := tar.NewReader(reader)
|
||||||
|
var filesDone int64
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := tarReader.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
relPath, ok := safeArchivePath(header.Name)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fullPath := filepath.Join(targetDir, relPath)
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
if err := os.MkdirAll(fullPath, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case tar.TypeReg, tar.TypeRegA:
|
||||||
|
if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeArchiveFile(fullPath, tarReader, os.FileMode(header.Mode)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filesDone++
|
||||||
|
if progress != nil {
|
||||||
|
progress(CopyProgress{
|
||||||
|
FilesDone: int(filesDone),
|
||||||
|
FilesTotal: int(totalFiles),
|
||||||
|
BytesDone: 0,
|
||||||
|
BytesTotal: totalBytes,
|
||||||
|
Stage: "Extracting data",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeArchiveFile(path string, source io.Reader, mode os.FileMode) error {
|
||||||
|
if mode == 0 {
|
||||||
|
mode = 0o644
|
||||||
|
}
|
||||||
|
output, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode.Perm())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer output.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(output, source)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeArchivePath(name string) (string, bool) {
|
||||||
|
clean := filepath.Clean(name)
|
||||||
|
if clean == "." || clean == string(filepath.Separator) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if filepath.IsAbs(clean) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return clean, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveFormat returns the file extension for a given archive format name.
|
||||||
|
func ArchiveFormat(format string) string {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(format)) {
|
||||||
|
case "zip":
|
||||||
|
return ".zip"
|
||||||
|
case "tar":
|
||||||
|
return ".tar"
|
||||||
|
case "targz", "tar.gz", "tgz":
|
||||||
|
return ".tar.gz"
|
||||||
|
default:
|
||||||
|
return ".zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveName generates an archive filename from source paths.
|
||||||
|
func ArchiveName(sources []string, format string) string {
|
||||||
|
ext := ArchiveFormat(format)
|
||||||
|
if len(sources) == 1 {
|
||||||
|
base := strings.TrimSuffix(filepath.Base(sources[0]), filepath.Ext(sources[0]))
|
||||||
|
return base + ext
|
||||||
|
}
|
||||||
|
base := filepath.Base(filepath.Dir(sources[0]))
|
||||||
|
if base == "." || base == "" || base == string(filepath.Separator) {
|
||||||
|
base = "archive"
|
||||||
|
}
|
||||||
|
return base + ext
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateArchive creates an archive from source paths using the given format.
|
||||||
|
// Supported formats: "zip", "tar", "tar.gz" (or "targz", "tgz").
|
||||||
|
// Progress is reported via the callback function.
|
||||||
|
func CreateArchive(ctx context.Context, sources []string, archivePath string, progress func(CopyProgress)) error {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
lower := strings.ToLower(archivePath)
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(lower, ".zip"):
|
||||||
|
return createZipArchive(ctx, sources, archivePath, progress)
|
||||||
|
case strings.HasSuffix(lower, ".tar.gz"), strings.HasSuffix(lower, ".tgz"):
|
||||||
|
return createTarGzArchive(ctx, sources, archivePath, progress)
|
||||||
|
case strings.HasSuffix(lower, ".tar"):
|
||||||
|
return createTarArchive(ctx, sources, archivePath, progress)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported archive format: %s", filepath.Ext(archivePath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createZipArchive(ctx context.Context, sources []string, archivePath string, progress func(CopyProgress)) error {
|
||||||
|
file, err := os.Create(archivePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create %s: %w", archivePath, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
zipWriter := zip.NewWriter(file)
|
||||||
|
defer zipWriter.Close()
|
||||||
|
|
||||||
|
var totalFiles int
|
||||||
|
var totalBytes int64
|
||||||
|
for _, source := range sources {
|
||||||
|
info, err := os.Lstat(source)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stat %s: %w", source, err)
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
totalFiles++
|
||||||
|
if !info.IsDir() {
|
||||||
|
totalBytes += info.Size()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
totalFiles++
|
||||||
|
totalBytes += info.Size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state := ©ProgressState{
|
||||||
|
ctx: ctx,
|
||||||
|
stats: TransferStats{FilesTotal: totalFiles, BytesTotal: totalBytes},
|
||||||
|
callback: progress,
|
||||||
|
lastEmit: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDir := commonBaseDir(sources)
|
||||||
|
for _, source := range sources {
|
||||||
|
info, err := os.Lstat(source)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stat %s: %w", source, err)
|
||||||
|
}
|
||||||
|
relRoot := source
|
||||||
|
if baseDir != "" {
|
||||||
|
relRoot, _ = filepath.Rel(baseDir, source)
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
relPath, _ := filepath.Rel(baseDir, path)
|
||||||
|
relPath = filepath.ToSlash(relPath)
|
||||||
|
header, zipErr := zip.FileInfoHeader(info)
|
||||||
|
if zipErr != nil {
|
||||||
|
return zipErr
|
||||||
|
}
|
||||||
|
header.Name = relPath
|
||||||
|
if info.IsDir() {
|
||||||
|
header.Name += "/"
|
||||||
|
} else {
|
||||||
|
header.Method = zip.Deflate
|
||||||
|
}
|
||||||
|
writer, zipErr := zipWriter.CreateHeader(header)
|
||||||
|
if zipErr != nil {
|
||||||
|
return zipErr
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
f, openErr := os.Open(path)
|
||||||
|
if openErr != nil {
|
||||||
|
return openErr
|
||||||
|
}
|
||||||
|
written, copyErr := io.Copy(writer, f)
|
||||||
|
f.Close()
|
||||||
|
if copyErr != nil {
|
||||||
|
return copyErr
|
||||||
|
}
|
||||||
|
state.filesDone++
|
||||||
|
state.bytesDone += written
|
||||||
|
} else {
|
||||||
|
state.filesDone++
|
||||||
|
}
|
||||||
|
emitArchiveProgress(state, path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
relPath := filepath.ToSlash(relRoot)
|
||||||
|
header, zipErr := zip.FileInfoHeader(info)
|
||||||
|
if zipErr != nil {
|
||||||
|
return zipErr
|
||||||
|
}
|
||||||
|
header.Name = relPath
|
||||||
|
header.Method = zip.Deflate
|
||||||
|
writer, zipErr := zipWriter.CreateHeader(header)
|
||||||
|
if zipErr != nil {
|
||||||
|
return zipErr
|
||||||
|
}
|
||||||
|
f, openErr := os.Open(source)
|
||||||
|
if openErr != nil {
|
||||||
|
return openErr
|
||||||
|
}
|
||||||
|
written, copyErr := io.Copy(writer, f)
|
||||||
|
f.Close()
|
||||||
|
if copyErr != nil {
|
||||||
|
return copyErr
|
||||||
|
}
|
||||||
|
state.filesDone++
|
||||||
|
state.bytesDone += written
|
||||||
|
emitArchiveProgress(state, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTarArchive(ctx context.Context, sources []string, archivePath string, progress func(CopyProgress)) error {
|
||||||
|
return createTarArchiveWithGzip(ctx, sources, archivePath, false, progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTarGzArchive(ctx context.Context, sources []string, archivePath string, progress func(CopyProgress)) error {
|
||||||
|
return createTarArchiveWithGzip(ctx, sources, archivePath, true, progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTarArchiveWithGzip(ctx context.Context, sources []string, archivePath string, gzipped bool, progress func(CopyProgress)) error {
|
||||||
|
file, err := os.Create(archivePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create %s: %w", archivePath, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var writer io.WriteCloser = file
|
||||||
|
if gzipped {
|
||||||
|
gzipWriter := gzip.NewWriter(file)
|
||||||
|
defer gzipWriter.Close()
|
||||||
|
writer = gzipWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
tarWriter := tar.NewWriter(writer)
|
||||||
|
defer tarWriter.Close()
|
||||||
|
|
||||||
|
var totalFiles int
|
||||||
|
var totalBytes int64
|
||||||
|
for _, source := range sources {
|
||||||
|
info, err := os.Lstat(source)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stat %s: %w", source, err)
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
totalFiles++
|
||||||
|
if !info.IsDir() {
|
||||||
|
totalBytes += info.Size()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
totalFiles++
|
||||||
|
totalBytes += info.Size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state := ©ProgressState{
|
||||||
|
ctx: ctx,
|
||||||
|
stats: TransferStats{FilesTotal: totalFiles, BytesTotal: totalBytes},
|
||||||
|
callback: progress,
|
||||||
|
lastEmit: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDir := commonBaseDir(sources)
|
||||||
|
for _, source := range sources {
|
||||||
|
info, err := os.Lstat(source)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stat %s: %w", source, err)
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
relPath, _ := filepath.Rel(baseDir, path)
|
||||||
|
relPath = filepath.ToSlash(relPath)
|
||||||
|
header, tarErr := tar.FileInfoHeader(info, path)
|
||||||
|
if tarErr != nil {
|
||||||
|
return tarErr
|
||||||
|
}
|
||||||
|
header.Name = relPath
|
||||||
|
if info.IsDir() {
|
||||||
|
header.Name += "/"
|
||||||
|
}
|
||||||
|
if err := tarWriter.WriteHeader(header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
f, openErr := os.Open(path)
|
||||||
|
if openErr != nil {
|
||||||
|
return openErr
|
||||||
|
}
|
||||||
|
written, copyErr := io.Copy(tarWriter, f)
|
||||||
|
f.Close()
|
||||||
|
if copyErr != nil {
|
||||||
|
return copyErr
|
||||||
|
}
|
||||||
|
state.filesDone++
|
||||||
|
state.bytesDone += written
|
||||||
|
} else {
|
||||||
|
state.filesDone++
|
||||||
|
}
|
||||||
|
emitArchiveProgress(state, path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
relPath, _ := filepath.Rel(baseDir, source)
|
||||||
|
relPath = filepath.ToSlash(relPath)
|
||||||
|
header, tarErr := tar.FileInfoHeader(info, source)
|
||||||
|
if tarErr != nil {
|
||||||
|
return tarErr
|
||||||
|
}
|
||||||
|
header.Name = relPath
|
||||||
|
if err := tarWriter.WriteHeader(header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, openErr := os.Open(source)
|
||||||
|
if openErr != nil {
|
||||||
|
return openErr
|
||||||
|
}
|
||||||
|
written, copyErr := io.Copy(tarWriter, f)
|
||||||
|
f.Close()
|
||||||
|
if copyErr != nil {
|
||||||
|
return copyErr
|
||||||
|
}
|
||||||
|
state.filesDone++
|
||||||
|
state.bytesDone += written
|
||||||
|
emitArchiveProgress(state, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func emitArchiveProgress(state *copyProgressState, currentPath string) {
|
||||||
|
if state.callback == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(state.lastEmit) < 50*time.Millisecond {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.lastEmit = now
|
||||||
|
state.callback(CopyProgress{
|
||||||
|
FilesDone: state.filesDone,
|
||||||
|
FilesTotal: state.stats.FilesTotal,
|
||||||
|
BytesDone: state.bytesDone,
|
||||||
|
BytesTotal: state.stats.BytesTotal,
|
||||||
|
CurrentPath: currentPath,
|
||||||
|
Stage: "Archiving",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// commonBaseDir returns the longest common directory prefix for the given paths.
|
||||||
|
func commonBaseDir(paths []string) string {
|
||||||
|
if len(paths) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(paths) == 1 {
|
||||||
|
if info, err := os.Lstat(paths[0]); err == nil && info.IsDir() {
|
||||||
|
return filepath.Dir(paths[0])
|
||||||
|
}
|
||||||
|
return filepath.Dir(paths[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
base := filepath.Dir(paths[0])
|
||||||
|
for _, p := range paths[1:] {
|
||||||
|
dir := filepath.Dir(p)
|
||||||
|
for !strings.HasPrefix(dir, base) && base != "" {
|
||||||
|
parent := filepath.Dir(base)
|
||||||
|
if parent == base {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
base = parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
156
src/vcom-0.2.5/internal/fs/entry.go
Normal file
156
src/vcom-0.2.5/internal/fs/entry.go
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configExtensions = map[string]struct{}{
|
||||||
|
"toml": {}, "yaml": {}, "yml": {}, "json": {}, "jsonc": {}, "ini": {}, "conf": {},
|
||||||
|
"config": {}, "env": {}, "properties": {}, "xml": {}, "mod": {}, "sum": {}, "lock": {},
|
||||||
|
}
|
||||||
|
textExtensions = map[string]struct{}{
|
||||||
|
"txt": {}, "md": {}, "rst": {}, "go": {}, "rs": {}, "c": {}, "h": {}, "cpp": {}, "hpp": {},
|
||||||
|
"py": {}, "js": {}, "ts": {}, "tsx": {}, "jsx": {}, "java": {}, "kt": {}, "kts": {}, "swift": {},
|
||||||
|
"html": {}, "css": {}, "scss": {}, "sass": {}, "less": {}, "styl": {},
|
||||||
|
"sh": {}, "bash": {}, "zsh": {}, "fish": {}, "sql": {},
|
||||||
|
"log": {}, "csv": {}, "tsv": {},
|
||||||
|
"lua": {}, "rb": {}, "pl": {}, "pm": {}, "t": {}, "ps1": {}, "bat": {}, "cmd": {},
|
||||||
|
"vue": {}, "svelte": {}, "astro": {}, "ejs": {}, "hbs": {}, "pug": {}, "haml": {}, "php": {}, "twig": {},
|
||||||
|
"scala": {}, "groovy": {}, "clj": {}, "ex": {}, "exs": {}, "elm": {}, "hs": {}, "lisp": {}, "cl": {}, "rkt": {}, "scm": {}, "dart": {},
|
||||||
|
"tex": {}, "bib": {}, "sty": {}, "cls": {},
|
||||||
|
"gradle": {}, "cmake": {}, "mk": {}, "mak": {},
|
||||||
|
"asm": {}, "s": {}, "inc": {},
|
||||||
|
"patch": {}, "diff": {},
|
||||||
|
"proto": {}, "graphql": {}, "gql": {},
|
||||||
|
"tf": {}, "hcl": {},
|
||||||
|
"r": {}, "m": {}, "mm": {},
|
||||||
|
"nim": {}, "zig": {}, "odin": {}, "v": {}, "nix": {},
|
||||||
|
"cr": {}, "jl": {},
|
||||||
|
"erl": {}, "hrl": {},
|
||||||
|
}
|
||||||
|
// textFilenames lists common text files without a meaningful extension
|
||||||
|
// (like Makefile, Dockerfile, etc.) so they open in the editor.
|
||||||
|
textFilenames = map[string]struct{}{
|
||||||
|
"makefile": {}, "dockerfile": {}, "containerfile": {},
|
||||||
|
"readme": {}, "license": {}, "licence": {}, "copying": {}, "changelog": {}, "changes": {},
|
||||||
|
"todo": {}, "notes": {}, "authors": {}, "contributors": {}, "maintainers": {},
|
||||||
|
"procfile": {}, "gemfile": {}, "rakefile": {}, "snapfile": {}, "fastfile": {},
|
||||||
|
"cmakelists": {}, "justfile": {}, "taskfile": {},
|
||||||
|
"gitignore": {}, "gitattributes": {}, "gitmodules": {}, "gitkeep": {},
|
||||||
|
"gitconfig": {}, "git-blame-ignore-revs": {},
|
||||||
|
"editorconfig": {}, "envrc": {}, "hushlogin": {},
|
||||||
|
"xsession": {}, "xresources": {}, "xinitrc": {},
|
||||||
|
"bashrc": {}, "bash_profile": {}, "bash_logout": {},
|
||||||
|
"zshrc": {}, "zprofile": {}, "zlogin": {}, "zlogout": {},
|
||||||
|
"profile": {}, "inputrc": {}, "tmux.conf": {},
|
||||||
|
"npmrc": {}, "yarnrc": {}, "pnpmrc": {},
|
||||||
|
"eslintrc": {}, "prettierrc": {}, "babelrc": {},
|
||||||
|
"stylelintrc": {}, "commitlintrc": {},
|
||||||
|
"htaccess": {}, "htpasswd": {},
|
||||||
|
}
|
||||||
|
imageExtensions = map[string]struct{}{
|
||||||
|
"png": {}, "jpg": {}, "jpeg": {}, "gif": {}, "webp": {}, "bmp": {}, "svg": {}, "ico": {},
|
||||||
|
"avif": {}, "heic": {}, "heif": {}, "tiff": {}, "tif": {},
|
||||||
|
}
|
||||||
|
pdfExtensions = map[string]struct{}{
|
||||||
|
"pdf": {},
|
||||||
|
}
|
||||||
|
audioExtensions = map[string]struct{}{
|
||||||
|
"mp3": {}, "flac": {}, "ogg": {}, "opus": {}, "wav": {},
|
||||||
|
"aac": {}, "m4a": {}, "wma": {}, "dsf": {}, "ape": {},
|
||||||
|
}
|
||||||
|
videoExtensions = map[string]struct{}{
|
||||||
|
"mp4": {}, "mkv": {}, "mov": {}, "avi": {}, "webm": {},
|
||||||
|
"m4v": {}, "wmv": {}, "flv": {}, "ts": {}, "mts": {},
|
||||||
|
}
|
||||||
|
archiveExtensions = map[string]struct{}{
|
||||||
|
"zip": {}, "tar": {}, "gz": {}, "tgz": {}, "xz": {}, "bz2": {}, "7z": {}, "rar": {},
|
||||||
|
"zst": {}, "lz": {}, "lz4": {}, "lzma": {},
|
||||||
|
"iso": {}, "img": {}, "dmg": {},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
Extension string
|
||||||
|
Mode fs.FileMode
|
||||||
|
Size int64
|
||||||
|
ModifiedAt time.Time
|
||||||
|
CreatedAt time.Time
|
||||||
|
CreatedKnown bool
|
||||||
|
IsDir bool
|
||||||
|
IsParent bool
|
||||||
|
IsHidden bool
|
||||||
|
IsRemote bool
|
||||||
|
Connected bool
|
||||||
|
DirSizeKnown bool
|
||||||
|
RemoteHostName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Entry) DisplayName() string {
|
||||||
|
if e.IsParent {
|
||||||
|
return ".."
|
||||||
|
}
|
||||||
|
if e.IsDir {
|
||||||
|
return e.Name + "/"
|
||||||
|
}
|
||||||
|
return e.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Entry) IsFile() bool {
|
||||||
|
return !e.IsDir && !e.IsParent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Entry) MatchKey() string {
|
||||||
|
return strings.ToLower(e.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Entry) IsExecutable() bool {
|
||||||
|
return !e.IsDir && !e.IsParent && e.Mode&0o111 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Entry) Category() string {
|
||||||
|
switch {
|
||||||
|
case e.IsParent:
|
||||||
|
return "parent"
|
||||||
|
case e.IsRemote:
|
||||||
|
return "remote"
|
||||||
|
case e.IsDir:
|
||||||
|
return "directory"
|
||||||
|
case hasExt(configExtensions, e.Extension):
|
||||||
|
return "config"
|
||||||
|
case hasExt(textExtensions, e.Extension):
|
||||||
|
return "text"
|
||||||
|
case hasExt(textFilenames, strings.ToLower(e.Name)):
|
||||||
|
return "text"
|
||||||
|
case hasExt(imageExtensions, e.Extension):
|
||||||
|
return "image"
|
||||||
|
case hasExt(pdfExtensions, e.Extension):
|
||||||
|
return "pdf"
|
||||||
|
case hasExt(audioExtensions, e.Extension):
|
||||||
|
return "audio"
|
||||||
|
case hasExt(videoExtensions, e.Extension):
|
||||||
|
return "video"
|
||||||
|
case hasExt(archiveExtensions, e.Extension):
|
||||||
|
return "archive"
|
||||||
|
case e.IsExecutable():
|
||||||
|
return "executable"
|
||||||
|
default:
|
||||||
|
return "binary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ext(name string) string {
|
||||||
|
value := strings.TrimPrefix(filepath.Ext(name), ".")
|
||||||
|
return strings.ToLower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasExt(set map[string]struct{}, ext string) bool {
|
||||||
|
_, ok := set[strings.ToLower(ext)]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
527
src/vcom-0.2.5/internal/fs/ops.go
Normal file
527
src/vcom-0.2.5/internal/fs/ops.go
Normal file
|
|
@ -0,0 +1,527 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransferStats struct {
|
||||||
|
FilesTotal int
|
||||||
|
BytesTotal int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type CopyProgress struct {
|
||||||
|
FilesDone int
|
||||||
|
FilesTotal int
|
||||||
|
BytesDone int64
|
||||||
|
BytesTotal int64
|
||||||
|
CurrentPath string
|
||||||
|
Stage string
|
||||||
|
}
|
||||||
|
|
||||||
|
type copyProgressState struct {
|
||||||
|
ctx context.Context
|
||||||
|
filesDone int
|
||||||
|
bytesDone int64
|
||||||
|
stats TransferStats
|
||||||
|
callback func(CopyProgress)
|
||||||
|
lastEmit time.Time
|
||||||
|
stage string
|
||||||
|
discover bool // if true, count files during copy for progress total
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *copyProgressState) discoverFiles(count int, dirPath string) {
|
||||||
|
if count == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.stats.FilesTotal += count
|
||||||
|
s.emit(dirPath, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyPath(srcPath string, dstDir string, overwrite bool) (string, error) {
|
||||||
|
return CopyPathWithProgress(srcPath, dstDir, overwrite, TransferStats{}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyPathWithProgressContext(ctx context.Context, srcPath string, dstDir string, overwrite bool, stats TransferStats, progress func(CopyProgress)) (string, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
srcInfo, err := os.Lstat(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("stat %s: %w", srcPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(dstDir, filepath.Base(srcPath))
|
||||||
|
if same, err := samePath(srcPath, targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if same {
|
||||||
|
return "", fmt.Errorf("source and target are the same: %s", targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists, err := PathExists(targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if exists {
|
||||||
|
if !overwrite {
|
||||||
|
return "", ErrOverwrite(targetPath)
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if progress == nil {
|
||||||
|
progress = func(CopyProgress) {}
|
||||||
|
}
|
||||||
|
tracker := copyProgressState{
|
||||||
|
ctx: ctx,
|
||||||
|
stats: stats,
|
||||||
|
callback: progress,
|
||||||
|
stage: "Scanning files...",
|
||||||
|
discover: stats.FilesTotal == 0,
|
||||||
|
}
|
||||||
|
tracker.emit(srcPath, true)
|
||||||
|
tracker.stage = "Copying files..."
|
||||||
|
|
||||||
|
cleanupOnErr := func(copyErr error) (string, error) {
|
||||||
|
if copyErr != nil {
|
||||||
|
_ = os.RemoveAll(targetPath)
|
||||||
|
}
|
||||||
|
return "", copyErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcInfo.Mode()&os.ModeSymlink != 0 {
|
||||||
|
target, err := os.Readlink(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := os.Symlink(target, targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
tracker.finishFile(srcPath)
|
||||||
|
return targetPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcInfo.IsDir() {
|
||||||
|
if err := copyDir(srcPath, targetPath, &tracker); err != nil {
|
||||||
|
return cleanupOnErr(err)
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return cleanupOnErr(err)
|
||||||
|
}
|
||||||
|
tracker.emit(srcPath, true)
|
||||||
|
return targetPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyFile(srcPath, targetPath, srcInfo.Mode(), &tracker); err != nil {
|
||||||
|
return cleanupOnErr(err)
|
||||||
|
}
|
||||||
|
tracker.emit(srcPath, true)
|
||||||
|
return targetPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyStats(srcPath string) (TransferStats, error) {
|
||||||
|
srcInfo, err := os.Lstat(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return TransferStats{}, fmt.Errorf("stat %s: %w", srcPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcInfo.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return TransferStats{FilesTotal: 1, BytesTotal: 0}, nil
|
||||||
|
}
|
||||||
|
if !srcInfo.IsDir() {
|
||||||
|
return TransferStats{FilesTotal: 1, BytesTotal: srcInfo.Size()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stats := TransferStats{}
|
||||||
|
err = filepath.WalkDir(srcPath, func(current string, d fs.DirEntry, walkErr error) error {
|
||||||
|
if walkErr != nil {
|
||||||
|
return walkErr
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
stats.FilesTotal++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return TransferStats{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyPathWithProgress(srcPath string, dstDir string, overwrite bool, stats TransferStats, progress func(CopyProgress)) (string, error) {
|
||||||
|
return CopyPathWithProgressContext(context.Background(), srcPath, dstDir, overwrite, stats, progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MovePath(srcPath string, dstDir string, overwrite bool) (string, error) {
|
||||||
|
return MovePathWithProgress(srcPath, dstDir, overwrite, TransferStats{}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MovePathWithProgress(srcPath string, dstDir string, overwrite bool, stats TransferStats, progress func(CopyProgress)) (string, error) {
|
||||||
|
return MovePathWithProgressContext(context.Background(), srcPath, dstDir, overwrite, stats, progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MovePathWithProgressContext(ctx context.Context, srcPath string, dstDir string, overwrite bool, stats TransferStats, progress func(CopyProgress)) (string, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(dstDir, filepath.Base(srcPath))
|
||||||
|
if same, err := samePath(srcPath, targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if same {
|
||||||
|
return "", fmt.Errorf("source and target are the same: %s", targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists, err := PathExists(targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if exists {
|
||||||
|
if !overwrite {
|
||||||
|
return "", ErrOverwrite(targetPath)
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if progress == nil {
|
||||||
|
progress = func(CopyProgress) {}
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := os.Rename(srcPath, targetPath); err == nil {
|
||||||
|
progress(CopyProgress{
|
||||||
|
FilesDone: stats.FilesTotal,
|
||||||
|
FilesTotal: stats.FilesTotal,
|
||||||
|
BytesDone: stats.BytesTotal,
|
||||||
|
BytesTotal: stats.BytesTotal,
|
||||||
|
CurrentPath: srcPath,
|
||||||
|
Stage: "Move completed",
|
||||||
|
})
|
||||||
|
return targetPath, nil
|
||||||
|
} else if !errors.Is(err, syscall.EXDEV) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath, err := CopyPathWithProgressContext(ctx, srcPath, dstDir, overwrite, stats, progress)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
_ = os.RemoveAll(targetPath)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
progress(CopyProgress{
|
||||||
|
FilesDone: stats.FilesTotal,
|
||||||
|
FilesTotal: stats.FilesTotal,
|
||||||
|
BytesDone: stats.BytesTotal,
|
||||||
|
BytesTotal: stats.BytesTotal,
|
||||||
|
CurrentPath: srcPath,
|
||||||
|
Stage: "Finalizing move",
|
||||||
|
})
|
||||||
|
if err := DeletePath(srcPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return targetPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PathExists(path string) (bool, error) {
|
||||||
|
if _, err := os.Lstat(path); err == nil {
|
||||||
|
return true, nil
|
||||||
|
} else if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeletePath(path string) error {
|
||||||
|
return os.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveToTrash moves a file or directory to the FreeDesktop Trash directory
|
||||||
|
// (~/.local/share/Trash). Follows the FreeDesktop Trash specification:
|
||||||
|
// - The original item is moved to Trash/files/<basename>
|
||||||
|
// - A .trashinfo file is written to Trash/info/<basename>.trashinfo
|
||||||
|
// - If <basename> already exists in Trash/files, a numeric suffix is appended.
|
||||||
|
func MoveToTrash(path string) error {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot determine home directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trashDir := filepath.Join(home, ".local", "share", "Trash")
|
||||||
|
filesDir := filepath.Join(trashDir, "files")
|
||||||
|
infoDir := filepath.Join(trashDir, "info")
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filesDir, 0o700); err != nil {
|
||||||
|
return fmt.Errorf("cannot create trash files directory: %w", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(infoDir, 0o700); err != nil {
|
||||||
|
return fmt.Errorf("cannot create trash info directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseName := filepath.Base(path)
|
||||||
|
|
||||||
|
// Generate a unique name in the trash directory
|
||||||
|
destName := baseName
|
||||||
|
for counter := 1; ; counter++ {
|
||||||
|
destPath := filepath.Join(filesDir, destName)
|
||||||
|
if _, err := os.Stat(destPath); os.IsNotExist(err) {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("cannot stat trash path: %w", err)
|
||||||
|
}
|
||||||
|
destName = fmt.Sprintf("%s.%d", baseName, counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
destPath := filepath.Join(filesDir, destName)
|
||||||
|
if err := os.Rename(path, destPath); err != nil {
|
||||||
|
// Cross-filesystem move: fall back to copy+delete
|
||||||
|
return fmt.Errorf("cannot move to trash: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write .trashinfo file
|
||||||
|
absPath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
absPath = path
|
||||||
|
}
|
||||||
|
now := time.Now().Format("2006-01-02T15:04:05")
|
||||||
|
infoContent := fmt.Sprintf("[Trash Info]\nPath=%s\nDeletionDate=%s\n", absPath, now)
|
||||||
|
infoPath := filepath.Join(infoDir, destName+".trashinfo")
|
||||||
|
if err := os.WriteFile(infoPath, []byte(infoContent), 0o600); err != nil {
|
||||||
|
// Best-effort: if info file fails, try to move the file back
|
||||||
|
_ = os.Rename(destPath, path)
|
||||||
|
return fmt.Errorf("cannot write trash info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeDir(parent string, name string) (string, error) {
|
||||||
|
target := filepath.Join(parent, name)
|
||||||
|
if err := os.MkdirAll(target, 0o755); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenamePath(sourcePath string, newName string) (string, error) {
|
||||||
|
newName = filepath.Base(filepath.Clean(newName))
|
||||||
|
if newName == "." || newName == "" {
|
||||||
|
return "", fmt.Errorf("invalid target name")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(filepath.Dir(sourcePath), newName)
|
||||||
|
if same, err := samePath(sourcePath, targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if same {
|
||||||
|
return "", fmt.Errorf("source and target are the same: %s", targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists, err := PathExists(targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if exists {
|
||||||
|
return "", ErrOverwrite(targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(sourcePath, targetPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return targetPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDir(srcDir string, dstDir string, tracker *copyProgressState) error {
|
||||||
|
if tracker != nil && tracker.ctx != nil {
|
||||||
|
if err := tracker.ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info, err := os.Lstat(srcDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dstDir, info.Mode().Perm()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(srcDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count files in this directory so progress total converges
|
||||||
|
if tracker != nil && tracker.discover {
|
||||||
|
fileCount := 0
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsDir() {
|
||||||
|
fileCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracker.discoverFiles(fileCount, srcDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if tracker != nil && tracker.ctx != nil {
|
||||||
|
if err := tracker.ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
srcPath := filepath.Join(srcDir, entry.Name())
|
||||||
|
dstPath := filepath.Join(dstDir, entry.Name())
|
||||||
|
|
||||||
|
info, err := os.Lstat(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case info.Mode()&os.ModeSymlink != 0:
|
||||||
|
target, err := os.Readlink(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Symlink(target, dstPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tracker != nil {
|
||||||
|
tracker.finishFile(srcPath)
|
||||||
|
}
|
||||||
|
case info.IsDir():
|
||||||
|
if err := copyDir(srcPath, dstPath, tracker); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := copyFile(srcPath, dstPath, info.Mode(), tracker); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tracker != nil && tracker.ctx != nil {
|
||||||
|
if err := tracker.ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(srcPath string, dstPath string, mode os.FileMode, tracker *copyProgressState) error {
|
||||||
|
if tracker != nil && tracker.ctx != nil {
|
||||||
|
if err := tracker.ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
srcFile, err := os.Open(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
dstFile, err := os.OpenFile(dstPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, mode.Perm())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstFile.Close()
|
||||||
|
|
||||||
|
writer := io.Writer(dstFile)
|
||||||
|
if tracker != nil {
|
||||||
|
writer = &progressWriter{base: dstFile, tracker: tracker, path: srcPath}
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(writer, srcFile); err != nil {
|
||||||
|
_ = dstFile.Close()
|
||||||
|
_ = os.Remove(dstPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tracker != nil && tracker.ctx != nil {
|
||||||
|
if err := tracker.ctx.Err(); err != nil {
|
||||||
|
_ = dstFile.Close()
|
||||||
|
_ = os.Remove(dstPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tracker != nil {
|
||||||
|
tracker.finishFile(srcPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type progressWriter struct {
|
||||||
|
base io.Writer
|
||||||
|
tracker *copyProgressState
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *progressWriter) Write(data []byte) (int, error) {
|
||||||
|
if w.tracker != nil && w.tracker.ctx != nil {
|
||||||
|
if err := w.tracker.ctx.Err(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err := w.base.Write(data)
|
||||||
|
if n > 0 {
|
||||||
|
w.tracker.addBytes(int64(n), w.path)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *copyProgressState) addBytes(delta int64, currentPath string) {
|
||||||
|
s.bytesDone += delta
|
||||||
|
s.emit(currentPath, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *copyProgressState) finishFile(currentPath string) {
|
||||||
|
s.filesDone++
|
||||||
|
s.emit(currentPath, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *copyProgressState) emit(currentPath string, force bool) {
|
||||||
|
if s.callback == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !force && time.Since(s.lastEmit) < 75*time.Millisecond {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.lastEmit = time.Now()
|
||||||
|
stage := s.stage
|
||||||
|
if stage == "" {
|
||||||
|
stage = "Transferring data"
|
||||||
|
}
|
||||||
|
s.callback(CopyProgress{
|
||||||
|
FilesDone: s.filesDone,
|
||||||
|
FilesTotal: s.stats.FilesTotal,
|
||||||
|
BytesDone: s.bytesDone,
|
||||||
|
BytesTotal: s.stats.BytesTotal,
|
||||||
|
CurrentPath: currentPath,
|
||||||
|
Stage: stage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func samePath(left string, right string) (bool, error) {
|
||||||
|
leftAbs, err := filepath.Abs(left)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
rightAbs, err := filepath.Abs(right)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return leftAbs == rightAbs, nil
|
||||||
|
}
|
||||||
135
src/vcom-0.2.5/internal/fs/ops_test.go
Normal file
135
src/vcom-0.2.5/internal/fs/ops_test.go
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCopyPathWithProgressContextRemovesPartialTargetOnCancel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
srcDir := filepath.Join(root, "src")
|
||||||
|
dstDir := filepath.Join(root, "dst")
|
||||||
|
if err := os.MkdirAll(srcDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("mkdir src: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dstDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("mkdir dst: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := 0; idx < 64; idx++ {
|
||||||
|
path := filepath.Join(srcDir, "file-"+strconv.Itoa(idx)+".txt")
|
||||||
|
if err := os.WriteFile(path, []byte("payload-"+strconv.Itoa(idx)), 0o644); err != nil {
|
||||||
|
t.Fatalf("write source file %d: %v", idx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := CopyStats(srcDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("copy stats: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err = CopyPathWithProgressContext(ctx, srcDir, dstDir, false, stats, func(progress CopyProgress) {
|
||||||
|
if progress.FilesDone >= 1 {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
t.Fatalf("expected context cancellation, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(dstDir, filepath.Base(srcDir))
|
||||||
|
if _, statErr := os.Stat(targetPath); !errors.Is(statErr, os.ErrNotExist) {
|
||||||
|
t.Fatalf("expected partial target to be removed, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMovePathWithProgressContextCancelledBeforeStartKeepsSource(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
srcFile := filepath.Join(root, "source.txt")
|
||||||
|
dstDir := filepath.Join(root, "dst")
|
||||||
|
if err := os.WriteFile(srcFile, []byte("payload"), 0o644); err != nil {
|
||||||
|
t.Fatalf("write source: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dstDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("mkdir dst: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := CopyStats(srcFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("copy stats: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
_, err = MovePathWithProgressContext(ctx, srcFile, dstDir, false, stats, nil)
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
t.Fatalf("expected context cancellation, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, statErr := os.Stat(srcFile); statErr != nil {
|
||||||
|
t.Fatalf("expected source to remain in place, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
targetPath := filepath.Join(dstDir, filepath.Base(srcFile))
|
||||||
|
if _, statErr := os.Stat(targetPath); !errors.Is(statErr, os.ErrNotExist) {
|
||||||
|
t.Fatalf("expected destination file to be absent, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenamePath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
source := filepath.Join(root, "old.txt")
|
||||||
|
if err := os.WriteFile(source, []byte("payload"), 0o644); err != nil {
|
||||||
|
t.Fatalf("write source: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := RenamePath(source, "new.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("rename: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepath.Base(target) != "new.txt" {
|
||||||
|
t.Fatalf("unexpected target path: %s", target)
|
||||||
|
}
|
||||||
|
if _, statErr := os.Stat(target); statErr != nil {
|
||||||
|
t.Fatalf("expected renamed file to exist, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
if _, statErr := os.Stat(source); !errors.Is(statErr, os.ErrNotExist) {
|
||||||
|
t.Fatalf("expected source to be absent, stat err=%v", statErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenamePathRejectsExistingTarget(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
source := filepath.Join(root, "old.txt")
|
||||||
|
target := filepath.Join(root, "new.txt")
|
||||||
|
if err := os.WriteFile(source, []byte("payload"), 0o644); err != nil {
|
||||||
|
t.Fatalf("write source: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(target, []byte("payload"), 0o644); err != nil {
|
||||||
|
t.Fatalf("write target: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := RenamePath(source, "new.txt")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected overwrite error, got nil")
|
||||||
|
}
|
||||||
|
if got, want := err.Error(), ErrOverwrite(target).Error(); got != want {
|
||||||
|
t.Fatalf("expected overwrite error %q, got %q", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
693
src/vcom-0.2.5/internal/fs/preview.go
Normal file
693
src/vcom-0.2.5/internal/fs/preview.go
Normal file
|
|
@ -0,0 +1,693 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
_ "image/gif"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
"github.com/alecthomas/chroma/v2/formatters"
|
||||||
|
"github.com/alecthomas/chroma/v2/lexers"
|
||||||
|
"github.com/alecthomas/chroma/v2/styles"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sgrRegexp = regexp.MustCompile(`\x1b\[([0-9;:]*)m`)
|
||||||
|
var sgrNumberRegexp = regexp.MustCompile(`\d+`)
|
||||||
|
|
||||||
|
type PreviewKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PreviewKindEmpty PreviewKind = "empty"
|
||||||
|
PreviewKindDirectory PreviewKind = "directory"
|
||||||
|
PreviewKindText PreviewKind = "text"
|
||||||
|
PreviewKindImage PreviewKind = "image"
|
||||||
|
PreviewKindPDF PreviewKind = "pdf"
|
||||||
|
PreviewKindAudio PreviewKind = "audio"
|
||||||
|
PreviewKindVideo PreviewKind = "video"
|
||||||
|
PreviewKindBinary PreviewKind = "binary"
|
||||||
|
PreviewKindError PreviewKind = "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Path string
|
||||||
|
Kind string
|
||||||
|
Size int64
|
||||||
|
SizeKnown bool
|
||||||
|
ModifiedAt string
|
||||||
|
CreatedAt string
|
||||||
|
Permissions string
|
||||||
|
ImageFormat string
|
||||||
|
ImageSize string
|
||||||
|
Extension string
|
||||||
|
|
||||||
|
// Extended preview metadata
|
||||||
|
Duration string
|
||||||
|
Bitrate string
|
||||||
|
AudioCodec string
|
||||||
|
VideoCodec string
|
||||||
|
SampleRate string
|
||||||
|
Channels string
|
||||||
|
PageCount string
|
||||||
|
Dimensions string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Preview struct {
|
||||||
|
Kind PreviewKind
|
||||||
|
Title string
|
||||||
|
Body string
|
||||||
|
PlainBody string
|
||||||
|
Metadata Metadata
|
||||||
|
Entries []Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreviewOptions struct {
|
||||||
|
ShowHidden bool
|
||||||
|
DirsFirst bool
|
||||||
|
SortBy string
|
||||||
|
SortReverse bool
|
||||||
|
MaxPreviewBytes int64
|
||||||
|
DirectoryPreviewLimit int
|
||||||
|
HumanReadableSize bool
|
||||||
|
ThemeName string
|
||||||
|
UseNerdIcons bool
|
||||||
|
ImagePreviewWidth int
|
||||||
|
ImagePreviewHeight int
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildPreview(entry Entry, options PreviewOptions) Preview {
|
||||||
|
preview := Preview{
|
||||||
|
Kind: PreviewKindEmpty,
|
||||||
|
Title: entry.DisplayName(),
|
||||||
|
Metadata: Metadata{
|
||||||
|
Path: entry.Path,
|
||||||
|
Kind: kindLabel(entry),
|
||||||
|
Permissions: Permissions(entry.Mode),
|
||||||
|
ModifiedAt: ShortTime(entry.ModifiedAt),
|
||||||
|
CreatedAt: "n/a",
|
||||||
|
Extension: entry.Extension,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.CreatedKnown {
|
||||||
|
preview.Metadata.CreatedAt = ShortTime(entry.CreatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.IsDir {
|
||||||
|
preview.Kind = PreviewKindDirectory
|
||||||
|
preview.Metadata.Size = entry.Size
|
||||||
|
preview.Metadata.SizeKnown = entry.DirSizeKnown
|
||||||
|
preview.Body, preview.Entries = buildDirectoryPreview(entry.Path, options)
|
||||||
|
preview.PlainBody = preview.Body
|
||||||
|
return preview
|
||||||
|
}
|
||||||
|
|
||||||
|
preview.Metadata.Size = entry.Size
|
||||||
|
preview.Metadata.SizeKnown = true
|
||||||
|
|
||||||
|
file, err := os.Open(entry.Path)
|
||||||
|
if err != nil {
|
||||||
|
preview.Kind = PreviewKindError
|
||||||
|
preview.Body = fmt.Sprintf("Could not open file:\n\n%s", err)
|
||||||
|
preview.PlainBody = preview.Body
|
||||||
|
return preview
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
if _, err := io.CopyN(buffer, file, options.MaxPreviewBytes); err != nil && err != io.EOF {
|
||||||
|
preview.Kind = PreviewKindError
|
||||||
|
preview.Body = fmt.Sprintf("Could not read preview:\n\n%s", err)
|
||||||
|
preview.PlainBody = preview.Body
|
||||||
|
return preview
|
||||||
|
}
|
||||||
|
|
||||||
|
data := buffer.Bytes()
|
||||||
|
if format, dimensions, ok := DetectImage(data); ok {
|
||||||
|
preview.Kind = PreviewKindImage
|
||||||
|
preview.Metadata.ImageFormat = format
|
||||||
|
preview.Metadata.ImageSize = dimensions
|
||||||
|
inline := renderImageInlinePreview(entry.Path, options.ImagePreviewWidth, options.ImagePreviewHeight)
|
||||||
|
if inline != "" {
|
||||||
|
preview.Body = inline
|
||||||
|
}
|
||||||
|
preview.PlainBody = preview.Body
|
||||||
|
return preview
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extended preview for PDF, audio, video via external utilities
|
||||||
|
if hasExt(pdfExtensions, entry.Extension) {
|
||||||
|
return buildPDFPreview(entry, options, preview)
|
||||||
|
}
|
||||||
|
if hasExt(audioExtensions, entry.Extension) {
|
||||||
|
return buildAudioPreview(entry, options, preview)
|
||||||
|
}
|
||||||
|
if hasExt(videoExtensions, entry.Extension) {
|
||||||
|
return buildVideoPreview(entry, options, preview)
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsBinarySample(data) {
|
||||||
|
preview.Kind = PreviewKindBinary
|
||||||
|
preview.Body = "Binary file detected.\n\nSafe inline preview is disabled for this file type."
|
||||||
|
preview.PlainBody = preview.Body
|
||||||
|
return preview
|
||||||
|
}
|
||||||
|
|
||||||
|
preview.Kind = PreviewKindText
|
||||||
|
preview.PlainBody = strings.ReplaceAll(string(data), "\t", " ")
|
||||||
|
preview.Body = highlightText(entry.Path, preview.PlainBody, options.ThemeName)
|
||||||
|
return preview
|
||||||
|
}
|
||||||
|
|
||||||
|
func highlightText(path string, source string, themeName string) string {
|
||||||
|
lexer := lexers.Match(path)
|
||||||
|
if lexer == nil {
|
||||||
|
lexer = lexers.Analyse(source)
|
||||||
|
}
|
||||||
|
if lexer == nil {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator, err := chroma.Coalesce(lexer).Tokenise(nil, source)
|
||||||
|
if err != nil {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
style := styles.Get(chromaStyleName(themeName))
|
||||||
|
if style == nil {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
style = styleWithoutBackground(style)
|
||||||
|
if style == nil {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
var output bytes.Buffer
|
||||||
|
if err := formatters.TTY16m.Format(&output, style, iterator); err != nil {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
return stripBackgroundSGR(output.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func styleWithoutBackground(base *chroma.Style) *chroma.Style {
|
||||||
|
if base == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
builder := base.Builder().Transform(func(entry chroma.StyleEntry) chroma.StyleEntry {
|
||||||
|
entry.Background = 0
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
stripped, err := builder.Build()
|
||||||
|
if err != nil {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
return stripped
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripBackgroundSGR(text string) string {
|
||||||
|
return sgrRegexp.ReplaceAllStringFunc(text, func(seq string) string {
|
||||||
|
matches := sgrRegexp.FindStringSubmatch(seq)
|
||||||
|
if len(matches) != 2 {
|
||||||
|
return seq
|
||||||
|
}
|
||||||
|
filtered := filterSGRParams(matches[1])
|
||||||
|
if filtered == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "\x1b[" + filtered + "m"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterSGRParams(paramString string) string {
|
||||||
|
if paramString == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := sgrNumberRegexp.FindAllString(paramString, -1)
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
codes := make([]int, 0, len(raw))
|
||||||
|
for _, token := range raw {
|
||||||
|
value, err := strconv.Atoi(token)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
codes = append(codes, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
kept := make([]string, 0, len(codes))
|
||||||
|
for i := 0; i < len(codes); i++ {
|
||||||
|
code := codes[i]
|
||||||
|
|
||||||
|
if code == 0 {
|
||||||
|
// Do not hard-reset background to terminal default.
|
||||||
|
// Reset common text attributes + foreground only.
|
||||||
|
kept = append(kept, "39", "22", "23", "24", "59")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if code == 49 || code == 7 || code == 27 || (code >= 40 && code <= 47) || (code >= 100 && code <= 107) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch code {
|
||||||
|
case 48:
|
||||||
|
// Background color payloads:
|
||||||
|
// 48;5;n or 48;2;r;g;b (also appears in ':' form; parsed as the same int stream).
|
||||||
|
if i+1 < len(codes) {
|
||||||
|
mode := codes[i+1]
|
||||||
|
switch mode {
|
||||||
|
case 5:
|
||||||
|
i += 2
|
||||||
|
case 2:
|
||||||
|
i += 4
|
||||||
|
default:
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case 38, 58:
|
||||||
|
// Preserve foreground (38) and underline color (58) payloads.
|
||||||
|
kept = append(kept, strconv.Itoa(code))
|
||||||
|
if i+1 < len(codes) {
|
||||||
|
mode := codes[i+1]
|
||||||
|
kept = append(kept, strconv.Itoa(mode))
|
||||||
|
switch mode {
|
||||||
|
case 5:
|
||||||
|
if i+2 < len(codes) {
|
||||||
|
kept = append(kept, strconv.Itoa(codes[i+2]))
|
||||||
|
}
|
||||||
|
i += 2
|
||||||
|
case 2:
|
||||||
|
if i+4 < len(codes) {
|
||||||
|
kept = append(kept,
|
||||||
|
strconv.Itoa(codes[i+2]),
|
||||||
|
strconv.Itoa(codes[i+3]),
|
||||||
|
strconv.Itoa(codes[i+4]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
i += 4
|
||||||
|
default:
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
kept = append(kept, strconv.Itoa(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(kept, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
func chromaStyleName(themeName string) string {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(themeName)) {
|
||||||
|
case "catppuccin-mocha", "catppuccin-lavender":
|
||||||
|
return "catppuccin-mocha"
|
||||||
|
case "tokyo-night":
|
||||||
|
return "tokyonight-night"
|
||||||
|
case "gruvbox-dark", "gruvbox":
|
||||||
|
return "gruvbox"
|
||||||
|
case "nord-frost", "nord":
|
||||||
|
return "nord"
|
||||||
|
case "dracula":
|
||||||
|
return "dracula"
|
||||||
|
case "rose-pine":
|
||||||
|
return "rose-pine"
|
||||||
|
case "solarized-dark":
|
||||||
|
return "solarized-dark"
|
||||||
|
default:
|
||||||
|
return "catppuccin-mocha"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDirectoryPreview(path string, options PreviewOptions) (string, []Entry) {
|
||||||
|
entries, err := ListDir(path, ListOptions{
|
||||||
|
ShowHidden: options.ShowHidden,
|
||||||
|
DirsFirst: options.DirsFirst,
|
||||||
|
SortBy: options.SortBy,
|
||||||
|
SortReverse: options.SortReverse,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("Could not list directory:\n\n%s", err), nil
|
||||||
|
}
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return "Directory is empty.", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all entries as-is for column-based rendering.
|
||||||
|
// The text body is still generated for terminals that don't support
|
||||||
|
// the rich rendering, and as a fallback.
|
||||||
|
var lines []string
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsParent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
icon := previewIcon(entry, options.UseNerdIcons)
|
||||||
|
|
||||||
|
size := ""
|
||||||
|
if !entry.IsDir {
|
||||||
|
if options.HumanReadableSize {
|
||||||
|
size = HumanSize(entry.Size)
|
||||||
|
} else {
|
||||||
|
size = fmt.Sprintf("%d", entry.Size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, fmt.Sprintf("%s %-36s %12s %s", icon, entry.DisplayName(), size, ShortTime(entry.ModifiedAt)))
|
||||||
|
if len(lines) >= options.DirectoryPreviewLimit {
|
||||||
|
lines = append(lines, "…")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(lines, "\n"), entries
|
||||||
|
}
|
||||||
|
|
||||||
|
func previewIcon(entry Entry, useNerdIcons bool) string {
|
||||||
|
if !useNerdIcons {
|
||||||
|
switch entry.Category() {
|
||||||
|
case "directory":
|
||||||
|
return "[D]"
|
||||||
|
case "config":
|
||||||
|
return "[C]"
|
||||||
|
case "text":
|
||||||
|
return "[T]"
|
||||||
|
case "image":
|
||||||
|
return "[I]"
|
||||||
|
case "executable":
|
||||||
|
return "[X]"
|
||||||
|
case "archive":
|
||||||
|
return "[A]"
|
||||||
|
default:
|
||||||
|
return "[F]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch entry.Category() {
|
||||||
|
case "directory":
|
||||||
|
return ""
|
||||||
|
case "config":
|
||||||
|
return ""
|
||||||
|
case "text":
|
||||||
|
return ""
|
||||||
|
case "image":
|
||||||
|
return ""
|
||||||
|
case "executable":
|
||||||
|
return ""
|
||||||
|
case "archive":
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderImageInlinePreview(path string, width int, height int) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func findTool(name string) string {
|
||||||
|
path, err := exec.LookPath(name)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPDFPreview(entry Entry, options PreviewOptions, base Preview) Preview {
|
||||||
|
pdftotext := findTool("pdftotext")
|
||||||
|
if pdftotext == "" {
|
||||||
|
base.Kind = PreviewKindBinary
|
||||||
|
base.Body = "PDF file detected.\nInstall poppler-utils (pdftotext) for text preview."
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(pdftotext, "-layout", "-nopgbrk", entry.Path, "-")
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("pdftotext error:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
text := string(out)
|
||||||
|
if int64(len(text)) > options.MaxPreviewBytes {
|
||||||
|
text = text[:options.MaxPreviewBytes]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get page count via pdfinfo if available
|
||||||
|
pdfinfo := findTool("pdfinfo")
|
||||||
|
if pdfinfo != "" {
|
||||||
|
infoCmd := exec.Command(pdfinfo, entry.Path)
|
||||||
|
if infoOut, err := infoCmd.Output(); err == nil {
|
||||||
|
for _, line := range strings.Split(string(infoOut), "\n") {
|
||||||
|
if strings.HasPrefix(strings.ToLower(line), "pages:") {
|
||||||
|
base.Metadata.PageCount = strings.TrimSpace(strings.TrimPrefix(line[5:], ":"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Kind = PreviewKindPDF
|
||||||
|
base.PlainBody = text
|
||||||
|
base.Body = highlightText(entry.Path, text, options.ThemeName)
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAudioPreview(entry Entry, options PreviewOptions, base Preview) Preview {
|
||||||
|
ffprobe := findTool("ffprobe")
|
||||||
|
if ffprobe == "" {
|
||||||
|
base.Kind = PreviewKindBinary
|
||||||
|
base.Body = "Audio file detected.\nInstall ffmpeg (ffprobe) for metadata preview."
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(ffprobe,
|
||||||
|
"-v", "quiet",
|
||||||
|
"-print_format", "json",
|
||||||
|
"-show_format",
|
||||||
|
"-show_streams",
|
||||||
|
entry.Path,
|
||||||
|
)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("ffprobe error:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
var info struct {
|
||||||
|
Format struct {
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
Bitrate string `json:"bit_rate"`
|
||||||
|
} `json:"format"`
|
||||||
|
Streams []struct {
|
||||||
|
CodecType string `json:"codec_type"`
|
||||||
|
CodecName string `json:"codec_name"`
|
||||||
|
SampleRate string `json:"sample_rate"`
|
||||||
|
Channels int `json:"channels"`
|
||||||
|
} `json:"streams"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(out, &info); err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("Could not parse ffprobe output:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format duration
|
||||||
|
if info.Format.Duration != "" {
|
||||||
|
if secs, err := strconv.ParseFloat(info.Format.Duration, 64); err == nil {
|
||||||
|
mins := int(secs) / 60
|
||||||
|
secsRem := int(secs) % 60
|
||||||
|
base.Metadata.Duration = fmt.Sprintf("%d:%02d", mins, secsRem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Format.Bitrate != "" {
|
||||||
|
if bps, err := strconv.ParseInt(info.Format.Bitrate, 10, 64); err == nil {
|
||||||
|
base.Metadata.Bitrate = fmt.Sprintf("%d kbps", bps/1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stream := range info.Streams {
|
||||||
|
if stream.CodecType == "audio" {
|
||||||
|
base.Metadata.AudioCodec = stream.CodecName
|
||||||
|
if stream.SampleRate != "" {
|
||||||
|
base.Metadata.SampleRate = stream.SampleRate + " Hz"
|
||||||
|
}
|
||||||
|
switch stream.Channels {
|
||||||
|
case 1:
|
||||||
|
base.Metadata.Channels = "mono"
|
||||||
|
case 2:
|
||||||
|
base.Metadata.Channels = "stereo"
|
||||||
|
case 6:
|
||||||
|
base.Metadata.Channels = "5.1"
|
||||||
|
case 8:
|
||||||
|
base.Metadata.Channels = "7.1"
|
||||||
|
default:
|
||||||
|
base.Metadata.Channels = fmt.Sprintf("%d ch", stream.Channels)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines []string
|
||||||
|
lines = append(lines, fmt.Sprintf(" Duration: %s", base.Metadata.Duration))
|
||||||
|
if base.Metadata.Bitrate != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Bitrate: %s", base.Metadata.Bitrate))
|
||||||
|
}
|
||||||
|
if base.Metadata.AudioCodec != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Codec: %s", base.Metadata.AudioCodec))
|
||||||
|
}
|
||||||
|
if base.Metadata.SampleRate != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Rate: %s", base.Metadata.SampleRate))
|
||||||
|
}
|
||||||
|
if base.Metadata.Channels != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Channels: %s", base.Metadata.Channels))
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Kind = PreviewKindAudio
|
||||||
|
base.Body = fmt.Sprintf("🎵 Audio File\n\n%s", strings.Join(lines, "\n"))
|
||||||
|
base.PlainBody = fmt.Sprintf("Audio File\n\nDuration: %s\nBitrate: %s\nCodec: %s\nRate: %s\nChannels: %s",
|
||||||
|
base.Metadata.Duration, base.Metadata.Bitrate, base.Metadata.AudioCodec,
|
||||||
|
base.Metadata.SampleRate, base.Metadata.Channels)
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildVideoPreview(entry Entry, options PreviewOptions, base Preview) Preview {
|
||||||
|
ffprobe := findTool("ffprobe")
|
||||||
|
if ffprobe == "" {
|
||||||
|
base.Kind = PreviewKindBinary
|
||||||
|
base.Body = "Video file detected.\nInstall ffmpeg (ffprobe) for metadata preview."
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(ffprobe,
|
||||||
|
"-v", "quiet",
|
||||||
|
"-print_format", "json",
|
||||||
|
"-show_format",
|
||||||
|
"-show_streams",
|
||||||
|
entry.Path,
|
||||||
|
)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("ffprobe error:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
var info struct {
|
||||||
|
Format struct {
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
Bitrate string `json:"bit_rate"`
|
||||||
|
} `json:"format"`
|
||||||
|
Streams []struct {
|
||||||
|
CodecType string `json:"codec_type"`
|
||||||
|
CodecName string `json:"codec_name"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
} `json:"streams"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(out, &info); err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("Could not parse ffprobe output:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format duration
|
||||||
|
if info.Format.Duration != "" {
|
||||||
|
if secs, err := strconv.ParseFloat(info.Format.Duration, 64); err == nil {
|
||||||
|
hrs := int(secs) / 3600
|
||||||
|
mins := (int(secs) % 3600) / 60
|
||||||
|
secsRem := int(secs) % 60
|
||||||
|
if hrs > 0 {
|
||||||
|
base.Metadata.Duration = fmt.Sprintf("%d:%02d:%02d", hrs, mins, secsRem)
|
||||||
|
} else {
|
||||||
|
base.Metadata.Duration = fmt.Sprintf("%d:%02d", mins, secsRem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Format.Bitrate != "" {
|
||||||
|
if bps, err := strconv.ParseInt(info.Format.Bitrate, 10, 64); err == nil {
|
||||||
|
base.Metadata.Bitrate = fmt.Sprintf("%d kbps", bps/1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stream := range info.Streams {
|
||||||
|
switch stream.CodecType {
|
||||||
|
case "video":
|
||||||
|
base.Metadata.VideoCodec = stream.CodecName
|
||||||
|
if stream.Width > 0 && stream.Height > 0 {
|
||||||
|
base.Metadata.Dimensions = fmt.Sprintf("%dx%d", stream.Width, stream.Height)
|
||||||
|
}
|
||||||
|
case "audio":
|
||||||
|
if base.Metadata.AudioCodec == "" {
|
||||||
|
base.Metadata.AudioCodec = stream.CodecName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines []string
|
||||||
|
lines = append(lines, fmt.Sprintf(" Duration: %s", base.Metadata.Duration))
|
||||||
|
if base.Metadata.Bitrate != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Bitrate: %s", base.Metadata.Bitrate))
|
||||||
|
}
|
||||||
|
if base.Metadata.VideoCodec != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Video: %s", base.Metadata.VideoCodec))
|
||||||
|
}
|
||||||
|
if base.Metadata.Dimensions != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Resolution: %s", base.Metadata.Dimensions))
|
||||||
|
}
|
||||||
|
if base.Metadata.AudioCodec != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Audio: %s", base.Metadata.AudioCodec))
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Kind = PreviewKindVideo
|
||||||
|
base.Body = fmt.Sprintf("🎬 Video File\n\n%s", strings.Join(lines, "\n"))
|
||||||
|
base.PlainBody = fmt.Sprintf("Video File\n\nDuration: %s\nBitrate: %s\nVideo: %s\nResolution: %s\nAudio: %s",
|
||||||
|
base.Metadata.Duration, base.Metadata.Bitrate, base.Metadata.VideoCodec,
|
||||||
|
base.Metadata.Dimensions, base.Metadata.AudioCodec)
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func DetectImage(data []byte) (string, string, bool) {
|
||||||
|
cfg, format, err := image.DecodeConfig(bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return format, fmt.Sprintf("%dx%d", cfg.Width, cfg.Height), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func kindLabel(entry Entry) string {
|
||||||
|
switch {
|
||||||
|
case entry.IsParent:
|
||||||
|
return "parent"
|
||||||
|
case entry.IsDir:
|
||||||
|
return "directory"
|
||||||
|
case entry.Extension != "":
|
||||||
|
return "file"
|
||||||
|
default:
|
||||||
|
return strings.TrimPrefix(filepath.Ext(entry.Name), ".")
|
||||||
|
}
|
||||||
|
}
|
||||||
751
src/vcom-0.2.5/internal/fs/remote/client.go
Normal file
751
src/vcom-0.2.5/internal/fs/remote/client.go
Normal file
|
|
@ -0,0 +1,751 @@
|
||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSHClient wraps an SSH connection and SFTP client for remote filesystem access.
|
||||||
|
type SSHClient struct {
|
||||||
|
// Host is the SSH host configuration used to establish the connection.
|
||||||
|
Host SSHHost
|
||||||
|
|
||||||
|
sshConn *ssh.Client
|
||||||
|
sftpCli *sftp.Client
|
||||||
|
|
||||||
|
keepaliveStop chan struct{}
|
||||||
|
keepaliveWg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect establishes an SSH connection to the remote host and opens an SFTP session.
|
||||||
|
// It uses key-based authentication if IdentityFile is set, otherwise falls back to password auth.
|
||||||
|
func Connect(host SSHHost) (*SSHClient, error) {
|
||||||
|
authMethods, err := authMethodsForHost(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ssh auth: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := host.User
|
||||||
|
if user == "" {
|
||||||
|
user = os.Getenv("USER")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: user,
|
||||||
|
Auth: authMethods,
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // TODO: support known_hosts verification
|
||||||
|
Timeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := host.Addr()
|
||||||
|
sshConn, err := ssh.Dial("tcp", addr, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ssh dial %s: %w", addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sftpCli, err := sftp.NewClient(sshConn)
|
||||||
|
if err != nil {
|
||||||
|
sshConn.Close()
|
||||||
|
return nil, fmt.Errorf("sftp client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &SSHClient{
|
||||||
|
Host: host,
|
||||||
|
sshConn: sshConn,
|
||||||
|
sftpCli: sftpCli,
|
||||||
|
keepaliveStop: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start keepalive goroutine — sends keepalive@openssh.com every 30s
|
||||||
|
// to prevent the SSH server from dropping the connection during inactivity.
|
||||||
|
client.keepaliveWg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer client.keepaliveWg.Done()
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
_, _, err := sshConn.SendRequest("keepalive@openssh.com", true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-client.keepaliveStop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authMethodsForHost returns the appropriate SSH auth methods for the given host.
|
||||||
|
// For SSH config hosts with IdentityFile, it uses public key authentication.
|
||||||
|
// For custom hosts with a password, it uses password authentication.
|
||||||
|
func authMethodsForHost(host SSHHost) ([]ssh.AuthMethod, error) {
|
||||||
|
var methods []ssh.AuthMethod
|
||||||
|
|
||||||
|
// Try key-based auth if identity file is specified
|
||||||
|
if host.IdentityFile != "" {
|
||||||
|
key, err := os.ReadFile(host.IdentityFile)
|
||||||
|
if err == nil {
|
||||||
|
signer, err := ssh.ParsePrivateKey(key)
|
||||||
|
if err == nil {
|
||||||
|
methods = append(methods, ssh.PublicKeys(signer))
|
||||||
|
} else {
|
||||||
|
// If the key is encrypted, try with empty passphrase or common ones
|
||||||
|
// For simplicity, we try without passphrase first
|
||||||
|
// In a real implementation, we might prompt for a passphrase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try password auth if password is set
|
||||||
|
if host.Password != "" {
|
||||||
|
methods = append(methods, ssh.Password(host.Password))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always include keyboard-interactive as a fallback (it wraps password)
|
||||||
|
if host.Password != "" {
|
||||||
|
methods = append(methods, ssh.KeyboardInteractive(
|
||||||
|
func(user, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||||
|
answers := make([]string, len(questions))
|
||||||
|
for i := range questions {
|
||||||
|
answers[i] = host.Password
|
||||||
|
}
|
||||||
|
return answers, nil
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always try default SSH agent and default keys as a last resort
|
||||||
|
// This covers the case where the user has an SSH agent running with loaded keys
|
||||||
|
// but no IdentityFile is specified in the config.
|
||||||
|
if host.IdentityFile == "" && host.Password == "" {
|
||||||
|
// Add default key paths
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err == nil {
|
||||||
|
defaultKeys := []string{
|
||||||
|
home + "/.ssh/id_rsa",
|
||||||
|
home + "/.ssh/id_ed25519",
|
||||||
|
home + "/.ssh/id_ecdsa",
|
||||||
|
home + "/.ssh/id_ecdsa_sk",
|
||||||
|
home + "/.ssh/id_ed25519_sk",
|
||||||
|
home + "/.ssh/identity",
|
||||||
|
}
|
||||||
|
for _, keyPath := range defaultKeys {
|
||||||
|
if key, err := os.ReadFile(keyPath); err == nil {
|
||||||
|
if signer, err := ssh.ParsePrivateKey(key); err == nil {
|
||||||
|
methods = append(methods, ssh.PublicKeys(signer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(methods) == 0 {
|
||||||
|
return nil, fmt.Errorf("no authentication methods available for host %q", host.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return methods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDir reads the contents of a remote directory and returns os.FileInfo entries.
|
||||||
|
func (c *SSHClient) ReadDir(dirPath string) ([]os.FileInfo, error) {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.ReadDir(dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lstat returns file information without following symlinks.
|
||||||
|
func (c *SSHClient) Lstat(path string) (os.FileInfo, error) {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.Lstat(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns file information following symlinks.
|
||||||
|
func (c *SSHClient) Stat(path string) (os.FileInfo, error) {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.Stat(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLink reads the target of a symbolic link.
|
||||||
|
func (c *SSHClient) ReadLink(linkPath string) (string, error) {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return "", fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.ReadLink(linkPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealPath resolves a path to its absolute form on the remote server.
|
||||||
|
func (c *SSHClient) RealPath(p string) (string, error) {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return "", fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.RealPath(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile opens a remote file for reading.
|
||||||
|
func (c *SSHClient) ReadFile(filePath string) (io.ReadCloser, error) {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.Open(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFile opens a remote file for writing, creating it if it doesn't exist.
|
||||||
|
func (c *SSHClient) CreateFile(filePath string) (io.WriteCloser, error) {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.Create(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAll creates a remote directory and any necessary parents.
|
||||||
|
// If the directory already exists, it returns nil (no error).
|
||||||
|
func (c *SSHClient) MkdirAll(dirPath string) error {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
// sftp doesn't have MkdirAll, so we implement it manually
|
||||||
|
// First check if the path already exists
|
||||||
|
_, err := c.sftpCli.Stat(dirPath)
|
||||||
|
if err == nil {
|
||||||
|
return nil // already exists
|
||||||
|
}
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Ensure parent exists first
|
||||||
|
parent := path.Dir(dirPath)
|
||||||
|
if parent != dirPath && parent != "." {
|
||||||
|
if err := c.MkdirAll(parent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.sftpCli.Mkdir(dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mkdir creates a single remote directory.
|
||||||
|
func (c *SSHClient) Mkdir(dirPath string) error {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.Mkdir(dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes a remote file.
|
||||||
|
func (c *SSHClient) Remove(filePath string) error {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.Remove(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDirectory removes a remote directory (must be empty).
|
||||||
|
func (c *SSHClient) RemoveDirectory(dirPath string) error {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.RemoveDirectory(dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename moves/renames a remote file or directory.
|
||||||
|
func (c *SSHClient) Rename(oldPath, newPath string) error {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
return c.sftpCli.Rename(oldPath, newPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the SFTP session and SSH connection.
|
||||||
|
func (c *SSHClient) Close() error {
|
||||||
|
// Stop the keepalive goroutine first
|
||||||
|
if c.keepaliveStop != nil {
|
||||||
|
select {
|
||||||
|
case <-c.keepaliveStop:
|
||||||
|
// already closed
|
||||||
|
default:
|
||||||
|
close(c.keepaliveStop)
|
||||||
|
}
|
||||||
|
c.keepaliveWg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
|
||||||
|
if c.sftpCli != nil {
|
||||||
|
if err := c.sftpCli.Close(); err != nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
c.sftpCli = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.sshConn != nil {
|
||||||
|
if err := c.sshConn.Close(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
c.sshConn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsConnected returns true if the client has an active connection.
|
||||||
|
func (c *SSHClient) IsConnected() bool {
|
||||||
|
return c.sftpCli != nil && c.sshConn != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec runs a shell command on the remote server and returns combined output.
|
||||||
|
func (c *SSHClient) Exec(cmd string) ([]byte, error) {
|
||||||
|
if c.sshConn == nil {
|
||||||
|
return nil, fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
session, err := c.sshConn.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open session: %w", err)
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
return session.CombinedOutput(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecWithProgress runs a shell command on the remote server and calls onLine
|
||||||
|
// for each line of stdout output.
|
||||||
|
func (c *SSHClient) ExecWithProgress(cmd string, onLine func(line string)) error {
|
||||||
|
if c.sshConn == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
session, err := c.sshConn.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open session: %w", err)
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
stdout, err := session.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stdout pipe: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := session.Start(cmd); err != nil {
|
||||||
|
return fmt.Errorf("start command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
onLine(scanner.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanErr := scanner.Err(); scanErr != nil {
|
||||||
|
return scanErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SameHostAs returns true if this client and other are connected to the same server.
|
||||||
|
func (c *SSHClient) SameHostAs(other *SSHClient) bool {
|
||||||
|
if c == nil || other == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return c.Host.SameAs(other.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRecursive recursively deletes a remote file or directory.
|
||||||
|
// For directories, it walks and removes all children first.
|
||||||
|
func (c *SSHClient) RemoveRecursive(path string) error {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := c.sftpCli.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
return c.sftpCli.Remove(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk directory and collect all paths (files first, then dirs)
|
||||||
|
var files []string
|
||||||
|
var dirs []string
|
||||||
|
err = c.Walk(path, func(walkPath string, info os.FileInfo, walkErr error) error {
|
||||||
|
if walkErr != nil {
|
||||||
|
return walkErr
|
||||||
|
}
|
||||||
|
if walkPath == path {
|
||||||
|
return nil // skip root
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
dirs = append(dirs, walkPath)
|
||||||
|
} else {
|
||||||
|
files = append(files, walkPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove files first, then directories (reverse order for deepest first)
|
||||||
|
for _, f := range files {
|
||||||
|
if err := c.sftpCli.Remove(f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := len(dirs) - 1; i >= 0; i-- {
|
||||||
|
if err := c.sftpCli.RemoveDirectory(dirs[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally remove the root directory
|
||||||
|
return c.sftpCli.RemoveDirectory(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFileToRemote copies a local file to a remote destination via SFTP.
|
||||||
|
// It creates parent directories as needed.
|
||||||
|
func (c *SSHClient) CopyFileToRemote(localPath, remotePath string) error {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
localFile, err := os.Open(localPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open local: %w", err)
|
||||||
|
}
|
||||||
|
defer localFile.Close()
|
||||||
|
|
||||||
|
// Ensure parent directory exists
|
||||||
|
parent := path.Dir(remotePath)
|
||||||
|
if err := c.MkdirAll(parent); err != nil {
|
||||||
|
return fmt.Errorf("mkdir remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteFile, err := c.sftpCli.Create(remotePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create remote: %w", err)
|
||||||
|
}
|
||||||
|
defer remoteFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(remoteFile, localFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copy to remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFileFromRemote copies a remote file to a local destination via SFTP.
|
||||||
|
// It creates parent directories as needed.
|
||||||
|
func (c *SSHClient) CopyFileFromRemote(remotePath, localPath string) error {
|
||||||
|
if c.sftpCli == nil {
|
||||||
|
return fmt.Errorf("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteFile, err := c.sftpCli.Open(remotePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open remote: %w", err)
|
||||||
|
}
|
||||||
|
defer remoteFile.Close()
|
||||||
|
|
||||||
|
// Ensure parent directory exists
|
||||||
|
parent := filepath.Dir(localPath)
|
||||||
|
if err := os.MkdirAll(parent, 0o755); err != nil {
|
||||||
|
return fmt.Errorf("mkdir local: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
localFile, err := os.Create(localPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create local: %w", err)
|
||||||
|
}
|
||||||
|
defer localFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(localFile, remoteFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copy from remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadFile downloads a remote file to a local path via SFTP.
|
||||||
|
func (c *SSHClient) DownloadFile(remotePath, localPath string) error {
|
||||||
|
return c.CopyFileFromRemote(remotePath, localPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDirToRemote recursively copies a local directory to a remote path.
|
||||||
|
func (c *SSHClient) CopyDirToRemote(localDir, remoteDir string) error {
|
||||||
|
return c.copyDirToRemote(localDir, remoteDir, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDirToRemoteProgress is like CopyDirToRemote but calls onFile after each copy.
|
||||||
|
func (c *SSHClient) CopyDirToRemoteProgress(localDir, remoteDir string, onFile func(path string, done, total int), ctx context.Context) error {
|
||||||
|
return c.copyDirToRemote(localDir, remoteDir, onFile, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSHClient) copyDirToRemote(localDir, remoteDir string, onFile func(path string, done, total int), ctx context.Context) error {
|
||||||
|
done := 0
|
||||||
|
return filepath.Walk(localDir, func(localPath string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ctx != nil {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relPath, _ := filepath.Rel(localDir, localPath)
|
||||||
|
remotePath := path.Join(remoteDir, relPath)
|
||||||
|
if info.IsDir() {
|
||||||
|
return c.MkdirAll(remotePath)
|
||||||
|
}
|
||||||
|
if err := c.CopyFileToRemote(localPath, remotePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
done++
|
||||||
|
if onFile != nil {
|
||||||
|
onFile(remotePath, done, 0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDirFromRemote recursively copies a remote directory to a local path.
|
||||||
|
func (c *SSHClient) CopyDirFromRemote(remoteDir, localDir string) error {
|
||||||
|
return c.copyDirFromRemote(remoteDir, localDir, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDirFromRemoteProgress is like CopyDirFromRemote but calls onFile after each copy.
|
||||||
|
func (c *SSHClient) CopyDirFromRemoteProgress(remoteDir, localDir string, onFile func(path string, done, total int), ctx context.Context) error {
|
||||||
|
return c.copyDirFromRemote(remoteDir, localDir, onFile, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSHClient) copyDirFromRemote(remoteDir, localDir string, onFile func(path string, done, total int), ctx context.Context) error {
|
||||||
|
done := 0
|
||||||
|
return c.Walk(remoteDir, func(remotePath string, info os.FileInfo, err error) error {
|
||||||
|
if ctx != nil {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
relPath, _ := filepath.Rel(remoteDir, remotePath)
|
||||||
|
localPath := filepath.Join(localDir, relPath)
|
||||||
|
if info.IsDir() {
|
||||||
|
return os.MkdirAll(localPath, 0o755)
|
||||||
|
}
|
||||||
|
if err := c.CopyFileFromRemote(remotePath, localPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
done++
|
||||||
|
if onFile != nil {
|
||||||
|
onFile(localPath, done, 0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFileBetweenRemotes copies a single file from one remote host to another
|
||||||
|
// by streaming the file contents through the local machine. Both SFTP connections
|
||||||
|
// must be active (connected).
|
||||||
|
func CopyFileBetweenRemotes(srcClient, dstClient *SSHClient, srcPath, dstPath string) error {
|
||||||
|
if srcClient.sftpCli == nil {
|
||||||
|
return fmt.Errorf("source client not connected")
|
||||||
|
}
|
||||||
|
if dstClient.sftpCli == nil {
|
||||||
|
return fmt.Errorf("destination client not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
srcFile, err := srcClient.sftpCli.Open(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open remote source %s: %w", srcPath, err)
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
// Ensure parent directory exists on the destination
|
||||||
|
parent := path.Dir(dstPath)
|
||||||
|
if err := dstClient.MkdirAll(parent); err != nil {
|
||||||
|
return fmt.Errorf("mkdir remote dest %s: %w", parent, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstFile, err := dstClient.sftpCli.Create(dstPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create remote dest %s: %w", dstPath, err)
|
||||||
|
}
|
||||||
|
defer dstFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(dstFile, srcFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copy remote to remote %s → %s: %w", srcPath, dstPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDirBetweenRemotes recursively copies a directory from one remote host to another.
|
||||||
|
func CopyDirBetweenRemotes(srcClient, dstClient *SSHClient, srcDir, dstDir string) error {
|
||||||
|
return copyDirBetweenRemotes(srcClient, dstClient, srcDir, dstDir, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDirBetweenRemotes(srcClient, dstClient *SSHClient, srcDir, dstDir string, onFile func(path string, done, total int), ctx context.Context) error {
|
||||||
|
done := 0
|
||||||
|
return srcClient.Walk(srcDir, func(remotePath string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ctx != nil {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relPath, _ := filepath.Rel(srcDir, remotePath)
|
||||||
|
dstPath := path.Join(dstDir, relPath)
|
||||||
|
if info.IsDir() {
|
||||||
|
return dstClient.MkdirAll(dstPath)
|
||||||
|
}
|
||||||
|
if err := CopyFileBetweenRemotes(srcClient, dstClient, remotePath, dstPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
done++
|
||||||
|
if onFile != nil {
|
||||||
|
onFile(remotePath, done, 0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDirBetweenRemotesProgress is like CopyDirBetweenRemotes with progress and context support.
|
||||||
|
func CopyDirBetweenRemotesProgress(srcClient, dstClient *SSHClient, srcDir, dstDir string, onFile func(path string, done, total int), ctx context.Context) error {
|
||||||
|
return copyDirBetweenRemotes(srcClient, dstClient, srcDir, dstDir, onFile, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk walks the remote filesystem tree rooted at root, calling walkFn for each file/dir.
|
||||||
|
// This is a simplified version of filepath.Walk for SFTP.
|
||||||
|
type walkFunc func(path string, info os.FileInfo, err error) error
|
||||||
|
|
||||||
|
func (c *SSHClient) Walk(root string, walkFn walkFunc) error {
|
||||||
|
return c.walk(root, walkFn, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SSHClient) walk(dirPath string, walkFn walkFunc, info os.FileInfo) error {
|
||||||
|
if info == nil {
|
||||||
|
var err error
|
||||||
|
info, err = c.sftpCli.Stat(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(dirPath, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := walkFn(dirPath, info, nil)
|
||||||
|
if err != nil {
|
||||||
|
if err == filepathSkipDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := c.sftpCli.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(dirPath, info, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
childPath := path.Join(dirPath, entry.Name())
|
||||||
|
if entry.IsDir() {
|
||||||
|
err = c.walk(childPath, walkFn, entry)
|
||||||
|
} else {
|
||||||
|
err = walkFn(childPath, entry, nil)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// filepathSkipDir is used as a return value from Walk to skip a directory.
|
||||||
|
var filepathSkipDir = fmt.Errorf("skip this directory")
|
||||||
|
|
||||||
|
// DirectorySize recursively walks a remote directory and sums up file sizes.
|
||||||
|
func (c *SSHClient) DirectorySize(dirPath string) (int64, error) {
|
||||||
|
var total int64
|
||||||
|
err := c.Walk(dirPath, func(_ string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
total += info.Size()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpToFileInfo converts an os.FileInfo to a vfs-compatible file info.
|
||||||
|
// This is used for consistent file information handling across local and remote.
|
||||||
|
func SftpToFileInfo(name string, info os.FileInfo) (os.FileInfo, error) {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkDirEntry wraps os.FileInfo with the file name for directory listings.
|
||||||
|
type WalkDirEntry struct {
|
||||||
|
os.FileInfo
|
||||||
|
entryName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *WalkDirEntry) Name() string {
|
||||||
|
return e.entryName
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWalkDirEntry creates a new WalkDirEntry with an overridden name.
|
||||||
|
func NewWalkDirEntry(info os.FileInfo, name string) *WalkDirEntry {
|
||||||
|
return &WalkDirEntry{FileInfo: info, entryName: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTimeout is the timeout for establishing SSH connections.
|
||||||
|
const DialTimeout = 15 * time.Second
|
||||||
|
|
||||||
|
// DefaultPort is the default SSH port.
|
||||||
|
const DefaultPort = "22"
|
||||||
|
|
||||||
|
// ResolveAddr returns the SSH address for the given host, applying the default port if needed.
|
||||||
|
func ResolveAddr(hostname, port string) string {
|
||||||
|
host := strings.TrimSpace(hostname)
|
||||||
|
if port == "" || port == "0" {
|
||||||
|
port = DefaultPort
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(host, port)
|
||||||
|
}
|
||||||
209
src/vcom-0.2.5/internal/fs/remote/config.go
Normal file
209
src/vcom-0.2.5/internal/fs/remote/config.go
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseSSHConfig parses ~/.ssh/config and returns a list of SSH hosts.
|
||||||
|
// It handles the most common SSH config directives: Host, HostName, Port, User, IdentityFile.
|
||||||
|
func ParseSSHConfig() []SSHHost {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath := filepath.Join(home, ".ssh", "config")
|
||||||
|
return parseSSHConfigFile(configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSSHConfigFile(path string) []SSHHost {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var hosts []SSHHost
|
||||||
|
var current *SSHHost
|
||||||
|
var currentNames []string
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
// Skip empty lines and comments
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove inline comments (everything after # that's not in quotes)
|
||||||
|
if idx := strings.Index(line, "#"); idx >= 0 {
|
||||||
|
line = strings.TrimSpace(line[:idx])
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keyword := strings.ToLower(parts[0])
|
||||||
|
value := strings.Join(parts[1:], " ")
|
||||||
|
|
||||||
|
switch keyword {
|
||||||
|
case "host":
|
||||||
|
// Save previous host block
|
||||||
|
if current != nil && len(currentNames) > 0 {
|
||||||
|
for _, name := range currentNames {
|
||||||
|
if !isWildcardPattern(name) {
|
||||||
|
host := *current
|
||||||
|
host.Name = name
|
||||||
|
hosts = append(hosts, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start new host block
|
||||||
|
current = &SSHHost{
|
||||||
|
Port: "22",
|
||||||
|
FromSSHConfig: true,
|
||||||
|
}
|
||||||
|
currentNames = strings.Fields(value)
|
||||||
|
|
||||||
|
case "hostname":
|
||||||
|
if current != nil {
|
||||||
|
current.HostName = value
|
||||||
|
}
|
||||||
|
|
||||||
|
case "port":
|
||||||
|
if current != nil {
|
||||||
|
current.Port = value
|
||||||
|
}
|
||||||
|
|
||||||
|
case "user":
|
||||||
|
if current != nil {
|
||||||
|
current.User = value
|
||||||
|
}
|
||||||
|
|
||||||
|
case "identityfile":
|
||||||
|
if current != nil {
|
||||||
|
// Handle ~ expansion and relative paths
|
||||||
|
resolved := resolveIdentityPath(value)
|
||||||
|
if resolved != "" {
|
||||||
|
current.IdentityFile = resolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save last host block
|
||||||
|
if current != nil && len(currentNames) > 0 {
|
||||||
|
for _, name := range currentNames {
|
||||||
|
if !isWildcardPattern(name) {
|
||||||
|
host := *current
|
||||||
|
host.Name = name
|
||||||
|
hosts = append(hosts, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWildcardPattern returns true if the pattern contains wildcard characters.
|
||||||
|
func isWildcardPattern(pattern string) bool {
|
||||||
|
return strings.ContainsAny(pattern, "*?")
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveIdentityPath resolves a path from SSH config (handles ~ and relative paths).
|
||||||
|
func resolveIdentityPath(path string) string {
|
||||||
|
if path == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ~/ or $HOME/
|
||||||
|
if strings.HasPrefix(path, "~/") {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
path = filepath.Join(home, path[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle relative paths (relative to ~/.ssh/)
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
path = filepath.Join(home, ".ssh", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Clean(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHConfigPath returns the path to the user's SSH config file.
|
||||||
|
func SSHConfigPath() string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".ssh", "config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HostsFilePath returns the path to the custom hosts data file.
|
||||||
|
func HostsFilePath() string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".config", "vcom", "hosts.dat")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSSHDir returns the path to the .ssh directory.
|
||||||
|
func GetSSHDir() string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".ssh")
|
||||||
|
}
|
||||||
|
|
||||||
|
// KnownHostsPath returns the path to known_hosts.
|
||||||
|
func KnownHostsPath() string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".ssh", "known_hosts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigFileExists checks if the SSH config file exists.
|
||||||
|
func ConfigFileExists() bool {
|
||||||
|
path := SSHConfigPath()
|
||||||
|
if path == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateHost checks if a host entry has the minimum required fields.
|
||||||
|
func ValidateHost(host SSHHost) error {
|
||||||
|
if strings.TrimSpace(host.Name) == "" {
|
||||||
|
return fmt.Errorf("host name is required")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(host.HostName) == "" {
|
||||||
|
return fmt.Errorf("hostname/address is required")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(host.User) == "" {
|
||||||
|
return fmt.Errorf("username is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
311
src/vcom-0.2.5/internal/fs/remote/host.go
Normal file
311
src/vcom-0.2.5/internal/fs/remote/host.go
Normal file
|
|
@ -0,0 +1,311 @@
|
||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSHHost represents a single SSH host configuration.
|
||||||
|
type SSHHost struct {
|
||||||
|
// Name is the host alias (e.g. "myserver").
|
||||||
|
Name string `json:"name"`
|
||||||
|
// HostName is the actual hostname or IP address.
|
||||||
|
HostName string `json:"hostname"`
|
||||||
|
// Port is the SSH port (default 22).
|
||||||
|
Port string `json:"port,omitempty"`
|
||||||
|
// User is the SSH username.
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
// IdentityFile is the path to the private key file (for key-based auth).
|
||||||
|
IdentityFile string `json:"identity_file,omitempty"`
|
||||||
|
// Password is stored encrypted (for password-based auth, user-added hosts).
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
// FromSSHConfig indicates this host came from ~/.ssh/config.
|
||||||
|
FromSSHConfig bool `json:"from_ssh_config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisplayName returns the host display name.
|
||||||
|
func (h SSHHost) DisplayName() string {
|
||||||
|
addr := h.HostName
|
||||||
|
if h.Port != "" && h.Port != "22" {
|
||||||
|
addr = fmt.Sprintf("%s:%s", addr, h.Port)
|
||||||
|
}
|
||||||
|
if h.User != "" {
|
||||||
|
return fmt.Sprintf("%s (%s@%s)", h.Name, h.User, addr)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (%s)", h.Name, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the SSH address string (host:port).
|
||||||
|
func (h SSHHost) Addr() string {
|
||||||
|
if h.Port == "" || h.Port == "22" {
|
||||||
|
return h.HostName + ":22"
|
||||||
|
}
|
||||||
|
return h.HostName + ":" + h.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
// SameAs returns true if two hosts point to the same server.
|
||||||
|
func (h SSHHost) SameAs(other SSHHost) bool {
|
||||||
|
return h.HostName == other.HostName &&
|
||||||
|
(h.Port == other.Port || (h.Port == "" && other.Port == "22") || (h.Port == "22" && other.Port == ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HostStore manages SSH hosts from both ~/.ssh/config and user-added hosts.
|
||||||
|
type HostStore struct {
|
||||||
|
customHosts []SSHHost
|
||||||
|
configPath string
|
||||||
|
cipherKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHostStore creates a new HostStore.
|
||||||
|
func NewHostStore() (*HostStore, error) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("home dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
store := &HostStore{
|
||||||
|
configPath: filepath.Join(home, ".config", "vcom", "hosts.dat"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load or create encryption key
|
||||||
|
keyPath := filepath.Join(home, ".config", "vcom", ".hosts-key")
|
||||||
|
store.cipherKey, err = loadOrCreateKey(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("encryption key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load custom hosts
|
||||||
|
if err := store.load(); err != nil {
|
||||||
|
// Ignore load errors for missing file
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return store, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadOrCreateKey loads an existing AES key or creates a new one.
|
||||||
|
func loadOrCreateKey(path string) ([]byte, error) {
|
||||||
|
if data, err := os.ReadFile(path); err == nil {
|
||||||
|
key, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(data)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(key) == 32 {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new 32-byte key for AES-256
|
||||||
|
key := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(key); err != nil {
|
||||||
|
return nil, fmt.Errorf("generate key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||||
|
return nil, fmt.Errorf("mkdir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := base64.StdEncoding.EncodeToString(key)
|
||||||
|
if err := os.WriteFile(path, []byte(encoded), 0o600); err != nil {
|
||||||
|
return nil, fmt.Errorf("write key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type storedHosts struct {
|
||||||
|
Hosts []storedHost `json:"hosts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type storedHost struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
HostName string `json:"hostname"`
|
||||||
|
Port string `json:"port,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"` // encrypted
|
||||||
|
IdentityFile string `json:"identity_file,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HostStore) load() error {
|
||||||
|
data, err := os.ReadFile(s.configPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
decrypted, err := decrypt(data, s.cipherKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decrypt hosts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stored storedHosts
|
||||||
|
if err := json.Unmarshal(decrypted, &stored); err != nil {
|
||||||
|
return fmt.Errorf("parse hosts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.customHosts = make([]SSHHost, len(stored.Hosts))
|
||||||
|
for i, h := range stored.Hosts {
|
||||||
|
password := ""
|
||||||
|
if h.Password != "" {
|
||||||
|
pwd, err := decrypt([]byte(h.Password), s.cipherKey)
|
||||||
|
if err == nil {
|
||||||
|
password = string(pwd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.customHosts[i] = SSHHost{
|
||||||
|
Name: h.Name,
|
||||||
|
HostName: h.HostName,
|
||||||
|
Port: h.Port,
|
||||||
|
User: h.User,
|
||||||
|
Password: password,
|
||||||
|
IdentityFile: h.IdentityFile,
|
||||||
|
FromSSHConfig: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save persists custom hosts to disk (encrypted).
|
||||||
|
func (s *HostStore) Save() error {
|
||||||
|
stored := storedHosts{
|
||||||
|
Hosts: make([]storedHost, len(s.customHosts)),
|
||||||
|
}
|
||||||
|
for i, h := range s.customHosts {
|
||||||
|
password := ""
|
||||||
|
if h.Password != "" {
|
||||||
|
enc, err := encrypt([]byte(h.Password), s.cipherKey)
|
||||||
|
if err == nil {
|
||||||
|
password = string(enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stored.Hosts[i] = storedHost{
|
||||||
|
Name: h.Name,
|
||||||
|
HostName: h.HostName,
|
||||||
|
Port: h.Port,
|
||||||
|
User: h.User,
|
||||||
|
Password: password,
|
||||||
|
IdentityFile: h.IdentityFile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(stored)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal hosts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypted, err := encrypt(data, s.cipherKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encrypt hosts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Dir(s.configPath)
|
||||||
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||||
|
return fmt.Errorf("mkdir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(s.configPath, encrypted, 0o600)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHost adds a custom host and saves.
|
||||||
|
func (s *HostStore) AddHost(host SSHHost) error {
|
||||||
|
host.FromSSHConfig = false
|
||||||
|
s.customHosts = append(s.customHosts, host)
|
||||||
|
return s.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveHost removes a custom host by name.
|
||||||
|
func (s *HostStore) RemoveHost(name string) error {
|
||||||
|
for i, h := range s.customHosts {
|
||||||
|
if h.Name == name {
|
||||||
|
s.customHosts = append(s.customHosts[:i], s.customHosts[i+1:]...)
|
||||||
|
return s.Save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("host %q not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllHosts returns all hosts (from ssh config + custom).
|
||||||
|
func (s *HostStore) AllHosts() []SSHHost {
|
||||||
|
sshConfigHosts := ParseSSHConfig()
|
||||||
|
result := make([]SSHHost, 0, len(sshConfigHosts)+len(s.customHosts))
|
||||||
|
|
||||||
|
// Build a set of names from ssh config to avoid duplicates
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, h := range sshConfigHosts {
|
||||||
|
lower := strings.ToLower(h.Name)
|
||||||
|
seen[lower] = true
|
||||||
|
result = append(result, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range s.customHosts {
|
||||||
|
lower := strings.ToLower(h.Name)
|
||||||
|
if !seen[lower] {
|
||||||
|
result = append(result, h)
|
||||||
|
seen[lower] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByName looks up a host by its Name field. Returns nil if not found.
|
||||||
|
func (s *HostStore) FindByName(name string) *SSHHost {
|
||||||
|
all := s.AllHosts()
|
||||||
|
for i := range all {
|
||||||
|
if strings.EqualFold(all[i].Name, name) {
|
||||||
|
return &all[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encrypt(plaintext []byte, key []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gcm.Seal(nonce, nonce, plaintext, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt(ciphertext []byte, key []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonceSize := gcm.NonceSize()
|
||||||
|
if len(ciphertext) < nonceSize {
|
||||||
|
return nil, fmt.Errorf("ciphertext too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||||
|
return gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
}
|
||||||
275
src/vcom-0.2.5/internal/fs/scan.go
Normal file
275
src/vcom-0.2.5/internal/fs/scan.go
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListOptions struct {
|
||||||
|
ShowHidden bool
|
||||||
|
DirsFirst bool
|
||||||
|
SortBy string
|
||||||
|
SortReverse bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListDir(path string, options ListOptions) ([]Entry, error) {
|
||||||
|
resolvedPath := path
|
||||||
|
if resolvedPath == "" {
|
||||||
|
resolvedPath = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
dirEntries, err := os.ReadDir(resolvedPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read dir %s: %w", resolvedPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := make([]Entry, 0, len(dirEntries)+1)
|
||||||
|
if parent := filepath.Dir(resolvedPath); parent != resolvedPath {
|
||||||
|
entries = append(entries, Entry{
|
||||||
|
Name: "..",
|
||||||
|
Path: parent,
|
||||||
|
IsDir: true,
|
||||||
|
IsParent: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dirEntry := range dirEntries {
|
||||||
|
name := dirEntry.Name()
|
||||||
|
hidden := strings.HasPrefix(name, ".")
|
||||||
|
if hidden && !options.ShowHidden {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(resolvedPath, name)
|
||||||
|
info, err := dirEntry.Info()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := Entry{
|
||||||
|
Name: name,
|
||||||
|
Path: fullPath,
|
||||||
|
Extension: ext(name),
|
||||||
|
Mode: info.Mode(),
|
||||||
|
Size: info.Size(),
|
||||||
|
ModifiedAt: info.ModTime(),
|
||||||
|
IsDir: info.IsDir(),
|
||||||
|
IsHidden: hidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdAt, ok := statBirthTime(fullPath); ok {
|
||||||
|
entry.CreatedAt = createdAt
|
||||||
|
entry.CreatedKnown = true
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = append(entries, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(entries, func(i, j int) bool {
|
||||||
|
left, right := entries[i], entries[j]
|
||||||
|
|
||||||
|
if left.IsParent != right.IsParent {
|
||||||
|
return left.IsParent
|
||||||
|
}
|
||||||
|
if options.DirsFirst && left.IsDir != right.IsDir {
|
||||||
|
return left.IsDir
|
||||||
|
}
|
||||||
|
comparison := compareEntries(left, right, options.SortBy)
|
||||||
|
if options.SortReverse {
|
||||||
|
return comparison > 0
|
||||||
|
}
|
||||||
|
return comparison < 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareEntries(left Entry, right Entry, sortBy string) int {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(sortBy)) {
|
||||||
|
case "size":
|
||||||
|
if left.Size != right.Size {
|
||||||
|
return cmpInt64(left.Size, right.Size)
|
||||||
|
}
|
||||||
|
case "modified":
|
||||||
|
if !left.ModifiedAt.Equal(right.ModifiedAt) {
|
||||||
|
return cmpTimeDesc(left.ModifiedAt, right.ModifiedAt)
|
||||||
|
}
|
||||||
|
case "created":
|
||||||
|
if left.CreatedKnown != right.CreatedKnown {
|
||||||
|
if left.CreatedKnown {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if !left.CreatedAt.Equal(right.CreatedAt) {
|
||||||
|
return cmpTimeDesc(left.CreatedAt, right.CreatedAt)
|
||||||
|
}
|
||||||
|
case "extension":
|
||||||
|
if left.Extension != right.Extension {
|
||||||
|
return strings.Compare(left.Extension, right.Extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Compare(strings.ToLower(left.Name), strings.ToLower(right.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmpInt64(left int64, right int64) int {
|
||||||
|
switch {
|
||||||
|
case left < right:
|
||||||
|
return -1
|
||||||
|
case left > right:
|
||||||
|
return 1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmpTimeDesc(left time.Time, right time.Time) int {
|
||||||
|
switch {
|
||||||
|
case left.Equal(right):
|
||||||
|
return 0
|
||||||
|
case left.After(right):
|
||||||
|
return -1
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func statBirthTime(path string) (time.Time, bool) {
|
||||||
|
var stx unix.Statx_t
|
||||||
|
if err := unix.Statx(unix.AT_FDCWD, path, unix.AT_STATX_SYNC_AS_STAT, unix.STATX_BTIME, &stx); err == nil {
|
||||||
|
if stx.Mask&unix.STATX_BTIME != 0 {
|
||||||
|
return time.Unix(int64(stx.Btime.Sec), int64(stx.Btime.Nsec)), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
seconds := int64(stat.Ctim.Sec)
|
||||||
|
nanos := int64(stat.Ctim.Nsec)
|
||||||
|
if seconds == 0 && nanos == 0 {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
return time.Unix(seconds, nanos), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func DirectorySize(path string) (int64, error) {
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
err := filepath.WalkDir(path, func(current string, d fs.DirEntry, walkErr error) error {
|
||||||
|
if walkErr != nil {
|
||||||
|
return walkErr
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
info, err := d.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
total += info.Size()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindSelected(entries []Entry, key string) int {
|
||||||
|
for idx, entry := range entries {
|
||||||
|
if entry.MatchKey() == key {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func HumanSize(size int64) string {
|
||||||
|
if size < 0 {
|
||||||
|
return "?"
|
||||||
|
}
|
||||||
|
if size < 1024 {
|
||||||
|
return fmt.Sprintf("%d B", size)
|
||||||
|
}
|
||||||
|
units := []string{"KB", "MB", "GB", "TB"}
|
||||||
|
value := float64(size)
|
||||||
|
for _, unit := range units {
|
||||||
|
value /= 1024
|
||||||
|
if value < 1024 {
|
||||||
|
return fmt.Sprintf("%.1f %s", value, unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.1f PB", value/1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShortTime(t time.Time) string {
|
||||||
|
if t.IsZero() {
|
||||||
|
return "n/a"
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompactTime(t time.Time) string {
|
||||||
|
if t.IsZero() {
|
||||||
|
return "n/a"
|
||||||
|
}
|
||||||
|
return t.Format("01-02 15:04")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Permissions(mode fs.FileMode) string {
|
||||||
|
return mode.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsBinarySample(data []byte) bool {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var controls int
|
||||||
|
for _, b := range data {
|
||||||
|
if b == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if b < 9 || (b > 13 && b < 32) {
|
||||||
|
controls++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return controls > len(data)/10
|
||||||
|
}
|
||||||
|
|
||||||
|
func SafeBase(path string) string {
|
||||||
|
base := filepath.Base(path)
|
||||||
|
if base == "." || base == string(filepath.Separator) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func JoinPath(path string, name string) string {
|
||||||
|
return filepath.Join(path, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrOverwrite(path string) error {
|
||||||
|
return fmt.Errorf("target already exists: %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNotExist(err error) bool {
|
||||||
|
return errors.Is(err, os.ErrNotExist)
|
||||||
|
}
|
||||||
836
src/vcom-0.2.5/internal/theme/theme.go
Normal file
836
src/vcom-0.2.5/internal/theme/theme.go
Normal file
|
|
@ -0,0 +1,836 @@
|
||||||
|
package theme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Palette struct {
|
||||||
|
Name string
|
||||||
|
Background lipgloss.Color
|
||||||
|
Panel lipgloss.Color
|
||||||
|
PanelInactive lipgloss.Color
|
||||||
|
PanelElevated lipgloss.Color
|
||||||
|
StatusBar lipgloss.Color
|
||||||
|
Footer lipgloss.Color
|
||||||
|
Border lipgloss.Color
|
||||||
|
BorderActive lipgloss.Color
|
||||||
|
Text lipgloss.Color
|
||||||
|
Muted lipgloss.Color
|
||||||
|
Accent lipgloss.Color
|
||||||
|
Info lipgloss.Color
|
||||||
|
Success lipgloss.Color
|
||||||
|
Selection lipgloss.Color
|
||||||
|
Hover lipgloss.Color
|
||||||
|
Marked lipgloss.Color
|
||||||
|
Warning lipgloss.Color
|
||||||
|
Danger lipgloss.Color
|
||||||
|
ActivePath lipgloss.Color
|
||||||
|
ConfirmButton lipgloss.Color
|
||||||
|
CancelButton lipgloss.Color
|
||||||
|
ProgressFill lipgloss.Color
|
||||||
|
ProgressEmpty lipgloss.Color
|
||||||
|
HelpNav lipgloss.Color
|
||||||
|
HelpPanels lipgloss.Color
|
||||||
|
HelpDialogs lipgloss.Color
|
||||||
|
HelpMouse lipgloss.Color
|
||||||
|
Folder lipgloss.Color
|
||||||
|
TextFile lipgloss.Color
|
||||||
|
ConfigFile lipgloss.Color
|
||||||
|
ExecFile lipgloss.Color
|
||||||
|
ImageFile lipgloss.Color
|
||||||
|
BinaryFile lipgloss.Color
|
||||||
|
FooterKey lipgloss.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtInThemes = []string{
|
||||||
|
"catppuccin-mocha",
|
||||||
|
"catppuccin-macchiato",
|
||||||
|
"catppuccin-lavender",
|
||||||
|
"tokyo-night",
|
||||||
|
"gruvbox-dark",
|
||||||
|
"nord",
|
||||||
|
"one-dark",
|
||||||
|
"everforest",
|
||||||
|
"github-dark",
|
||||||
|
"ayu-dark",
|
||||||
|
"breeze",
|
||||||
|
"cyberpunk",
|
||||||
|
"dracula",
|
||||||
|
"eldritch",
|
||||||
|
"kanagawa",
|
||||||
|
"kanagawa-paper",
|
||||||
|
"rose-pine",
|
||||||
|
"solarized-dark",
|
||||||
|
"vesper",
|
||||||
|
}
|
||||||
|
|
||||||
|
func Names() []string {
|
||||||
|
values := make([]string, len(builtInThemes))
|
||||||
|
copy(values, builtInThemes)
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func Next(current string) string {
|
||||||
|
values := Names()
|
||||||
|
if len(values) == 0 {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
current = strings.ToLower(strings.TrimSpace(current))
|
||||||
|
for idx, value := range values {
|
||||||
|
if value == current {
|
||||||
|
return values[(idx+1)%len(values)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Resolve(name string) (Palette, error) {
|
||||||
|
switch strings.ToLower(name) {
|
||||||
|
case "catppuccin-mocha":
|
||||||
|
return Palette{
|
||||||
|
Name: "catppuccin-mocha",
|
||||||
|
Background: lipgloss.Color("#11111B"),
|
||||||
|
Panel: lipgloss.Color("#181825"),
|
||||||
|
PanelInactive: lipgloss.Color("#1E1E2E"),
|
||||||
|
PanelElevated: lipgloss.Color("#24273A"),
|
||||||
|
StatusBar: lipgloss.Color("#1E1E2E"),
|
||||||
|
Footer: lipgloss.Color("#11111B"),
|
||||||
|
Border: lipgloss.Color("#45475A"),
|
||||||
|
BorderActive: lipgloss.Color("#89B4FA"),
|
||||||
|
Text: lipgloss.Color("#CDD6F4"),
|
||||||
|
Muted: lipgloss.Color("#A6ADC8"),
|
||||||
|
Accent: lipgloss.Color("#F5C2E7"),
|
||||||
|
Info: lipgloss.Color("#89DCEB"),
|
||||||
|
Success: lipgloss.Color("#A6E3A1"),
|
||||||
|
Selection: lipgloss.Color("#313244"),
|
||||||
|
Hover: lipgloss.Color("#2A2B3C"),
|
||||||
|
Marked: lipgloss.Color("#F38BA8"),
|
||||||
|
Warning: lipgloss.Color("#F9E2AF"),
|
||||||
|
Danger: lipgloss.Color("#F38BA8"),
|
||||||
|
ActivePath: lipgloss.Color("#89DCEB"),
|
||||||
|
ConfirmButton: lipgloss.Color("#A6E3A1"),
|
||||||
|
CancelButton: lipgloss.Color("#F38BA8"),
|
||||||
|
ProgressFill: lipgloss.Color("#89B4FA"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#45475A"),
|
||||||
|
HelpNav: lipgloss.Color("#89B4FA"),
|
||||||
|
HelpPanels: lipgloss.Color("#F9E2AF"),
|
||||||
|
HelpDialogs: lipgloss.Color("#CBA6F7"),
|
||||||
|
HelpMouse: lipgloss.Color("#F38BA8"),
|
||||||
|
Folder: lipgloss.Color("#89B4FA"),
|
||||||
|
TextFile: lipgloss.Color("#A6E3A1"),
|
||||||
|
ConfigFile: lipgloss.Color("#F9E2AF"),
|
||||||
|
ExecFile: lipgloss.Color("#FAB387"),
|
||||||
|
ImageFile: lipgloss.Color("#94E2D5"),
|
||||||
|
BinaryFile: lipgloss.Color("#CBA6F7"),
|
||||||
|
FooterKey: lipgloss.Color("#89DCEB"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "catppuccin-lavender":
|
||||||
|
return Palette{
|
||||||
|
Name: "catppuccin-lavender",
|
||||||
|
Background: lipgloss.Color("#11111B"),
|
||||||
|
Panel: lipgloss.Color("#181825"),
|
||||||
|
PanelInactive: lipgloss.Color("#1E1E2E"),
|
||||||
|
PanelElevated: lipgloss.Color("#24273A"),
|
||||||
|
StatusBar: lipgloss.Color("#1E1E2E"),
|
||||||
|
Footer: lipgloss.Color("#11111B"),
|
||||||
|
Border: lipgloss.Color("#45475A"),
|
||||||
|
BorderActive: lipgloss.Color("#B4BEFE"),
|
||||||
|
Text: lipgloss.Color("#CDD6F4"),
|
||||||
|
Muted: lipgloss.Color("#A6ADC8"),
|
||||||
|
Accent: lipgloss.Color("#B4BEFE"),
|
||||||
|
Info: lipgloss.Color("#89DCEB"),
|
||||||
|
Success: lipgloss.Color("#A6E3A1"),
|
||||||
|
Selection: lipgloss.Color("#313244"),
|
||||||
|
Hover: lipgloss.Color("#2A2B3C"),
|
||||||
|
Marked: lipgloss.Color("#F38BA8"),
|
||||||
|
Warning: lipgloss.Color("#F9E2AF"),
|
||||||
|
Danger: lipgloss.Color("#F38BA8"),
|
||||||
|
ActivePath: lipgloss.Color("#B4BEFE"),
|
||||||
|
ConfirmButton: lipgloss.Color("#A6E3A1"),
|
||||||
|
CancelButton: lipgloss.Color("#F38BA8"),
|
||||||
|
ProgressFill: lipgloss.Color("#B4BEFE"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#45475A"),
|
||||||
|
HelpNav: lipgloss.Color("#B4BEFE"),
|
||||||
|
HelpPanels: lipgloss.Color("#F9E2AF"),
|
||||||
|
HelpDialogs: lipgloss.Color("#CBA6F7"),
|
||||||
|
HelpMouse: lipgloss.Color("#F38BA8"),
|
||||||
|
Folder: lipgloss.Color("#B4BEFE"),
|
||||||
|
TextFile: lipgloss.Color("#A6E3A1"),
|
||||||
|
ConfigFile: lipgloss.Color("#F9E2AF"),
|
||||||
|
ExecFile: lipgloss.Color("#FAB387"),
|
||||||
|
ImageFile: lipgloss.Color("#89DCEB"),
|
||||||
|
BinaryFile: lipgloss.Color("#CBA6F7"),
|
||||||
|
FooterKey: lipgloss.Color("#B4BEFE"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "tokyo-night":
|
||||||
|
return Palette{
|
||||||
|
Name: "tokyo-night",
|
||||||
|
Background: lipgloss.Color("#16161E"),
|
||||||
|
Panel: lipgloss.Color("#1A1B26"),
|
||||||
|
PanelInactive: lipgloss.Color("#24283B"),
|
||||||
|
PanelElevated: lipgloss.Color("#2A2F44"),
|
||||||
|
StatusBar: lipgloss.Color("#24283B"),
|
||||||
|
Footer: lipgloss.Color("#16161E"),
|
||||||
|
Border: lipgloss.Color("#3B4261"),
|
||||||
|
BorderActive: lipgloss.Color("#7AA2F7"),
|
||||||
|
Text: lipgloss.Color("#C0CAF5"),
|
||||||
|
Muted: lipgloss.Color("#9AA5CE"),
|
||||||
|
Accent: lipgloss.Color("#BB9AF7"),
|
||||||
|
Info: lipgloss.Color("#73DACA"),
|
||||||
|
Success: lipgloss.Color("#9ECE6A"),
|
||||||
|
Selection: lipgloss.Color("#292E42"),
|
||||||
|
Hover: lipgloss.Color("#252A3D"),
|
||||||
|
Marked: lipgloss.Color("#F7768E"),
|
||||||
|
Warning: lipgloss.Color("#E0AF68"),
|
||||||
|
Danger: lipgloss.Color("#F7768E"),
|
||||||
|
ActivePath: lipgloss.Color("#73DACA"),
|
||||||
|
ConfirmButton: lipgloss.Color("#9ECE6A"),
|
||||||
|
CancelButton: lipgloss.Color("#F7768E"),
|
||||||
|
ProgressFill: lipgloss.Color("#7AA2F7"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#3B4261"),
|
||||||
|
HelpNav: lipgloss.Color("#7AA2F7"),
|
||||||
|
HelpPanels: lipgloss.Color("#E0AF68"),
|
||||||
|
HelpDialogs: lipgloss.Color("#BB9AF7"),
|
||||||
|
HelpMouse: lipgloss.Color("#F7768E"),
|
||||||
|
Folder: lipgloss.Color("#7AA2F7"),
|
||||||
|
TextFile: lipgloss.Color("#9ECE6A"),
|
||||||
|
ConfigFile: lipgloss.Color("#E0AF68"),
|
||||||
|
ExecFile: lipgloss.Color("#FF9E64"),
|
||||||
|
ImageFile: lipgloss.Color("#73DACA"),
|
||||||
|
BinaryFile: lipgloss.Color("#BB9AF7"),
|
||||||
|
FooterKey: lipgloss.Color("#73DACA"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "gruvbox-dark":
|
||||||
|
return Palette{
|
||||||
|
Name: name,
|
||||||
|
Background: lipgloss.Color("#1D2021"),
|
||||||
|
Panel: lipgloss.Color("#282828"),
|
||||||
|
PanelInactive: lipgloss.Color("#32302F"),
|
||||||
|
PanelElevated: lipgloss.Color("#3C3836"),
|
||||||
|
StatusBar: lipgloss.Color("#32302F"),
|
||||||
|
Footer: lipgloss.Color("#1D2021"),
|
||||||
|
Border: lipgloss.Color("#504945"),
|
||||||
|
BorderActive: lipgloss.Color("#FABD2F"),
|
||||||
|
Text: lipgloss.Color("#EBDBB2"),
|
||||||
|
Muted: lipgloss.Color("#BDAE93"),
|
||||||
|
Accent: lipgloss.Color("#83A598"),
|
||||||
|
Info: lipgloss.Color("#8EC07C"),
|
||||||
|
Success: lipgloss.Color("#B8BB26"),
|
||||||
|
Selection: lipgloss.Color("#3C3836"),
|
||||||
|
Hover: lipgloss.Color("#45403D"),
|
||||||
|
Marked: lipgloss.Color("#FB4934"),
|
||||||
|
Warning: lipgloss.Color("#FE8019"),
|
||||||
|
Danger: lipgloss.Color("#FB4934"),
|
||||||
|
ActivePath: lipgloss.Color("#8EC07C"),
|
||||||
|
ConfirmButton: lipgloss.Color("#B8BB26"),
|
||||||
|
CancelButton: lipgloss.Color("#FB4934"),
|
||||||
|
ProgressFill: lipgloss.Color("#FABD2F"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#504945"),
|
||||||
|
HelpNav: lipgloss.Color("#83A598"),
|
||||||
|
HelpPanels: lipgloss.Color("#FABD2F"),
|
||||||
|
HelpDialogs: lipgloss.Color("#D3869B"),
|
||||||
|
HelpMouse: lipgloss.Color("#FB4934"),
|
||||||
|
Folder: lipgloss.Color("#83A598"),
|
||||||
|
TextFile: lipgloss.Color("#B8BB26"),
|
||||||
|
ConfigFile: lipgloss.Color("#FABD2F"),
|
||||||
|
ExecFile: lipgloss.Color("#FE8019"),
|
||||||
|
ImageFile: lipgloss.Color("#8EC07C"),
|
||||||
|
BinaryFile: lipgloss.Color("#D3869B"),
|
||||||
|
FooterKey: lipgloss.Color("#8EC07C"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "nord":
|
||||||
|
return Palette{
|
||||||
|
Name: name,
|
||||||
|
Background: lipgloss.Color("#2E3440"),
|
||||||
|
Panel: lipgloss.Color("#3B4252"),
|
||||||
|
PanelInactive: lipgloss.Color("#434C5E"),
|
||||||
|
PanelElevated: lipgloss.Color("#4C566A"),
|
||||||
|
StatusBar: lipgloss.Color("#434C5E"),
|
||||||
|
Footer: lipgloss.Color("#2E3440"),
|
||||||
|
Border: lipgloss.Color("#4C566A"),
|
||||||
|
BorderActive: lipgloss.Color("#88C0D0"),
|
||||||
|
Text: lipgloss.Color("#ECEFF4"),
|
||||||
|
Muted: lipgloss.Color("#D8DEE9"),
|
||||||
|
Accent: lipgloss.Color("#81A1C1"),
|
||||||
|
Info: lipgloss.Color("#8FBCBB"),
|
||||||
|
Success: lipgloss.Color("#A3BE8C"),
|
||||||
|
Selection: lipgloss.Color("#434C5E"),
|
||||||
|
Hover: lipgloss.Color("#505A70"),
|
||||||
|
Marked: lipgloss.Color("#BF616A"),
|
||||||
|
Warning: lipgloss.Color("#EBCB8B"),
|
||||||
|
Danger: lipgloss.Color("#BF616A"),
|
||||||
|
ActivePath: lipgloss.Color("#8FBCBB"),
|
||||||
|
ConfirmButton: lipgloss.Color("#A3BE8C"),
|
||||||
|
CancelButton: lipgloss.Color("#BF616A"),
|
||||||
|
ProgressFill: lipgloss.Color("#88C0D0"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#4C566A"),
|
||||||
|
HelpNav: lipgloss.Color("#81A1C1"),
|
||||||
|
HelpPanels: lipgloss.Color("#EBCB8B"),
|
||||||
|
HelpDialogs: lipgloss.Color("#B48EAD"),
|
||||||
|
HelpMouse: lipgloss.Color("#BF616A"),
|
||||||
|
Folder: lipgloss.Color("#81A1C1"),
|
||||||
|
TextFile: lipgloss.Color("#A3BE8C"),
|
||||||
|
ConfigFile: lipgloss.Color("#EBCB8B"),
|
||||||
|
ExecFile: lipgloss.Color("#D08770"),
|
||||||
|
ImageFile: lipgloss.Color("#8FBCBB"),
|
||||||
|
BinaryFile: lipgloss.Color("#B48EAD"),
|
||||||
|
FooterKey: lipgloss.Color("#8FBCBB"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "one-dark":
|
||||||
|
return Palette{
|
||||||
|
Name: "one-dark",
|
||||||
|
Background: lipgloss.Color("#282C34"),
|
||||||
|
Panel: lipgloss.Color("#21252B"),
|
||||||
|
PanelInactive: lipgloss.Color("#1B1D23"),
|
||||||
|
PanelElevated: lipgloss.Color("#2C313A"),
|
||||||
|
StatusBar: lipgloss.Color("#21252B"),
|
||||||
|
Footer: lipgloss.Color("#282C34"),
|
||||||
|
Border: lipgloss.Color("#3B4048"),
|
||||||
|
BorderActive: lipgloss.Color("#61AFEF"),
|
||||||
|
Text: lipgloss.Color("#ABB2BF"),
|
||||||
|
Muted: lipgloss.Color("#5C6370"),
|
||||||
|
Accent: lipgloss.Color("#61AFEF"),
|
||||||
|
Info: lipgloss.Color("#56B6C2"),
|
||||||
|
Success: lipgloss.Color("#98C379"),
|
||||||
|
Selection: lipgloss.Color("#3E4451"),
|
||||||
|
Hover: lipgloss.Color("#333841"),
|
||||||
|
Marked: lipgloss.Color("#E06C75"),
|
||||||
|
Warning: lipgloss.Color("#E5C07B"),
|
||||||
|
Danger: lipgloss.Color("#E06C75"),
|
||||||
|
ActivePath: lipgloss.Color("#56B6C2"),
|
||||||
|
ConfirmButton: lipgloss.Color("#98C379"),
|
||||||
|
CancelButton: lipgloss.Color("#E06C75"),
|
||||||
|
ProgressFill: lipgloss.Color("#61AFEF"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#3B4048"),
|
||||||
|
HelpNav: lipgloss.Color("#61AFEF"),
|
||||||
|
HelpPanels: lipgloss.Color("#E5C07B"),
|
||||||
|
HelpDialogs: lipgloss.Color("#C678DD"),
|
||||||
|
HelpMouse: lipgloss.Color("#E06C75"),
|
||||||
|
Folder: lipgloss.Color("#61AFEF"),
|
||||||
|
TextFile: lipgloss.Color("#98C379"),
|
||||||
|
ConfigFile: lipgloss.Color("#E5C07B"),
|
||||||
|
ExecFile: lipgloss.Color("#D19A66"),
|
||||||
|
ImageFile: lipgloss.Color("#56B6C2"),
|
||||||
|
BinaryFile: lipgloss.Color("#C678DD"),
|
||||||
|
FooterKey: lipgloss.Color("#56B6C2"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "everforest":
|
||||||
|
return Palette{
|
||||||
|
Name: "everforest",
|
||||||
|
Background: lipgloss.Color("#2D353B"),
|
||||||
|
Panel: lipgloss.Color("#272E33"),
|
||||||
|
PanelInactive: lipgloss.Color("#232A2E"),
|
||||||
|
PanelElevated: lipgloss.Color("#333C43"),
|
||||||
|
StatusBar: lipgloss.Color("#232A2E"),
|
||||||
|
Footer: lipgloss.Color("#2D353B"),
|
||||||
|
Border: lipgloss.Color("#475258"),
|
||||||
|
BorderActive: lipgloss.Color("#A7C080"),
|
||||||
|
Text: lipgloss.Color("#D3C6AA"),
|
||||||
|
Muted: lipgloss.Color("#859289"),
|
||||||
|
Accent: lipgloss.Color("#A7C080"),
|
||||||
|
Info: lipgloss.Color("#83C092"),
|
||||||
|
Success: lipgloss.Color("#A7C080"),
|
||||||
|
Selection: lipgloss.Color("#3A454A"),
|
||||||
|
Hover: lipgloss.Color("#364147"),
|
||||||
|
Marked: lipgloss.Color("#E67E80"),
|
||||||
|
Warning: lipgloss.Color("#DBBC7F"),
|
||||||
|
Danger: lipgloss.Color("#E67E80"),
|
||||||
|
ActivePath: lipgloss.Color("#83C092"),
|
||||||
|
ConfirmButton: lipgloss.Color("#A7C080"),
|
||||||
|
CancelButton: lipgloss.Color("#E67E80"),
|
||||||
|
ProgressFill: lipgloss.Color("#A7C080"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#475258"),
|
||||||
|
HelpNav: lipgloss.Color("#A7C080"),
|
||||||
|
HelpPanels: lipgloss.Color("#DBBC7F"),
|
||||||
|
HelpDialogs: lipgloss.Color("#D699B6"),
|
||||||
|
HelpMouse: lipgloss.Color("#E67E80"),
|
||||||
|
Folder: lipgloss.Color("#A7C080"),
|
||||||
|
TextFile: lipgloss.Color("#D3C6AA"),
|
||||||
|
ConfigFile: lipgloss.Color("#DBBC7F"),
|
||||||
|
ExecFile: lipgloss.Color("#E69875"),
|
||||||
|
ImageFile: lipgloss.Color("#83C092"),
|
||||||
|
BinaryFile: lipgloss.Color("#D699B6"),
|
||||||
|
FooterKey: lipgloss.Color("#83C092"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "github-dark":
|
||||||
|
return Palette{
|
||||||
|
Name: "github-dark",
|
||||||
|
Background: lipgloss.Color("#0D1117"),
|
||||||
|
Panel: lipgloss.Color("#161B22"),
|
||||||
|
PanelInactive: lipgloss.Color("#1C2128"),
|
||||||
|
PanelElevated: lipgloss.Color("#21262D"),
|
||||||
|
StatusBar: lipgloss.Color("#1C2128"),
|
||||||
|
Footer: lipgloss.Color("#0D1117"),
|
||||||
|
Border: lipgloss.Color("#30363D"),
|
||||||
|
BorderActive: lipgloss.Color("#58A6FF"),
|
||||||
|
Text: lipgloss.Color("#E6EDF3"),
|
||||||
|
Muted: lipgloss.Color("#8B949E"),
|
||||||
|
Accent: lipgloss.Color("#58A6FF"),
|
||||||
|
Info: lipgloss.Color("#39D353"),
|
||||||
|
Success: lipgloss.Color("#3FB950"),
|
||||||
|
Selection: lipgloss.Color("#21262D"),
|
||||||
|
Hover: lipgloss.Color("#262C36"),
|
||||||
|
Marked: lipgloss.Color("#F85149"),
|
||||||
|
Warning: lipgloss.Color("#D29922"),
|
||||||
|
Danger: lipgloss.Color("#F85149"),
|
||||||
|
ActivePath: lipgloss.Color("#39D353"),
|
||||||
|
ConfirmButton: lipgloss.Color("#3FB950"),
|
||||||
|
CancelButton: lipgloss.Color("#F85149"),
|
||||||
|
ProgressFill: lipgloss.Color("#58A6FF"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#30363D"),
|
||||||
|
HelpNav: lipgloss.Color("#58A6FF"),
|
||||||
|
HelpPanels: lipgloss.Color("#D29922"),
|
||||||
|
HelpDialogs: lipgloss.Color("#BC8CFF"),
|
||||||
|
HelpMouse: lipgloss.Color("#F85149"),
|
||||||
|
Folder: lipgloss.Color("#58A6FF"),
|
||||||
|
TextFile: lipgloss.Color("#7EE787"),
|
||||||
|
ConfigFile: lipgloss.Color("#D29922"),
|
||||||
|
ExecFile: lipgloss.Color("#F0883E"),
|
||||||
|
ImageFile: lipgloss.Color("#39D353"),
|
||||||
|
BinaryFile: lipgloss.Color("#BC8CFF"),
|
||||||
|
FooterKey: lipgloss.Color("#39D353"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "catppuccin-macchiato":
|
||||||
|
return Palette{
|
||||||
|
Name: "catppuccin-macchiato",
|
||||||
|
Background: lipgloss.Color("#181926"),
|
||||||
|
Panel: lipgloss.Color("#1E2030"),
|
||||||
|
PanelInactive: lipgloss.Color("#24273A"),
|
||||||
|
PanelElevated: lipgloss.Color("#2A2E3F"),
|
||||||
|
StatusBar: lipgloss.Color("#24273A"),
|
||||||
|
Footer: lipgloss.Color("#181926"),
|
||||||
|
Border: lipgloss.Color("#363A4F"),
|
||||||
|
BorderActive: lipgloss.Color("#C6A0F6"),
|
||||||
|
Text: lipgloss.Color("#CAD3F5"),
|
||||||
|
Muted: lipgloss.Color("#A5ADCB"),
|
||||||
|
Accent: lipgloss.Color("#C6A0F6"),
|
||||||
|
Info: lipgloss.Color("#91D7E3"),
|
||||||
|
Success: lipgloss.Color("#A6DA95"),
|
||||||
|
Selection: lipgloss.Color("#363A4F"),
|
||||||
|
Hover: lipgloss.Color("#2E3248"),
|
||||||
|
Marked: lipgloss.Color("#ED8796"),
|
||||||
|
Warning: lipgloss.Color("#F5A97F"),
|
||||||
|
Danger: lipgloss.Color("#ED8796"),
|
||||||
|
ActivePath: lipgloss.Color("#91D7E3"),
|
||||||
|
ConfirmButton: lipgloss.Color("#A6DA95"),
|
||||||
|
CancelButton: lipgloss.Color("#ED8796"),
|
||||||
|
ProgressFill: lipgloss.Color("#C6A0F6"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#363A4F"),
|
||||||
|
HelpNav: lipgloss.Color("#C6A0F6"),
|
||||||
|
HelpPanels: lipgloss.Color("#F5A97F"),
|
||||||
|
HelpDialogs: lipgloss.Color("#C6A0F6"),
|
||||||
|
HelpMouse: lipgloss.Color("#ED8796"),
|
||||||
|
Folder: lipgloss.Color("#C6A0F6"),
|
||||||
|
TextFile: lipgloss.Color("#A6DA95"),
|
||||||
|
ConfigFile: lipgloss.Color("#F5A97F"),
|
||||||
|
ExecFile: lipgloss.Color("#EE99A0"),
|
||||||
|
ImageFile: lipgloss.Color("#91D7E3"),
|
||||||
|
BinaryFile: lipgloss.Color("#C6A0F6"),
|
||||||
|
FooterKey: lipgloss.Color("#91D7E3"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "ayu-dark":
|
||||||
|
return Palette{
|
||||||
|
Name: "ayu-dark",
|
||||||
|
Background: lipgloss.Color("#0A0E14"),
|
||||||
|
Panel: lipgloss.Color("#0D1017"),
|
||||||
|
PanelInactive: lipgloss.Color("#11151D"),
|
||||||
|
PanelElevated: lipgloss.Color("#151A23"),
|
||||||
|
StatusBar: lipgloss.Color("#11151D"),
|
||||||
|
Footer: lipgloss.Color("#0A0E14"),
|
||||||
|
Border: lipgloss.Color("#1F2430"),
|
||||||
|
BorderActive: lipgloss.Color("#FFCC66"),
|
||||||
|
Text: lipgloss.Color("#B3B1AD"),
|
||||||
|
Muted: lipgloss.Color("#565B66"),
|
||||||
|
Accent: lipgloss.Color("#FF8F40"),
|
||||||
|
Info: lipgloss.Color("#95E6CB"),
|
||||||
|
Success: lipgloss.Color("#7FD962"),
|
||||||
|
Selection: lipgloss.Color("#1F2430"),
|
||||||
|
Hover: lipgloss.Color("#191E27"),
|
||||||
|
Marked: lipgloss.Color("#F26D78"),
|
||||||
|
Warning: lipgloss.Color("#FFCC66"),
|
||||||
|
Danger: lipgloss.Color("#F26D78"),
|
||||||
|
ActivePath: lipgloss.Color("#95E6CB"),
|
||||||
|
ConfirmButton: lipgloss.Color("#7FD962"),
|
||||||
|
CancelButton: lipgloss.Color("#F26D78"),
|
||||||
|
ProgressFill: lipgloss.Color("#FFCC66"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#1F2430"),
|
||||||
|
HelpNav: lipgloss.Color("#FF8F40"),
|
||||||
|
HelpPanels: lipgloss.Color("#FFCC66"),
|
||||||
|
HelpDialogs: lipgloss.Color("#D4A0FF"),
|
||||||
|
HelpMouse: lipgloss.Color("#F26D78"),
|
||||||
|
Folder: lipgloss.Color("#FF8F40"),
|
||||||
|
TextFile: lipgloss.Color("#B3B1AD"),
|
||||||
|
ConfigFile: lipgloss.Color("#FFCC66"),
|
||||||
|
ExecFile: lipgloss.Color("#F29668"),
|
||||||
|
ImageFile: lipgloss.Color("#95E6CB"),
|
||||||
|
BinaryFile: lipgloss.Color("#D4A0FF"),
|
||||||
|
FooterKey: lipgloss.Color("#95E6CB"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "breeze":
|
||||||
|
return Palette{
|
||||||
|
Name: "breeze",
|
||||||
|
Background: lipgloss.Color("#232629"),
|
||||||
|
Panel: lipgloss.Color("#2A2D30"),
|
||||||
|
PanelInactive: lipgloss.Color("#313437"),
|
||||||
|
PanelElevated: lipgloss.Color("#383B3E"),
|
||||||
|
StatusBar: lipgloss.Color("#313437"),
|
||||||
|
Footer: lipgloss.Color("#232629"),
|
||||||
|
Border: lipgloss.Color("#494D51"),
|
||||||
|
BorderActive: lipgloss.Color("#3DAEE9"),
|
||||||
|
Text: lipgloss.Color("#EFF0F1"),
|
||||||
|
Muted: lipgloss.Color("#B0B5BA"),
|
||||||
|
Accent: lipgloss.Color("#3DAEE9"),
|
||||||
|
Info: lipgloss.Color("#27E6A6"),
|
||||||
|
Success: lipgloss.Color("#27AE60"),
|
||||||
|
Selection: lipgloss.Color("#313437"),
|
||||||
|
Hover: lipgloss.Color("#35383B"),
|
||||||
|
Marked: lipgloss.Color("#ED1515"),
|
||||||
|
Warning: lipgloss.Color("#F67400"),
|
||||||
|
Danger: lipgloss.Color("#ED1515"),
|
||||||
|
ActivePath: lipgloss.Color("#27E6A6"),
|
||||||
|
ConfirmButton: lipgloss.Color("#27AE60"),
|
||||||
|
CancelButton: lipgloss.Color("#ED1515"),
|
||||||
|
ProgressFill: lipgloss.Color("#3DAEE9"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#494D51"),
|
||||||
|
HelpNav: lipgloss.Color("#3DAEE9"),
|
||||||
|
HelpPanels: lipgloss.Color("#F67400"),
|
||||||
|
HelpDialogs: lipgloss.Color("#9B59B6"),
|
||||||
|
HelpMouse: lipgloss.Color("#ED1515"),
|
||||||
|
Folder: lipgloss.Color("#3DAEE9"),
|
||||||
|
TextFile: lipgloss.Color("#27AE60"),
|
||||||
|
ConfigFile: lipgloss.Color("#F67400"),
|
||||||
|
ExecFile: lipgloss.Color("#E67E22"),
|
||||||
|
ImageFile: lipgloss.Color("#27E6A6"),
|
||||||
|
BinaryFile: lipgloss.Color("#9B59B6"),
|
||||||
|
FooterKey: lipgloss.Color("#27E6A6"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "cyberpunk":
|
||||||
|
return Palette{
|
||||||
|
Name: "cyberpunk",
|
||||||
|
Background: lipgloss.Color("#000B1A"),
|
||||||
|
Panel: lipgloss.Color("#0A1628"),
|
||||||
|
PanelInactive: lipgloss.Color("#0F1D30"),
|
||||||
|
PanelElevated: lipgloss.Color("#142338"),
|
||||||
|
StatusBar: lipgloss.Color("#0F1D30"),
|
||||||
|
Footer: lipgloss.Color("#000B1A"),
|
||||||
|
Border: lipgloss.Color("#1E3A5F"),
|
||||||
|
BorderActive: lipgloss.Color("#00FFF0"),
|
||||||
|
Text: lipgloss.Color("#E0E0E0"),
|
||||||
|
Muted: lipgloss.Color("#808080"),
|
||||||
|
Accent: lipgloss.Color("#FF00FF"),
|
||||||
|
Info: lipgloss.Color("#00FFF0"),
|
||||||
|
Success: lipgloss.Color("#00FF41"),
|
||||||
|
Selection: lipgloss.Color("#142338"),
|
||||||
|
Hover: lipgloss.Color("#192C42"),
|
||||||
|
Marked: lipgloss.Color("#FF0055"),
|
||||||
|
Warning: lipgloss.Color("#FFB000"),
|
||||||
|
Danger: lipgloss.Color("#FF0055"),
|
||||||
|
ActivePath: lipgloss.Color("#00FFF0"),
|
||||||
|
ConfirmButton: lipgloss.Color("#00FF41"),
|
||||||
|
CancelButton: lipgloss.Color("#FF0055"),
|
||||||
|
ProgressFill: lipgloss.Color("#FF00FF"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#1E3A5F"),
|
||||||
|
HelpNav: lipgloss.Color("#FF00FF"),
|
||||||
|
HelpPanels: lipgloss.Color("#FFB000"),
|
||||||
|
HelpDialogs: lipgloss.Color("#FF00FF"),
|
||||||
|
HelpMouse: lipgloss.Color("#FF0055"),
|
||||||
|
Folder: lipgloss.Color("#00FFF0"),
|
||||||
|
TextFile: lipgloss.Color("#00FF41"),
|
||||||
|
ConfigFile: lipgloss.Color("#FFB000"),
|
||||||
|
ExecFile: lipgloss.Color("#FF6600"),
|
||||||
|
ImageFile: lipgloss.Color("#00FFF0"),
|
||||||
|
BinaryFile: lipgloss.Color("#FF00FF"),
|
||||||
|
FooterKey: lipgloss.Color("#00FFF0"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "dracula":
|
||||||
|
return Palette{
|
||||||
|
Name: "dracula",
|
||||||
|
Background: lipgloss.Color("#21222C"),
|
||||||
|
Panel: lipgloss.Color("#282A36"),
|
||||||
|
PanelInactive: lipgloss.Color("#2F3242"),
|
||||||
|
PanelElevated: lipgloss.Color("#363850"),
|
||||||
|
StatusBar: lipgloss.Color("#2F3242"),
|
||||||
|
Footer: lipgloss.Color("#21222C"),
|
||||||
|
Border: lipgloss.Color("#44475A"),
|
||||||
|
BorderActive: lipgloss.Color("#BD93F9"),
|
||||||
|
Text: lipgloss.Color("#F8F8F2"),
|
||||||
|
Muted: lipgloss.Color("#6272A4"),
|
||||||
|
Accent: lipgloss.Color("#FF79C6"),
|
||||||
|
Info: lipgloss.Color("#8BE9FD"),
|
||||||
|
Success: lipgloss.Color("#50FA7B"),
|
||||||
|
Selection: lipgloss.Color("#44475A"),
|
||||||
|
Hover: lipgloss.Color("#3A3D52"),
|
||||||
|
Marked: lipgloss.Color("#FF5555"),
|
||||||
|
Warning: lipgloss.Color("#F1FA8C"),
|
||||||
|
Danger: lipgloss.Color("#FF5555"),
|
||||||
|
ActivePath: lipgloss.Color("#8BE9FD"),
|
||||||
|
ConfirmButton: lipgloss.Color("#50FA7B"),
|
||||||
|
CancelButton: lipgloss.Color("#FF5555"),
|
||||||
|
ProgressFill: lipgloss.Color("#FF79C6"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#44475A"),
|
||||||
|
HelpNav: lipgloss.Color("#BD93F9"),
|
||||||
|
HelpPanels: lipgloss.Color("#F1FA8C"),
|
||||||
|
HelpDialogs: lipgloss.Color("#FF79C6"),
|
||||||
|
HelpMouse: lipgloss.Color("#FF5555"),
|
||||||
|
Folder: lipgloss.Color("#BD93F9"),
|
||||||
|
TextFile: lipgloss.Color("#50FA7B"),
|
||||||
|
ConfigFile: lipgloss.Color("#F1FA8C"),
|
||||||
|
ExecFile: lipgloss.Color("#FFB86C"),
|
||||||
|
ImageFile: lipgloss.Color("#8BE9FD"),
|
||||||
|
BinaryFile: lipgloss.Color("#FF79C6"),
|
||||||
|
FooterKey: lipgloss.Color("#8BE9FD"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "eldritch":
|
||||||
|
return Palette{
|
||||||
|
Name: "eldritch",
|
||||||
|
Background: lipgloss.Color("#0B0D15"),
|
||||||
|
Panel: lipgloss.Color("#10121A"),
|
||||||
|
PanelInactive: lipgloss.Color("#161822"),
|
||||||
|
PanelElevated: lipgloss.Color("#1C1F2B"),
|
||||||
|
StatusBar: lipgloss.Color("#161822"),
|
||||||
|
Footer: lipgloss.Color("#0B0D15"),
|
||||||
|
Border: lipgloss.Color("#262A3B"),
|
||||||
|
BorderActive: lipgloss.Color("#67B0E8"),
|
||||||
|
Text: lipgloss.Color("#D3D7E0"),
|
||||||
|
Muted: lipgloss.Color("#8B8FA6"),
|
||||||
|
Accent: lipgloss.Color("#C278E8"),
|
||||||
|
Info: lipgloss.Color("#67B0E8"),
|
||||||
|
Success: lipgloss.Color("#74C287"),
|
||||||
|
Selection: lipgloss.Color("#1C1F2B"),
|
||||||
|
Hover: lipgloss.Color("#222638"),
|
||||||
|
Marked: lipgloss.Color("#E06868"),
|
||||||
|
Warning: lipgloss.Color("#E0A868"),
|
||||||
|
Danger: lipgloss.Color("#E06868"),
|
||||||
|
ActivePath: lipgloss.Color("#67B0E8"),
|
||||||
|
ConfirmButton: lipgloss.Color("#74C287"),
|
||||||
|
CancelButton: lipgloss.Color("#E06868"),
|
||||||
|
ProgressFill: lipgloss.Color("#C278E8"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#262A3B"),
|
||||||
|
HelpNav: lipgloss.Color("#C278E8"),
|
||||||
|
HelpPanels: lipgloss.Color("#E0A868"),
|
||||||
|
HelpDialogs: lipgloss.Color("#C278E8"),
|
||||||
|
HelpMouse: lipgloss.Color("#E06868"),
|
||||||
|
Folder: lipgloss.Color("#67B0E8"),
|
||||||
|
TextFile: lipgloss.Color("#74C287"),
|
||||||
|
ConfigFile: lipgloss.Color("#E0A868"),
|
||||||
|
ExecFile: lipgloss.Color("#E08868"),
|
||||||
|
ImageFile: lipgloss.Color("#67B0E8"),
|
||||||
|
BinaryFile: lipgloss.Color("#C278E8"),
|
||||||
|
FooterKey: lipgloss.Color("#67B0E8"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "kanagawa":
|
||||||
|
return Palette{
|
||||||
|
Name: "kanagawa",
|
||||||
|
Background: lipgloss.Color("#1F1F28"),
|
||||||
|
Panel: lipgloss.Color("#252535"),
|
||||||
|
PanelInactive: lipgloss.Color("#2A2A3C"),
|
||||||
|
PanelElevated: lipgloss.Color("#363646"),
|
||||||
|
StatusBar: lipgloss.Color("#2A2A3C"),
|
||||||
|
Footer: lipgloss.Color("#1F1F28"),
|
||||||
|
Border: lipgloss.Color("#54546D"),
|
||||||
|
BorderActive: lipgloss.Color("#7FB4CA"),
|
||||||
|
Text: lipgloss.Color("#DCD7BA"),
|
||||||
|
Muted: lipgloss.Color("#938AA9"),
|
||||||
|
Accent: lipgloss.Color("#DCA561"),
|
||||||
|
Info: lipgloss.Color("#7FB4CA"),
|
||||||
|
Success: lipgloss.Color("#76946A"),
|
||||||
|
Selection: lipgloss.Color("#363646"),
|
||||||
|
Hover: lipgloss.Color("#30304A"),
|
||||||
|
Marked: lipgloss.Color("#C34043"),
|
||||||
|
Warning: lipgloss.Color("#DCA561"),
|
||||||
|
Danger: lipgloss.Color("#C34043"),
|
||||||
|
ActivePath: lipgloss.Color("#7FB4CA"),
|
||||||
|
ConfirmButton: lipgloss.Color("#76946A"),
|
||||||
|
CancelButton: lipgloss.Color("#C34043"),
|
||||||
|
ProgressFill: lipgloss.Color("#DCA561"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#54546D"),
|
||||||
|
HelpNav: lipgloss.Color("#DCA561"),
|
||||||
|
HelpPanels: lipgloss.Color("#DCA561"),
|
||||||
|
HelpDialogs: lipgloss.Color("#957FB8"),
|
||||||
|
HelpMouse: lipgloss.Color("#C34043"),
|
||||||
|
Folder: lipgloss.Color("#7FB4CA"),
|
||||||
|
TextFile: lipgloss.Color("#76946A"),
|
||||||
|
ConfigFile: lipgloss.Color("#DCA561"),
|
||||||
|
ExecFile: lipgloss.Color("#E6C384"),
|
||||||
|
ImageFile: lipgloss.Color("#7FB4CA"),
|
||||||
|
BinaryFile: lipgloss.Color("#957FB8"),
|
||||||
|
FooterKey: lipgloss.Color("#7FB4CA"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "kanagawa-paper":
|
||||||
|
return Palette{
|
||||||
|
Name: "kanagawa-paper",
|
||||||
|
Background: lipgloss.Color("#1A1A22"),
|
||||||
|
Panel: lipgloss.Color("#222233"),
|
||||||
|
PanelInactive: lipgloss.Color("#2A2A3E"),
|
||||||
|
PanelElevated: lipgloss.Color("#323248"),
|
||||||
|
StatusBar: lipgloss.Color("#2A2A3E"),
|
||||||
|
Footer: lipgloss.Color("#1A1A22"),
|
||||||
|
Border: lipgloss.Color("#4A4A5E"),
|
||||||
|
BorderActive: lipgloss.Color("#9EC1C9"),
|
||||||
|
Text: lipgloss.Color("#C8C2B0"),
|
||||||
|
Muted: lipgloss.Color("#8B849E"),
|
||||||
|
Accent: lipgloss.Color("#C0A36E"),
|
||||||
|
Info: lipgloss.Color("#9EC1C9"),
|
||||||
|
Success: lipgloss.Color("#8EAA7A"),
|
||||||
|
Selection: lipgloss.Color("#323248"),
|
||||||
|
Hover: lipgloss.Color("#2C2C42"),
|
||||||
|
Marked: lipgloss.Color("#B5534E"),
|
||||||
|
Warning: lipgloss.Color("#C0A36E"),
|
||||||
|
Danger: lipgloss.Color("#B5534E"),
|
||||||
|
ActivePath: lipgloss.Color("#9EC1C9"),
|
||||||
|
ConfirmButton: lipgloss.Color("#8EAA7A"),
|
||||||
|
CancelButton: lipgloss.Color("#B5534E"),
|
||||||
|
ProgressFill: lipgloss.Color("#C0A36E"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#4A4A5E"),
|
||||||
|
HelpNav: lipgloss.Color("#C0A36E"),
|
||||||
|
HelpPanels: lipgloss.Color("#C0A36E"),
|
||||||
|
HelpDialogs: lipgloss.Color("#A58DB8"),
|
||||||
|
HelpMouse: lipgloss.Color("#B5534E"),
|
||||||
|
Folder: lipgloss.Color("#9EC1C9"),
|
||||||
|
TextFile: lipgloss.Color("#8EAA7A"),
|
||||||
|
ConfigFile: lipgloss.Color("#C0A36E"),
|
||||||
|
ExecFile: lipgloss.Color("#D4BE8A"),
|
||||||
|
ImageFile: lipgloss.Color("#9EC1C9"),
|
||||||
|
BinaryFile: lipgloss.Color("#A58DB8"),
|
||||||
|
FooterKey: lipgloss.Color("#9EC1C9"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "rose-pine":
|
||||||
|
return Palette{
|
||||||
|
Name: "rose-pine",
|
||||||
|
Background: lipgloss.Color("#191724"),
|
||||||
|
Panel: lipgloss.Color("#1F1D2E"),
|
||||||
|
PanelInactive: lipgloss.Color("#26233A"),
|
||||||
|
PanelElevated: lipgloss.Color("#2A273F"),
|
||||||
|
StatusBar: lipgloss.Color("#26233A"),
|
||||||
|
Footer: lipgloss.Color("#191724"),
|
||||||
|
Border: lipgloss.Color("#3B355A"),
|
||||||
|
BorderActive: lipgloss.Color("#C4A7E7"),
|
||||||
|
Text: lipgloss.Color("#E0DEF4"),
|
||||||
|
Muted: lipgloss.Color("#908CAA"),
|
||||||
|
Accent: lipgloss.Color("#EB6F92"),
|
||||||
|
Info: lipgloss.Color("#9CCFD8"),
|
||||||
|
Success: lipgloss.Color("#3E8FB0"),
|
||||||
|
Selection: lipgloss.Color("#312F44"),
|
||||||
|
Hover: lipgloss.Color("#2A2740"),
|
||||||
|
Marked: lipgloss.Color("#EB6F92"),
|
||||||
|
Warning: lipgloss.Color("#F6C177"),
|
||||||
|
Danger: lipgloss.Color("#EB6F92"),
|
||||||
|
ActivePath: lipgloss.Color("#9CCFD8"),
|
||||||
|
ConfirmButton: lipgloss.Color("#3E8FB0"),
|
||||||
|
CancelButton: lipgloss.Color("#EB6F92"),
|
||||||
|
ProgressFill: lipgloss.Color("#C4A7E7"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#3B355A"),
|
||||||
|
HelpNav: lipgloss.Color("#C4A7E7"),
|
||||||
|
HelpPanels: lipgloss.Color("#F6C177"),
|
||||||
|
HelpDialogs: lipgloss.Color("#C4A7E7"),
|
||||||
|
HelpMouse: lipgloss.Color("#EB6F92"),
|
||||||
|
Folder: lipgloss.Color("#C4A7E7"),
|
||||||
|
TextFile: lipgloss.Color("#3E8FB0"),
|
||||||
|
ConfigFile: lipgloss.Color("#F6C177"),
|
||||||
|
ExecFile: lipgloss.Color("#E0DEF4"),
|
||||||
|
ImageFile: lipgloss.Color("#9CCFD8"),
|
||||||
|
BinaryFile: lipgloss.Color("#C4A7E7"),
|
||||||
|
FooterKey: lipgloss.Color("#9CCFD8"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "solarized-dark":
|
||||||
|
return Palette{
|
||||||
|
Name: "solarized-dark",
|
||||||
|
Background: lipgloss.Color("#002B36"),
|
||||||
|
Panel: lipgloss.Color("#073642"),
|
||||||
|
PanelInactive: lipgloss.Color("#0D4A56"),
|
||||||
|
PanelElevated: lipgloss.Color("#125A68"),
|
||||||
|
StatusBar: lipgloss.Color("#0D4A56"),
|
||||||
|
Footer: lipgloss.Color("#002B36"),
|
||||||
|
Border: lipgloss.Color("#586E75"),
|
||||||
|
BorderActive: lipgloss.Color("#268BD2"),
|
||||||
|
Text: lipgloss.Color("#93A1A1"),
|
||||||
|
Muted: lipgloss.Color("#657B83"),
|
||||||
|
Accent: lipgloss.Color("#D33682"),
|
||||||
|
Info: lipgloss.Color("#2AA198"),
|
||||||
|
Success: lipgloss.Color("#859900"),
|
||||||
|
Selection: lipgloss.Color("#073642"),
|
||||||
|
Hover: lipgloss.Color("#0B4A56"),
|
||||||
|
Marked: lipgloss.Color("#DC322F"),
|
||||||
|
Warning: lipgloss.Color("#B58900"),
|
||||||
|
Danger: lipgloss.Color("#DC322F"),
|
||||||
|
ActivePath: lipgloss.Color("#2AA198"),
|
||||||
|
ConfirmButton: lipgloss.Color("#859900"),
|
||||||
|
CancelButton: lipgloss.Color("#DC322F"),
|
||||||
|
ProgressFill: lipgloss.Color("#268BD2"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#586E75"),
|
||||||
|
HelpNav: lipgloss.Color("#268BD2"),
|
||||||
|
HelpPanels: lipgloss.Color("#B58900"),
|
||||||
|
HelpDialogs: lipgloss.Color("#D33682"),
|
||||||
|
HelpMouse: lipgloss.Color("#DC322F"),
|
||||||
|
Folder: lipgloss.Color("#268BD2"),
|
||||||
|
TextFile: lipgloss.Color("#859900"),
|
||||||
|
ConfigFile: lipgloss.Color("#B58900"),
|
||||||
|
ExecFile: lipgloss.Color("#CB4B16"),
|
||||||
|
ImageFile: lipgloss.Color("#2AA198"),
|
||||||
|
BinaryFile: lipgloss.Color("#D33682"),
|
||||||
|
FooterKey: lipgloss.Color("#2AA198"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "vesper":
|
||||||
|
return Palette{
|
||||||
|
Name: "vesper",
|
||||||
|
Background: lipgloss.Color("#101010"),
|
||||||
|
Panel: lipgloss.Color("#181820"),
|
||||||
|
PanelInactive: lipgloss.Color("#1E1E30"),
|
||||||
|
PanelElevated: lipgloss.Color("#252540"),
|
||||||
|
StatusBar: lipgloss.Color("#1E1E30"),
|
||||||
|
Footer: lipgloss.Color("#101010"),
|
||||||
|
Border: lipgloss.Color("#303050"),
|
||||||
|
BorderActive: lipgloss.Color("#A0A0FF"),
|
||||||
|
Text: lipgloss.Color("#E0E0F0"),
|
||||||
|
Muted: lipgloss.Color("#8888AA"),
|
||||||
|
Accent: lipgloss.Color("#C0C0FF"),
|
||||||
|
Info: lipgloss.Color("#8080FF"),
|
||||||
|
Success: lipgloss.Color("#80FF80"),
|
||||||
|
Selection: lipgloss.Color("#252540"),
|
||||||
|
Hover: lipgloss.Color("#2A2A48"),
|
||||||
|
Marked: lipgloss.Color("#FF6080"),
|
||||||
|
Warning: lipgloss.Color("#FFB040"),
|
||||||
|
Danger: lipgloss.Color("#FF6080"),
|
||||||
|
ActivePath: lipgloss.Color("#8080FF"),
|
||||||
|
ConfirmButton: lipgloss.Color("#80FF80"),
|
||||||
|
CancelButton: lipgloss.Color("#FF6080"),
|
||||||
|
ProgressFill: lipgloss.Color("#C0C0FF"),
|
||||||
|
ProgressEmpty: lipgloss.Color("#303050"),
|
||||||
|
HelpNav: lipgloss.Color("#A0A0FF"),
|
||||||
|
HelpPanels: lipgloss.Color("#FFB040"),
|
||||||
|
HelpDialogs: lipgloss.Color("#C0C0FF"),
|
||||||
|
HelpMouse: lipgloss.Color("#FF6080"),
|
||||||
|
Folder: lipgloss.Color("#8080FF"),
|
||||||
|
TextFile: lipgloss.Color("#80FF80"),
|
||||||
|
ConfigFile: lipgloss.Color("#FFB040"),
|
||||||
|
ExecFile: lipgloss.Color("#FF8040"),
|
||||||
|
ImageFile: lipgloss.Color("#8080FF"),
|
||||||
|
BinaryFile: lipgloss.Color("#C0C0FF"),
|
||||||
|
FooterKey: lipgloss.Color("#8080FF"),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Palette{}, fmt.Errorf("unknown theme %q", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/vcom-0.2.5/internal/ui/icon_mode.go
Normal file
37
src/vcom-0.2.5/internal/ui/icon_mode.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolveIconMode(mode string) (bool, string) {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(mode)) {
|
||||||
|
case "ascii":
|
||||||
|
return false, "Icon mode: ASCII"
|
||||||
|
case "nerd":
|
||||||
|
return true, ""
|
||||||
|
case "", "auto":
|
||||||
|
default:
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("fc-list"); err != nil {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := exec.Command("fc-list", ":", "family").Output()
|
||||||
|
if err != nil {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
text := strings.ToLower(string(out))
|
||||||
|
if strings.Contains(text, "nerd font") {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, "Nerd Font not found: using ASCII icons"
|
||||||
|
}
|
||||||
416
src/vcom-0.2.5/internal/ui/image_overlay.go
Normal file
416
src/vcom-0.2.5/internal/ui/image_overlay.go
Normal file
|
|
@ -0,0 +1,416 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
_ "image/gif"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
|
)
|
||||||
|
|
||||||
|
type overlayRect struct {
|
||||||
|
x int
|
||||||
|
y int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
type imageOverlayManager struct {
|
||||||
|
cmd *exec.Cmd
|
||||||
|
stdin io.WriteCloser
|
||||||
|
running bool
|
||||||
|
identifier string
|
||||||
|
visible bool
|
||||||
|
backend string
|
||||||
|
backends []string
|
||||||
|
lastPath string
|
||||||
|
lastRect overlayRect
|
||||||
|
kittyTried bool
|
||||||
|
kittyOK bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const kittyImageID = 31337
|
||||||
|
const assumedCellAspect = 0.5
|
||||||
|
const kittyPixelsPerCell = 16
|
||||||
|
|
||||||
|
func minFloat(a float64, b float64) float64 {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func newImageOverlayManager() *imageOverlayManager {
|
||||||
|
return &imageOverlayManager{identifier: "vcom-preview"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) isKittyTerminal() bool {
|
||||||
|
term := strings.ToLower(os.Getenv("TERM"))
|
||||||
|
termProgram := strings.ToLower(os.Getenv("TERM_PROGRAM"))
|
||||||
|
return os.Getenv("KITTY_WINDOW_ID") != "" || strings.Contains(term, "kitty") || strings.Contains(termProgram, "kitty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) canUseKitty() bool {
|
||||||
|
if !m.isKittyTerminal() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m.kittyTried {
|
||||||
|
return m.kittyOK
|
||||||
|
}
|
||||||
|
m.kittyTried = true
|
||||||
|
m.kittyOK = true
|
||||||
|
return m.kittyOK
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeKittyEscape(control string, payload []byte) error {
|
||||||
|
if payload == nil {
|
||||||
|
_, err := fmt.Fprintf(os.Stdout, "\x1b_G%s\x1b\\", control)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := base64.StdEncoding.EncodeToString(payload)
|
||||||
|
_, err := fmt.Fprintf(os.Stdout, "\x1b_G%s;%s\x1b\\", control, encoded)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func resizeNearest(src image.Image, target image.Point) image.Image {
|
||||||
|
bounds := src.Bounds()
|
||||||
|
srcSize := bounds.Size()
|
||||||
|
if target.X <= 0 || target.Y <= 0 || srcSize.X <= target.X && srcSize.Y <= target.Y {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, target.X, target.Y))
|
||||||
|
for y := 0; y < target.Y; y++ {
|
||||||
|
srcY := bounds.Min.Y + (y * srcSize.Y / target.Y)
|
||||||
|
for x := 0; x < target.X; x++ {
|
||||||
|
srcX := bounds.Min.X + (x * srcSize.X / target.X)
|
||||||
|
dst.Set(x, y, src.At(srcX, srcY))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func scaleImageToRect(img image.Image, rect overlayRect) image.Image {
|
||||||
|
size := img.Bounds().Size()
|
||||||
|
if size.X <= 0 || size.Y <= 0 || rect.width <= 0 || rect.height <= 0 {
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
maxWidth := rect.width * kittyPixelsPerCell
|
||||||
|
maxHeight := rect.height * kittyPixelsPerCell
|
||||||
|
if maxWidth <= 0 || maxHeight <= 0 {
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
scale := minFloat(float64(maxWidth)/float64(size.X), float64(maxHeight)/float64(size.Y))
|
||||||
|
if scale >= 1 {
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
target := image.Point{
|
||||||
|
X: max(int(float64(size.X)*scale), 1),
|
||||||
|
Y: max(int(float64(size.Y)*scale), 1),
|
||||||
|
}
|
||||||
|
return resizeNearest(img, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadKittyPayload(path string, rect overlayRect) ([]byte, image.Point, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, image.Point{}, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
img, _, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, image.Point{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
img = scaleImageToRect(img, rect)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := png.Encode(&buf, img); err != nil {
|
||||||
|
return nil, image.Point{}, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), img.Bounds().Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func kittyPlacementControl(rect overlayRect, size image.Point) string {
|
||||||
|
if size.X <= 0 || size.Y <= 0 {
|
||||||
|
return fmt.Sprintf("a=T,f=100,t=d,i=%d,c=%d,C=1", kittyImageID, rect.width)
|
||||||
|
}
|
||||||
|
|
||||||
|
availableAspect := (float64(rect.width) * assumedCellAspect) / float64(rect.height)
|
||||||
|
imageAspect := float64(size.X) / float64(size.Y)
|
||||||
|
if imageAspect >= availableAspect {
|
||||||
|
return fmt.Sprintf("a=T,f=100,t=d,i=%d,c=%d,C=1", kittyImageID, rect.width)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("a=T,f=100,t=d,i=%d,r=%d,C=1", kittyImageID, rect.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) showWithKitty(path string, rect overlayRect) error {
|
||||||
|
data, size, err := loadKittyPayload(path, rect)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeKittyEscape(fmt.Sprintf("a=d,d=I,i=%d", kittyImageID), nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(os.Stdout, "\x1b7\x1b[%d;%dH", rect.y+1, rect.x+1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkSize = 4096
|
||||||
|
for offset := 0; offset < len(data); offset += chunkSize {
|
||||||
|
end := min(offset+chunkSize, len(data))
|
||||||
|
chunk := data[offset:end]
|
||||||
|
more := 0
|
||||||
|
if end < len(data) {
|
||||||
|
more = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
control := fmt.Sprintf("m=%d", more)
|
||||||
|
if offset == 0 {
|
||||||
|
control = fmt.Sprintf("%s,m=%d", kittyPlacementControl(rect, size), more)
|
||||||
|
}
|
||||||
|
if err := writeKittyEscape(control, chunk); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprint(os.Stdout, "\x1b8")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) clearKitty() {
|
||||||
|
_ = writeKittyEscape(fmt.Sprintf("a=d,d=I,i=%d", kittyImageID), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) backendOutput() string {
|
||||||
|
term := strings.ToLower(os.Getenv("TERM"))
|
||||||
|
order := make([]string, 0, 5)
|
||||||
|
switch {
|
||||||
|
case strings.Contains(term, "kitty"):
|
||||||
|
order = append(order, "kitty")
|
||||||
|
case os.Getenv("WAYLAND_DISPLAY") != "":
|
||||||
|
order = append(order, "wayland")
|
||||||
|
case os.Getenv("DISPLAY") != "":
|
||||||
|
order = append(order, "x11")
|
||||||
|
}
|
||||||
|
order = append(order, "wayland", "x11", "sixel", "kitty")
|
||||||
|
|
||||||
|
unique := make([]string, 0, len(order))
|
||||||
|
for _, backend := range order {
|
||||||
|
if !slices.Contains(unique, backend) {
|
||||||
|
unique = append(unique, backend)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(unique, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) backendList() []string {
|
||||||
|
if len(m.backends) != 0 {
|
||||||
|
return m.backends
|
||||||
|
}
|
||||||
|
m.backends = strings.Split(m.backendOutput(), ",")
|
||||||
|
return m.backends
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) startBackend(backend string) error {
|
||||||
|
cmd := exec.Command("ueberzugpp", "layer", "-o", backend)
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Stdout = io.Discard
|
||||||
|
cmd.Stderr = io.Discard
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
_ = stdin.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.cmd = cmd
|
||||||
|
m.stdin = stdin
|
||||||
|
m.running = true
|
||||||
|
m.backend = backend
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) startLegacyBackend() error {
|
||||||
|
cmd := exec.Command("ueberzug", "layer", "--parser", "json")
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Stdout = io.Discard
|
||||||
|
cmd.Stderr = io.Discard
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
_ = stdin.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.cmd = cmd
|
||||||
|
m.stdin = stdin
|
||||||
|
m.running = true
|
||||||
|
m.backend = "ueberzug"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) ensureStarted() error {
|
||||||
|
if m.running {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := exec.LookPath("ueberzugpp"); err == nil {
|
||||||
|
var lastErr error
|
||||||
|
for _, backend := range m.backendList() {
|
||||||
|
if err := m.startBackend(backend); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Probe command channel right away; some backends terminate instantly.
|
||||||
|
if err := m.send(map[string]any{
|
||||||
|
"action": "remove",
|
||||||
|
"identifier": m.identifier,
|
||||||
|
}); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
m.stop()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if lastErr != nil {
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := exec.LookPath("ueberzug"); err == nil {
|
||||||
|
if err := m.startLegacyBackend(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := m.send(map[string]any{
|
||||||
|
"action": "remove",
|
||||||
|
"identifier": m.identifier,
|
||||||
|
}); err != nil {
|
||||||
|
m.stop()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("could not start image overlay backend")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) send(payload map[string]any) error {
|
||||||
|
if !m.running || m.stdin == nil {
|
||||||
|
return fmt.Errorf("overlay not running")
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.WriteString(m.stdin, string(data)+"\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) show(path string, rect overlayRect) error {
|
||||||
|
if rect.width <= 1 || rect.height <= 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if m.visible && m.lastPath == path && m.lastRect == rect {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.canUseKitty() {
|
||||||
|
if m.backend == "ueberzugpp" {
|
||||||
|
m.hide()
|
||||||
|
}
|
||||||
|
if err := m.showWithKitty(path, rect); err == nil {
|
||||||
|
m.backend = "kitty"
|
||||||
|
m.visible = true
|
||||||
|
m.lastPath = path
|
||||||
|
m.lastRect = rect
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(m.backendList()); i++ {
|
||||||
|
if err := m.ensureStarted(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
payload := map[string]any{
|
||||||
|
"action": "add",
|
||||||
|
"identifier": m.identifier,
|
||||||
|
"path": path,
|
||||||
|
"x": rect.x,
|
||||||
|
"y": rect.y,
|
||||||
|
}
|
||||||
|
if m.backend == "ueberzug" {
|
||||||
|
payload["width"] = rect.width
|
||||||
|
payload["height"] = rect.height
|
||||||
|
} else {
|
||||||
|
payload["max_width"] = rect.width
|
||||||
|
payload["max_height"] = rect.height
|
||||||
|
payload["scaler"] = "fit_contain"
|
||||||
|
}
|
||||||
|
if err := m.send(payload); err == nil {
|
||||||
|
m.visible = true
|
||||||
|
m.lastPath = path
|
||||||
|
m.lastRect = rect
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.stop()
|
||||||
|
if m.backend != "ueberzug" && len(m.backends) > 0 {
|
||||||
|
m.backends = append(m.backends[1:], m.backends[0])
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("could not render image overlay")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) hide() {
|
||||||
|
if !m.visible {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch m.backend {
|
||||||
|
case "kitty":
|
||||||
|
m.clearKitty()
|
||||||
|
case "ueberzugpp", "ueberzug":
|
||||||
|
if m.running {
|
||||||
|
_ = m.send(map[string]any{
|
||||||
|
"action": "remove",
|
||||||
|
"identifier": m.identifier,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.visible = false
|
||||||
|
m.lastPath = ""
|
||||||
|
m.lastRect = overlayRect{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *imageOverlayManager) stop() {
|
||||||
|
m.hide()
|
||||||
|
if m.stdin != nil {
|
||||||
|
_ = m.stdin.Close()
|
||||||
|
m.stdin = nil
|
||||||
|
}
|
||||||
|
if m.cmd != nil && m.cmd.Process != nil {
|
||||||
|
_ = m.cmd.Process.Kill()
|
||||||
|
_, _ = m.cmd.Process.Wait()
|
||||||
|
}
|
||||||
|
m.cmd = nil
|
||||||
|
m.running = false
|
||||||
|
m.backend = ""
|
||||||
|
}
|
||||||
97
src/vcom-0.2.5/internal/ui/keymap.go
Normal file
97
src/vcom-0.2.5/internal/ui/keymap.go
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "github.com/charmbracelet/bubbles/key"
|
||||||
|
|
||||||
|
type KeyMap struct {
|
||||||
|
Help key.Binding
|
||||||
|
Visual key.Binding
|
||||||
|
Caret key.Binding
|
||||||
|
View key.Binding
|
||||||
|
Rename key.Binding
|
||||||
|
Info key.Binding
|
||||||
|
Archive key.Binding
|
||||||
|
SelectText key.Binding
|
||||||
|
ToggleHidden key.Binding
|
||||||
|
CycleTheme key.Binding
|
||||||
|
CycleSort key.Binding
|
||||||
|
SSH key.Binding
|
||||||
|
Mirror key.Binding
|
||||||
|
Up key.Binding
|
||||||
|
Down key.Binding
|
||||||
|
SelectUp key.Binding
|
||||||
|
SelectDown key.Binding
|
||||||
|
PageUp key.Binding
|
||||||
|
PageDown key.Binding
|
||||||
|
Open key.Binding
|
||||||
|
Back key.Binding
|
||||||
|
Switch key.Binding
|
||||||
|
Filter key.Binding
|
||||||
|
Refresh key.Binding
|
||||||
|
DirSize key.Binding
|
||||||
|
Copy key.Binding
|
||||||
|
Move key.Binding
|
||||||
|
Mkdir key.Binding
|
||||||
|
Delete key.Binding
|
||||||
|
Unpack key.Binding
|
||||||
|
Confirm key.Binding
|
||||||
|
Background key.Binding
|
||||||
|
ProgressCancel key.Binding
|
||||||
|
Cancel key.Binding
|
||||||
|
Quit key.Binding
|
||||||
|
HistoryBack key.Binding
|
||||||
|
HistoryForward key.Binding
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultKeyMap() KeyMap {
|
||||||
|
return KeyMap{
|
||||||
|
Help: key.NewBinding(key.WithKeys("f1", "?"), key.WithHelp("F1/?", "help")),
|
||||||
|
Rename: key.NewBinding(key.WithKeys("f2", "r"), key.WithHelp("F2/r", "rename")),
|
||||||
|
View: key.NewBinding(key.WithKeys("f3", "v"), key.WithHelp("F3/v", "view")),
|
||||||
|
Visual: key.NewBinding(key.WithKeys("v"), key.WithHelp("v", "visual")),
|
||||||
|
Caret: key.NewBinding(key.WithKeys("i"), key.WithHelp("i", "caret")),
|
||||||
|
Archive: key.NewBinding(key.WithKeys("f4", "a"), key.WithHelp("F4/a", "archive")),
|
||||||
|
Info: key.NewBinding(key.WithKeys("f9", "o"), key.WithHelp("F9/o", "info")),
|
||||||
|
SelectText: key.NewBinding(key.WithKeys("ctrl+t"), key.WithHelp("C-t", "text select")),
|
||||||
|
ToggleHidden: key.NewBinding(key.WithKeys("."), key.WithHelp(".", "hidden")),
|
||||||
|
CycleTheme: key.NewBinding(key.WithKeys("t"), key.WithHelp("t", "select theme")),
|
||||||
|
CycleSort: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "sort")),
|
||||||
|
SSH: key.NewBinding(key.WithKeys("f12", "s"), key.WithHelp("F12/s", "ssh")),
|
||||||
|
Mirror: key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "mirror pane")),
|
||||||
|
Up: key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("↑/k", "up")),
|
||||||
|
Down: key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("↓/j", "down")),
|
||||||
|
SelectUp: key.NewBinding(key.WithKeys("shift+up", "K"), key.WithHelp("S-↑/K", "select up")),
|
||||||
|
SelectDown: key.NewBinding(key.WithKeys("shift+down", "J"), key.WithHelp("S-↓/J", "select down")),
|
||||||
|
PageUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("PgUp", "page up")),
|
||||||
|
PageDown: key.NewBinding(),
|
||||||
|
Filter: key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "filter")),
|
||||||
|
Open: key.NewBinding(key.WithKeys("enter", "right"), key.WithHelp("Enter", "open")),
|
||||||
|
Back: key.NewBinding(key.WithKeys("backspace", "left"), key.WithHelp("←", "parent")),
|
||||||
|
Switch: key.NewBinding(key.WithKeys("tab", "h", "l"), key.WithHelp("Tab/h/l", "switch pane")),
|
||||||
|
Refresh: key.NewBinding(key.WithKeys("ctrl+r"), key.WithHelp("C-r", "refresh")),
|
||||||
|
DirSize: key.NewBinding(key.WithKeys(" "), key.WithHelp("Space", "dir size")),
|
||||||
|
Copy: key.NewBinding(key.WithKeys("f5", "c"), key.WithHelp("F5/c", "copy")),
|
||||||
|
Move: key.NewBinding(key.WithKeys("f6", "m"), key.WithHelp("F6/m", "move")),
|
||||||
|
Mkdir: key.NewBinding(key.WithKeys("f7", "n"), key.WithHelp("F7/n", "mkdir")),
|
||||||
|
Delete: key.NewBinding(key.WithKeys("f8", "delete", "x"), key.WithHelp("F8/x", "delete")),
|
||||||
|
Unpack: key.NewBinding(key.WithKeys("f11", "e"), key.WithHelp("F11/e", "unpack")),
|
||||||
|
Confirm: key.NewBinding(key.WithKeys("enter", "y"), key.WithHelp("Enter/y", "confirm")),
|
||||||
|
Background: key.NewBinding(key.WithKeys("b"), key.WithHelp("b", "background")),
|
||||||
|
ProgressCancel: key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "cancel transfer")),
|
||||||
|
HistoryBack: key.NewBinding(key.WithKeys("alt+left"), key.WithHelp("A-←", "back")),
|
||||||
|
HistoryForward: key.NewBinding(key.WithKeys("alt+right"), key.WithHelp("A-→", "forward")),
|
||||||
|
Cancel: key.NewBinding(key.WithKeys("esc"), key.WithHelp("Esc", "cancel")),
|
||||||
|
Quit: key.NewBinding(key.WithKeys("f10", "q", "ctrl+c"), key.WithHelp("F10/q", "quit")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k KeyMap) ShortHelp() []key.Binding {
|
||||||
|
return []key.Binding{k.Help, k.Rename, k.View, k.Archive, k.Copy, k.Move, k.Mkdir, k.Delete, k.Info, k.Quit, k.Unpack, k.SSH}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k KeyMap) FullHelp() [][]key.Binding {
|
||||||
|
return [][]key.Binding{
|
||||||
|
{k.Help, k.Up, k.Down, k.SelectUp, k.SelectDown, k.Open, k.Back},
|
||||||
|
{k.Rename, k.View, k.Caret, k.Archive, k.Copy, k.Move, k.Delete},
|
||||||
|
{k.Unpack, k.SelectText, k.DirSize, k.Refresh, k.ToggleHidden, k.CycleSort, k.CycleTheme, k.Quit},
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/vcom-0.2.5/internal/ui/layout.go
Normal file
52
src/vcom-0.2.5/internal/ui/layout.go
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import tea "github.com/charmbracelet/bubbletea"
|
||||||
|
|
||||||
|
// cyrillicToLatin maps Russian ЙЦУКЕН characters to their QWERTY positional
|
||||||
|
// equivalents. This allows all single-letter commands to work regardless of
|
||||||
|
// whether the user has a Russian or Latin keyboard layout active.
|
||||||
|
var cyrillicToLatin = map[rune]rune{
|
||||||
|
// Lowercase — same physical key position
|
||||||
|
'й': 'q', 'ц': 'w', 'у': 'e', 'к': 'r', 'е': 't', 'н': 'y',
|
||||||
|
'г': 'u', 'ш': 'i', 'щ': 'o', 'з': 'p', 'х': '[', 'ъ': ']',
|
||||||
|
'ф': 'a', 'ы': 's', 'в': 'd', 'а': 'f', 'п': 'g', 'р': 'h',
|
||||||
|
'о': 'j', 'л': 'k', 'д': 'l', 'ж': ';', 'э': '\'',
|
||||||
|
'я': 'z', 'ч': 'x', 'с': 'c', 'м': 'v', 'и': 'b', 'т': 'n',
|
||||||
|
'ь': 'm', 'б': ',', 'ю': '.', 'ё': '`',
|
||||||
|
|
||||||
|
// Uppercase — same physical key position with Shift
|
||||||
|
'Й': 'Q', 'Ц': 'W', 'У': 'E', 'К': 'R', 'Е': 'T', 'Н': 'Y',
|
||||||
|
'Г': 'U', 'Ш': 'I', 'Щ': 'O', 'З': 'P', 'Х': '{', 'Ъ': '}',
|
||||||
|
'Ф': 'A', 'Ы': 'S', 'В': 'D', 'А': 'F', 'П': 'G', 'Р': 'H',
|
||||||
|
'О': 'J', 'Л': 'K', 'Д': 'L', 'Ж': ':', 'Э': '"',
|
||||||
|
'Я': 'Z', 'Ч': 'X', 'С': 'C', 'М': 'V', 'И': 'B', 'Т': 'N',
|
||||||
|
'Ь': 'M', 'Б': '<', 'Ю': '>', 'Ё': '~',
|
||||||
|
}
|
||||||
|
|
||||||
|
// translateKeyMsg translates Cyrillic characters in a KeyMsg to their Latin
|
||||||
|
// positional equivalents (ЙЦУКЕН → QWERTY). If no translation is needed, the
|
||||||
|
// original message is returned unchanged.
|
||||||
|
func translateKeyMsg(msg tea.KeyMsg) tea.KeyMsg {
|
||||||
|
if msg.Type != tea.KeyRunes || len(msg.Runes) == 0 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
translated := make([]rune, len(msg.Runes))
|
||||||
|
changed := false
|
||||||
|
for i, r := range msg.Runes {
|
||||||
|
if latin, ok := cyrillicToLatin[r]; ok {
|
||||||
|
translated[i] = latin
|
||||||
|
changed = true
|
||||||
|
} else {
|
||||||
|
translated[i] = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
return tea.KeyMsg{
|
||||||
|
Type: msg.Type,
|
||||||
|
Runes: translated,
|
||||||
|
Alt: msg.Alt,
|
||||||
|
Paste: msg.Paste,
|
||||||
|
}
|
||||||
|
}
|
||||||
6742
src/vcom-0.2.5/internal/ui/model.go
Normal file
6742
src/vcom-0.2.5/internal/ui/model.go
Normal file
File diff suppressed because it is too large
Load diff
850
src/vcom-0.2.5/internal/ui/pane.go
Normal file
850
src/vcom-0.2.5/internal/ui/pane.go
Normal file
|
|
@ -0,0 +1,850 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
|
||||||
|
"vcom/internal/config"
|
||||||
|
vfs "vcom/internal/fs"
|
||||||
|
"vcom/internal/fs/remote"
|
||||||
|
"vcom/internal/theme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PaneID string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PaneLeft PaneID = "left"
|
||||||
|
PaneRight PaneID = "right"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BrowserPane struct {
|
||||||
|
ID PaneID
|
||||||
|
Path string
|
||||||
|
Entries []vfs.Entry
|
||||||
|
Cursor int
|
||||||
|
Offset int
|
||||||
|
Marked map[string]struct{}
|
||||||
|
Archive []ArchiveMount
|
||||||
|
Remote []RemoteMount
|
||||||
|
|
||||||
|
dirHistory []string
|
||||||
|
dirFuture []string
|
||||||
|
|
||||||
|
// cursorMemory remembers the last selected entry display name per directory
|
||||||
|
// within a session. Keyed by directory path. Restored when re-entering a dir.
|
||||||
|
cursorMemory map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArchiveMount struct {
|
||||||
|
SourcePath string
|
||||||
|
ParentPath string
|
||||||
|
RootPath string
|
||||||
|
TempDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteMount represents an active SSH/SFTP remote filesystem connection.
|
||||||
|
type RemoteMount struct {
|
||||||
|
Host remote.SSHHost
|
||||||
|
RemotePath string
|
||||||
|
Client *remote.SSHClient
|
||||||
|
Connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) Selected() (vfs.Entry, bool) {
|
||||||
|
if len(p.Entries) == 0 || p.Cursor < 0 || p.Cursor >= len(p.Entries) {
|
||||||
|
return vfs.Entry{}, false
|
||||||
|
}
|
||||||
|
return p.Entries[p.Cursor], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) SetEntries(entries []vfs.Entry, preserveKey string) {
|
||||||
|
p.Entries = entries
|
||||||
|
p.PruneMarks()
|
||||||
|
if len(entries) == 0 {
|
||||||
|
p.Cursor = 0
|
||||||
|
p.Offset = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if preserveKey != "" {
|
||||||
|
oldCursor := p.Cursor
|
||||||
|
p.Cursor = vfs.FindSelected(entries, preserveKey)
|
||||||
|
if p.Cursor != oldCursor {
|
||||||
|
p.Offset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Cursor >= len(entries) {
|
||||||
|
p.Cursor = len(entries) - 1
|
||||||
|
}
|
||||||
|
if p.Cursor < 0 {
|
||||||
|
p.Cursor = 0
|
||||||
|
}
|
||||||
|
if p.Offset > p.Cursor {
|
||||||
|
p.Offset = p.Cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) Move(delta int, pageSize int) {
|
||||||
|
if len(p.Entries) == 0 {
|
||||||
|
p.Cursor = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Cursor += delta
|
||||||
|
if p.Cursor < 0 {
|
||||||
|
p.Cursor = 0
|
||||||
|
}
|
||||||
|
if p.Cursor >= len(p.Entries) {
|
||||||
|
p.Cursor = len(p.Entries) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Cursor < p.Offset {
|
||||||
|
p.Offset = p.Cursor
|
||||||
|
}
|
||||||
|
if pageSize > 0 && p.Cursor >= p.Offset+pageSize {
|
||||||
|
p.Offset = p.Cursor - pageSize + 1
|
||||||
|
}
|
||||||
|
if p.Offset < 0 {
|
||||||
|
p.Offset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) EnsureMarked(path string) {
|
||||||
|
if strings.TrimSpace(path) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.Marked == nil {
|
||||||
|
p.Marked = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
p.Marked[path] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) ToggleMarked(path string) {
|
||||||
|
if strings.TrimSpace(path) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.Marked == nil {
|
||||||
|
p.Marked = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
if _, ok := p.Marked[path]; ok {
|
||||||
|
delete(p.Marked, path)
|
||||||
|
if len(p.Marked) == 0 {
|
||||||
|
p.Marked = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Marked[path] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) IsMarked(path string) bool {
|
||||||
|
if p.Marked == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok := p.Marked[path]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) ClearMarks() {
|
||||||
|
p.Marked = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) PruneMarks() {
|
||||||
|
if len(p.Marked) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
valid := map[string]struct{}{}
|
||||||
|
for _, entry := range p.Entries {
|
||||||
|
if entry.IsParent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
valid[entry.Path] = struct{}{}
|
||||||
|
}
|
||||||
|
for path := range p.Marked {
|
||||||
|
if _, ok := valid[path]; !ok {
|
||||||
|
delete(p.Marked, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(p.Marked) == 0 {
|
||||||
|
p.Marked = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) MarkedEntries() []vfs.Entry {
|
||||||
|
if len(p.Marked) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make([]vfs.Entry, 0, len(p.Marked))
|
||||||
|
for _, entry := range p.Entries {
|
||||||
|
if entry.IsParent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p.IsMarked(entry.Path) {
|
||||||
|
result = append(result, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) EnsureVisible(pageSize int) {
|
||||||
|
if pageSize <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.Cursor < p.Offset {
|
||||||
|
p.Offset = p.Cursor
|
||||||
|
}
|
||||||
|
if p.Cursor >= p.Offset+pageSize {
|
||||||
|
p.Offset = p.Cursor - pageSize + 1
|
||||||
|
}
|
||||||
|
if p.Offset < 0 {
|
||||||
|
p.Offset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) SaveCursor(dirPath string, entryName string) {
|
||||||
|
if dirPath == "" || entryName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.cursorMemory == nil {
|
||||||
|
p.cursorMemory = map[string]string{}
|
||||||
|
}
|
||||||
|
p.cursorMemory[dirPath] = entryName
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadCursor returns the saved entry name for a directory, or empty string.
|
||||||
|
func (p *BrowserPane) LoadCursor(dirPath string) string {
|
||||||
|
if p.cursorMemory == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return p.cursorMemory[dirPath]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) InArchive() bool {
|
||||||
|
return len(p.Archive) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushHistory saves the current path to the back-stack and clears the forward-stack.
|
||||||
|
func (p *BrowserPane) PushHistory(path string) {
|
||||||
|
p.dirHistory = append(p.dirHistory, path)
|
||||||
|
p.dirFuture = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopHistory returns the most recent path from the back-stack.
|
||||||
|
func (p *BrowserPane) PopHistory() (string, bool) {
|
||||||
|
if len(p.dirHistory) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
path := p.dirHistory[len(p.dirHistory)-1]
|
||||||
|
p.dirHistory = p.dirHistory[:len(p.dirHistory)-1]
|
||||||
|
return path, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushFuture saves the current path to the forward-stack.
|
||||||
|
func (p *BrowserPane) PushFuture(path string) {
|
||||||
|
p.dirFuture = append(p.dirFuture, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopFuture returns the most recent path from the forward-stack.
|
||||||
|
func (p *BrowserPane) PopFuture() (string, bool) {
|
||||||
|
if len(p.dirFuture) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
path := p.dirFuture[len(p.dirFuture)-1]
|
||||||
|
p.dirFuture = p.dirFuture[:len(p.dirFuture)-1]
|
||||||
|
return path, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasHistory returns true if there are entries in the back-stack.
|
||||||
|
func (p *BrowserPane) HasHistory() bool {
|
||||||
|
return len(p.dirHistory) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasFuture returns true if there are entries in the forward-stack.
|
||||||
|
func (p *BrowserPane) HasFuture() bool {
|
||||||
|
return len(p.dirFuture) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// HistoryDepth returns the number of entries in the back-stack.
|
||||||
|
func (p *BrowserPane) HistoryDepth() int {
|
||||||
|
return len(p.dirHistory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FutureDepth returns the number of entries in the forward-stack.
|
||||||
|
func (p *BrowserPane) FutureDepth() int {
|
||||||
|
return len(p.dirFuture)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) PushArchive(mount ArchiveMount) {
|
||||||
|
p.Archive = append(p.Archive, mount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) PopArchive() (ArchiveMount, bool) {
|
||||||
|
if len(p.Archive) == 0 {
|
||||||
|
return ArchiveMount{}, false
|
||||||
|
}
|
||||||
|
last := p.Archive[len(p.Archive)-1]
|
||||||
|
p.Archive = p.Archive[:len(p.Archive)-1]
|
||||||
|
return last, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) CurrentArchive() (ArchiveMount, bool) {
|
||||||
|
if len(p.Archive) == 0 {
|
||||||
|
return ArchiveMount{}, false
|
||||||
|
}
|
||||||
|
return p.Archive[len(p.Archive)-1], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) ClearArchives() []ArchiveMount {
|
||||||
|
if len(p.Archive) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make([]ArchiveMount, len(p.Archive))
|
||||||
|
copy(out, p.Archive)
|
||||||
|
p.Archive = nil
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) PushRemote(mount RemoteMount) {
|
||||||
|
p.Remote = append(p.Remote, mount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) PopRemote() (RemoteMount, bool) {
|
||||||
|
if len(p.Remote) == 0 {
|
||||||
|
return RemoteMount{}, false
|
||||||
|
}
|
||||||
|
last := p.Remote[len(p.Remote)-1]
|
||||||
|
p.Remote = p.Remote[:len(p.Remote)-1]
|
||||||
|
return last, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) CurrentRemote() (RemoteMount, bool) {
|
||||||
|
if len(p.Remote) == 0 {
|
||||||
|
return RemoteMount{}, false
|
||||||
|
}
|
||||||
|
return p.Remote[len(p.Remote)-1], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) InRemote() bool {
|
||||||
|
return len(p.Remote) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) ClearRemotes() []RemoteMount {
|
||||||
|
if len(p.Remote) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make([]RemoteMount, len(p.Remote))
|
||||||
|
copy(out, p.Remote)
|
||||||
|
p.Remote = nil
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BrowserPane) DisplayPath() string {
|
||||||
|
if len(p.Remote) > 0 {
|
||||||
|
top := p.Remote[len(p.Remote)-1]
|
||||||
|
statusIcon := ""
|
||||||
|
if !top.Connected {
|
||||||
|
statusIcon = ""
|
||||||
|
}
|
||||||
|
if top.RemotePath == "/" || top.RemotePath == "" {
|
||||||
|
return fmt.Sprintf("%s %s:", statusIcon, top.Host.Name)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s:%s", statusIcon, top.Host.Name, top.RemotePath)
|
||||||
|
}
|
||||||
|
if len(p.Archive) == 0 {
|
||||||
|
return p.Path
|
||||||
|
}
|
||||||
|
top := p.Archive[len(p.Archive)-1]
|
||||||
|
rel, err := filepath.Rel(top.RootPath, p.Path)
|
||||||
|
if err != nil {
|
||||||
|
return p.Path
|
||||||
|
}
|
||||||
|
rel = filepath.ToSlash(rel)
|
||||||
|
if rel == "." {
|
||||||
|
rel = ""
|
||||||
|
}
|
||||||
|
if rel == "" {
|
||||||
|
return top.SourcePath + "::"
|
||||||
|
}
|
||||||
|
return top.SourcePath + "::/" + rel
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPane(
|
||||||
|
pane BrowserPane,
|
||||||
|
cfg config.Config,
|
||||||
|
palette theme.Palette,
|
||||||
|
width int,
|
||||||
|
height int,
|
||||||
|
active bool,
|
||||||
|
hoverIndex int,
|
||||||
|
useNerdIcons bool,
|
||||||
|
) string {
|
||||||
|
if width <= 0 || height <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
borderColor := palette.Border
|
||||||
|
headerBg := palette.PanelInactive
|
||||||
|
bodyBg := palette.Panel
|
||||||
|
if active {
|
||||||
|
borderColor = palette.BorderActive
|
||||||
|
headerBg = palette.Selection
|
||||||
|
}
|
||||||
|
|
||||||
|
innerWidth := max(width-2, 1)
|
||||||
|
innerHeight := max(height-2, 1)
|
||||||
|
|
||||||
|
box := lipgloss.NewStyle().
|
||||||
|
Width(innerWidth).
|
||||||
|
Height(innerHeight).
|
||||||
|
Background(bodyBg).
|
||||||
|
Foreground(palette.Text).
|
||||||
|
BorderStyle(borderStyle(cfg.UI.Border)).
|
||||||
|
BorderForeground(borderColor).
|
||||||
|
BorderBackground(bodyBg)
|
||||||
|
|
||||||
|
header := lipgloss.NewStyle().
|
||||||
|
Render(renderPaneHeader(pane, cfg, palette, innerWidth, active, headerBg))
|
||||||
|
|
||||||
|
isSSHHostList := pane.Path == "ssh://"
|
||||||
|
rowsHeight := max(innerHeight-2, 1)
|
||||||
|
headerRow := renderColumnsHeader(cfg, innerWidth, palette, bodyBg, useNerdIcons, isSSHHostList)
|
||||||
|
rows := renderPaneRows(pane, cfg, palette, innerWidth, rowsHeight, active, hoverIndex, bodyBg, useNerdIcons, isSSHHostList)
|
||||||
|
content := lipgloss.JoinVertical(lipgloss.Left, header, headerRow, rows)
|
||||||
|
return box.Render(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPaneHeader(pane BrowserPane, cfg config.Config, palette theme.Palette, width int, active bool, headerBg lipgloss.Color) string {
|
||||||
|
pathWidth := max(width, 4)
|
||||||
|
pathStyle := lipgloss.NewStyle().
|
||||||
|
Width(pathWidth).
|
||||||
|
Background(headerBg).
|
||||||
|
Foreground(palette.Text).
|
||||||
|
Bold(active)
|
||||||
|
if active {
|
||||||
|
pathStyle = pathStyle.Foreground(palette.ActivePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
Width(width).
|
||||||
|
Background(headerBg).
|
||||||
|
Render(pathStyle.Render(truncateMiddle(compactPath(pane.DisplayPath(), cfg.UI.PathDisplay), pathWidth)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderColumnsHeader(cfg config.Config, width int, palette theme.Palette, background lipgloss.Color, useNerdIcons bool, hideExtraCols bool) string {
|
||||||
|
columns := buildColumns(cfg, width, useNerdIcons, hideExtraCols)
|
||||||
|
parts := make([]string, 0, len(columns))
|
||||||
|
for idx, column := range columns {
|
||||||
|
style := lipgloss.NewStyle().
|
||||||
|
Width(column.Width).
|
||||||
|
Foreground(palette.Muted).
|
||||||
|
Background(background).
|
||||||
|
Bold(true)
|
||||||
|
if column.AlignRight {
|
||||||
|
style = style.Align(lipgloss.Right)
|
||||||
|
}
|
||||||
|
parts = append(parts, style.Render(truncateRight(column.Title, column.Width)))
|
||||||
|
if idx < len(columns)-1 {
|
||||||
|
parts = append(parts, columnSeparator(column.Key, palette, background))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
Width(width).
|
||||||
|
Background(background).
|
||||||
|
Render(strings.Join(parts, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPaneRows(pane BrowserPane, cfg config.Config, palette theme.Palette, width int, height int, active bool, hoverIndex int, background lipgloss.Color, useNerdIcons bool, hideExtraCols bool) string {
|
||||||
|
if len(pane.Entries) == 0 {
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
Width(width).
|
||||||
|
Height(height).
|
||||||
|
Padding(1, 1).
|
||||||
|
Background(background).
|
||||||
|
Foreground(palette.Muted).
|
||||||
|
Render("Empty directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleHeight := max(height, 1)
|
||||||
|
pane.EnsureVisible(visibleHeight)
|
||||||
|
end := min(len(pane.Entries), pane.Offset+visibleHeight)
|
||||||
|
|
||||||
|
lines := make([]string, 0, visibleHeight)
|
||||||
|
for idx := pane.Offset; idx < end; idx++ {
|
||||||
|
entry := pane.Entries[idx]
|
||||||
|
isSelected := idx == pane.Cursor && active
|
||||||
|
marked := !entry.IsParent && pane.IsMarked(entry.Path)
|
||||||
|
row := renderEntryRow(entry, cfg, width, isSelected, marked, idx == hoverIndex, active, palette, background, useNerdIcons, hideExtraCols)
|
||||||
|
lines = append(lines, row)
|
||||||
|
}
|
||||||
|
for len(lines) < visibleHeight {
|
||||||
|
lines = append(lines, lipgloss.NewStyle().Width(width).Background(background).Render(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
Width(width).
|
||||||
|
Background(background).
|
||||||
|
Render(strings.Join(lines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderEntryRow(entry vfs.Entry, cfg config.Config, width int, selected bool, marked bool, hovered bool, active bool, palette theme.Palette, baseBackground lipgloss.Color, useNerdIcons bool, hideExtraCols bool) string {
|
||||||
|
columns := buildColumns(cfg, width, useNerdIcons, hideExtraCols)
|
||||||
|
rowBackground := baseBackground
|
||||||
|
switch {
|
||||||
|
case marked:
|
||||||
|
rowBackground = palette.Marked
|
||||||
|
case selected:
|
||||||
|
rowBackground = palette.Selection
|
||||||
|
case hovered:
|
||||||
|
rowBackground = palette.Hover
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := make([]string, 0, len(columns))
|
||||||
|
for idx, column := range columns {
|
||||||
|
value := column.Value(entry, cfg.Browser.HumanReadableSize)
|
||||||
|
foreground := entryColor(entry, palette)
|
||||||
|
if marked {
|
||||||
|
foreground = palette.Background
|
||||||
|
}
|
||||||
|
style := lipgloss.NewStyle().
|
||||||
|
Width(column.Width).
|
||||||
|
Foreground(foreground).
|
||||||
|
Background(rowBackground)
|
||||||
|
|
||||||
|
if entry.IsHidden && !marked {
|
||||||
|
style = style.Foreground(palette.Muted)
|
||||||
|
}
|
||||||
|
if column.AlignRight {
|
||||||
|
style = style.Align(lipgloss.Right)
|
||||||
|
}
|
||||||
|
parts = append(parts, style.Render(truncateForColumn(value, column.Width, column.AlignRight)))
|
||||||
|
if idx < len(columns)-1 {
|
||||||
|
parts = append(parts, columnSeparator(column.Key, palette, rowBackground))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rowStyle := lipgloss.NewStyle().Width(width).Background(rowBackground)
|
||||||
|
if selected && active {
|
||||||
|
rowStyle = rowStyle.Bold(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowStyle.Render(strings.Join(parts, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
type columnSpec struct {
|
||||||
|
Key string
|
||||||
|
Title string
|
||||||
|
Width int
|
||||||
|
MinWidth int
|
||||||
|
AlignRight bool
|
||||||
|
Value func(entry vfs.Entry, human bool) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildColumns(cfg config.Config, totalWidth int, useNerdIcons bool, hideExtraCols bool) []columnSpec {
|
||||||
|
fixed := []columnSpec{}
|
||||||
|
|
||||||
|
if cfg.Browser.Columns.Permissions {
|
||||||
|
fixed = append(fixed, columnSpec{
|
||||||
|
Key: "permissions",
|
||||||
|
Title: "Perms",
|
||||||
|
Width: 10,
|
||||||
|
MinWidth: 9,
|
||||||
|
Value: func(entry vfs.Entry, _ bool) string {
|
||||||
|
return vfs.Permissions(entry.Mode)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if cfg.Browser.Columns.Extension {
|
||||||
|
fixed = append(fixed, columnSpec{
|
||||||
|
Key: "extension",
|
||||||
|
Title: "Ext",
|
||||||
|
Width: 6,
|
||||||
|
MinWidth: 4,
|
||||||
|
Value: func(entry vfs.Entry, _ bool) string {
|
||||||
|
if entry.IsDir {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return entry.Extension
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if cfg.Browser.Columns.Size && !hideExtraCols {
|
||||||
|
fixed = append(fixed, columnSpec{
|
||||||
|
Key: "size",
|
||||||
|
Title: "Size",
|
||||||
|
Width: 9,
|
||||||
|
MinWidth: 6,
|
||||||
|
AlignRight: true,
|
||||||
|
Value: func(entry vfs.Entry, human bool) string {
|
||||||
|
if entry.IsDir {
|
||||||
|
if !entry.DirSizeKnown {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if human {
|
||||||
|
return vfs.HumanSize(entry.Size)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d", entry.Size)
|
||||||
|
}
|
||||||
|
if human {
|
||||||
|
return vfs.HumanSize(entry.Size)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d", entry.Size)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if cfg.Browser.Columns.Created {
|
||||||
|
fixed = append(fixed, columnSpec{
|
||||||
|
Key: "created",
|
||||||
|
Title: "Created",
|
||||||
|
Width: 11,
|
||||||
|
MinWidth: 8,
|
||||||
|
Value: func(entry vfs.Entry, _ bool) string {
|
||||||
|
if !entry.CreatedKnown {
|
||||||
|
return "n/a"
|
||||||
|
}
|
||||||
|
return vfs.CompactTime(entry.CreatedAt)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if cfg.Browser.Columns.Modified && !hideExtraCols {
|
||||||
|
fixed = append(fixed, columnSpec{
|
||||||
|
Key: "modified",
|
||||||
|
Title: "Modified",
|
||||||
|
Width: 11,
|
||||||
|
MinWidth: 8,
|
||||||
|
Value: func(entry vfs.Entry, _ bool) string {
|
||||||
|
if entry.IsParent {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return vfs.CompactTime(entry.ModifiedAt)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
minNameWidth := 4
|
||||||
|
gaps := 0
|
||||||
|
for _, column := range fixed {
|
||||||
|
gaps += separatorWidth(column.Key)
|
||||||
|
}
|
||||||
|
availableForColumns := totalWidth - gaps
|
||||||
|
if availableForColumns < minNameWidth {
|
||||||
|
availableForColumns = minNameWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
fixedWidth := 0
|
||||||
|
for _, column := range fixed {
|
||||||
|
fixedWidth += column.Width
|
||||||
|
}
|
||||||
|
for fixedWidth+minNameWidth > availableForColumns {
|
||||||
|
changed := false
|
||||||
|
for idx := len(fixed) - 1; idx >= 0 && fixedWidth+minNameWidth > availableForColumns; idx-- {
|
||||||
|
if fixed[idx].Width > fixed[idx].MinWidth {
|
||||||
|
fixed[idx].Width--
|
||||||
|
fixedWidth--
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nameWidth := max(availableForColumns-fixedWidth, minNameWidth)
|
||||||
|
name := columnSpec{
|
||||||
|
Key: "name",
|
||||||
|
Title: "Name",
|
||||||
|
Width: nameWidth,
|
||||||
|
MinWidth: minNameWidth,
|
||||||
|
Value: func(entry vfs.Entry, _ bool) string {
|
||||||
|
return entryIcon(entry, useNerdIcons) + " " + entry.DisplayName()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return append([]columnSpec{name}, fixed...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func columnSeparator(columnKey string, palette theme.Palette, background lipgloss.Color) string {
|
||||||
|
width := separatorWidth(columnKey)
|
||||||
|
style := lipgloss.NewStyle().
|
||||||
|
Width(width).
|
||||||
|
Foreground(palette.Border)
|
||||||
|
if background != lipgloss.Color("") {
|
||||||
|
style = style.Background(background)
|
||||||
|
}
|
||||||
|
return style.Render(strings.Repeat(" ", width))
|
||||||
|
}
|
||||||
|
|
||||||
|
func separatorWidth(columnKey string) int {
|
||||||
|
if columnKey == "size" {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func borderStyle(value string) lipgloss.Border {
|
||||||
|
switch strings.ToLower(value) {
|
||||||
|
case "double":
|
||||||
|
return lipgloss.DoubleBorder()
|
||||||
|
case "thick":
|
||||||
|
return lipgloss.ThickBorder()
|
||||||
|
default:
|
||||||
|
return lipgloss.RoundedBorder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compactPath(path string, mode string) string {
|
||||||
|
switch strings.ToLower(mode) {
|
||||||
|
case "full":
|
||||||
|
return path
|
||||||
|
case "smart":
|
||||||
|
return smartPath(path, 42)
|
||||||
|
default:
|
||||||
|
return vfs.SafeBase(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartPath(path string, maxWidth int) string {
|
||||||
|
if lipgloss.Width(path) <= maxWidth {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return truncateMiddle(path, maxWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncateMiddle(value string, maxWidth int) string {
|
||||||
|
if maxWidth <= 0 || lipgloss.Width(value) <= maxWidth {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if maxWidth <= 3 {
|
||||||
|
return trimToWidthRight(value, maxWidth)
|
||||||
|
}
|
||||||
|
left := maxWidth/2 - 1
|
||||||
|
right := maxWidth - left - 1
|
||||||
|
if left < 1 {
|
||||||
|
left = 1
|
||||||
|
}
|
||||||
|
if right < 1 {
|
||||||
|
right = 1
|
||||||
|
}
|
||||||
|
return trimToWidthRight(value, left) + "…" + trimToWidthLeft(value, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncateRight(value string, maxWidth int) string {
|
||||||
|
if maxWidth <= 0 || lipgloss.Width(value) <= maxWidth {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if maxWidth == 1 {
|
||||||
|
return trimToWidthRight(value, 1)
|
||||||
|
}
|
||||||
|
return trimToWidthRight(value, maxWidth-1) + "…"
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncateForColumn(value string, maxWidth int, alignRight bool) string {
|
||||||
|
if lipgloss.Width(value) <= maxWidth {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if alignRight {
|
||||||
|
if maxWidth <= 1 {
|
||||||
|
return trimToWidthLeft(value, 1)
|
||||||
|
}
|
||||||
|
return "…" + trimToWidthLeft(value, maxWidth-1)
|
||||||
|
}
|
||||||
|
return truncateRight(value, maxWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimToWidthRight(value string, maxWidth int) string {
|
||||||
|
if maxWidth <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
width := 0
|
||||||
|
var builder strings.Builder
|
||||||
|
for _, r := range value {
|
||||||
|
rw := lipgloss.Width(string(r))
|
||||||
|
if width+rw > maxWidth {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
builder.WriteRune(r)
|
||||||
|
width += rw
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimToWidthLeft(value string, maxWidth int) string {
|
||||||
|
if maxWidth <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
runes := []rune(value)
|
||||||
|
width := 0
|
||||||
|
start := len(runes)
|
||||||
|
for i := len(runes) - 1; i >= 0; i-- {
|
||||||
|
rw := lipgloss.Width(string(runes[i]))
|
||||||
|
if width+rw > maxWidth {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
width += rw
|
||||||
|
start = i
|
||||||
|
}
|
||||||
|
return string(runes[start:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func entryIcon(entry vfs.Entry, useNerdIcons bool) string {
|
||||||
|
if !useNerdIcons {
|
||||||
|
switch entry.Category() {
|
||||||
|
case "parent":
|
||||||
|
return "<-"
|
||||||
|
case "remote":
|
||||||
|
return "[SV]"
|
||||||
|
case "directory":
|
||||||
|
return "[D]"
|
||||||
|
case "config":
|
||||||
|
return "[C]"
|
||||||
|
case "text":
|
||||||
|
return "[T]"
|
||||||
|
case "image":
|
||||||
|
return "[I]"
|
||||||
|
case "executable":
|
||||||
|
return "[X]"
|
||||||
|
case "archive":
|
||||||
|
return "[A]"
|
||||||
|
default:
|
||||||
|
return "[F]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch entry.Category() {
|
||||||
|
case "parent":
|
||||||
|
return "↩"
|
||||||
|
case "remote":
|
||||||
|
return ""
|
||||||
|
case "directory":
|
||||||
|
return ""
|
||||||
|
case "config":
|
||||||
|
return ""
|
||||||
|
case "text":
|
||||||
|
return ""
|
||||||
|
case "image":
|
||||||
|
return ""
|
||||||
|
case "executable":
|
||||||
|
return ""
|
||||||
|
case "archive":
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func entryColor(entry vfs.Entry, palette theme.Palette) lipgloss.Color {
|
||||||
|
switch entry.Category() {
|
||||||
|
case "remote":
|
||||||
|
return palette.ExecFile
|
||||||
|
case "directory", "parent":
|
||||||
|
return palette.Folder
|
||||||
|
case "config":
|
||||||
|
return palette.ConfigFile
|
||||||
|
case "text":
|
||||||
|
return palette.TextFile
|
||||||
|
case "image":
|
||||||
|
return palette.ImageFile
|
||||||
|
case "executable":
|
||||||
|
return palette.ExecFile
|
||||||
|
default:
|
||||||
|
return palette.BinaryFile
|
||||||
|
}
|
||||||
|
}
|
||||||
244
src/vcom-0.2.5/internal/ui/ssh.go
Normal file
244
src/vcom-0.2.5/internal/ui/ssh.go
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
vfs "vcom/internal/fs"
|
||||||
|
"vcom/internal/fs/remote"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isRemoteHostEntry returns true if the entry represents an SSH host.
|
||||||
|
func isRemoteHostEntry(entry vfs.Entry) bool {
|
||||||
|
return entry.IsRemote
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRemoteAddHostEntry returns true if the entry is the "Add host" special item.
|
||||||
|
func isRemoteAddHostEntry(entry vfs.Entry) bool {
|
||||||
|
return entry.IsRemote && entry.Name == "+ Add host"
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSSHHostEntries creates virtual directory entries for SSH hosts.
|
||||||
|
// connectedHosts optionally specifies which hosts have active connections
|
||||||
|
// (host name -> true). When non-nil, entries show a connection status prefix.
|
||||||
|
func buildSSHHostEntries(store *remote.HostStore, connectedHosts map[string]bool) []vfs.Entry {
|
||||||
|
hosts := store.AllHosts()
|
||||||
|
entries := make([]vfs.Entry, 0, len(hosts)+1)
|
||||||
|
|
||||||
|
// Find the longest host name so we can pad them all to the same width,
|
||||||
|
// making the (user@host) suffix align vertically across entries.
|
||||||
|
maxNameLen := 0
|
||||||
|
for _, h := range hosts {
|
||||||
|
if l := len(h.Name); l > maxNameLen {
|
||||||
|
maxNameLen = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range hosts {
|
||||||
|
isConnected := connectedHosts != nil && connectedHosts[h.Name]
|
||||||
|
|
||||||
|
addr := h.HostName
|
||||||
|
if h.Port != "" && h.Port != "22" {
|
||||||
|
addr = fmt.Sprintf("%s:%s", addr, h.Port)
|
||||||
|
}
|
||||||
|
suffix := ""
|
||||||
|
if h.User != "" {
|
||||||
|
suffix = fmt.Sprintf(" (%s@%s)", h.User, addr)
|
||||||
|
} else {
|
||||||
|
suffix = fmt.Sprintf(" (%s)", addr)
|
||||||
|
}
|
||||||
|
paddedName := h.Name + strings.Repeat(" ", maxNameLen-len(h.Name))
|
||||||
|
|
||||||
|
entries = append(entries, vfs.Entry{
|
||||||
|
Name: paddedName + suffix,
|
||||||
|
Path: "ssh://" + h.Name,
|
||||||
|
IsDir: true,
|
||||||
|
IsRemote: true,
|
||||||
|
Connected: isConnected,
|
||||||
|
RemoteHostName: h.Name,
|
||||||
|
Mode: 0o755,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add "Add host" entry at the bottom
|
||||||
|
entries = append(entries, vfs.Entry{
|
||||||
|
Name: "+ Add host",
|
||||||
|
Path: "ssh://add-host",
|
||||||
|
IsDir: false,
|
||||||
|
IsRemote: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHConnectDialogInputs creates the text input fields for the SSH connect dialog.
|
||||||
|
func SSHConnectDialogInputs() []textinput.Model {
|
||||||
|
fields := make([]textinput.Model, 5)
|
||||||
|
|
||||||
|
placeholders := []string{"my-server", "192.168.1.100", "22", "root", ""}
|
||||||
|
|
||||||
|
for i := range fields {
|
||||||
|
ti := textinput.New()
|
||||||
|
ti.Placeholder = placeholders[i]
|
||||||
|
ti.CharLimit = 128
|
||||||
|
ti.Width = 40
|
||||||
|
if i == 4 {
|
||||||
|
ti.EchoMode = textinput.EchoPassword
|
||||||
|
ti.EchoCharacter = '•'
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
ti.Focus()
|
||||||
|
}
|
||||||
|
fields[i] = ti
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// sshDialogLabel returns the label for an SSH dialog field at the given index.
|
||||||
|
func sshDialogLabel(index int) string {
|
||||||
|
lbls := []string{"Name:", "Hostname/IP:", "Port:", "User:", "Password:"}
|
||||||
|
if index >= 0 && index < len(lbls) {
|
||||||
|
return lbls[index]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// sshDialogValue returns the value for an SSH dialog field.
|
||||||
|
func sshDialogValue(inputs []textinput.Model, index int) string {
|
||||||
|
if index >= 0 && index < len(inputs) {
|
||||||
|
return inputs[index].Value()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSSHHostFromDialog creates an SSHHost from dialog input values.
|
||||||
|
func buildSSHHostFromDialog(inputs []textinput.Model) remote.SSHHost {
|
||||||
|
host := remote.SSHHost{
|
||||||
|
Name: strings.TrimSpace(sshDialogValue(inputs, 0)),
|
||||||
|
HostName: strings.TrimSpace(sshDialogValue(inputs, 1)),
|
||||||
|
Port: strings.TrimSpace(sshDialogValue(inputs, 2)),
|
||||||
|
User: strings.TrimSpace(sshDialogValue(inputs, 3)),
|
||||||
|
Password: sshDialogValue(inputs, 4),
|
||||||
|
}
|
||||||
|
if host.Port == "" {
|
||||||
|
host.Port = "22"
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatSSHConnectHelp returns the help text for the SSH connect dialog.
|
||||||
|
func formatSSHConnectHelp() string {
|
||||||
|
return `Fields:
|
||||||
|
Name — alias for this host (required)
|
||||||
|
Hostname/IP — server address (required)
|
||||||
|
Port — SSH port, default 22
|
||||||
|
User — login username (required)
|
||||||
|
Password — password for password-based auth
|
||||||
|
|
||||||
|
Navigation: Tab / Shift+Tab between fields
|
||||||
|
Confirm: Enter
|
||||||
|
Close: Esc / q
|
||||||
|
Help: F1 / ?`
|
||||||
|
}
|
||||||
|
|
||||||
|
// remoteDirToEntries reads a remote directory via SFTP and converts to vfs.Entry slice.
|
||||||
|
func remoteDirToEntries(remotePath string, sshClient *remote.SSHClient) ([]vfs.Entry, error) {
|
||||||
|
fileInfos, err := sshClient.ReadDir(remotePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := make([]vfs.Entry, 0, len(fileInfos)+1)
|
||||||
|
|
||||||
|
// Add parent directory entry if not at root
|
||||||
|
if remotePath != "/" {
|
||||||
|
parent := path.Dir(remotePath)
|
||||||
|
if parent == "" {
|
||||||
|
parent = "/"
|
||||||
|
}
|
||||||
|
entries = append(entries, vfs.Entry{
|
||||||
|
Name: "..",
|
||||||
|
Path: parent,
|
||||||
|
IsDir: true,
|
||||||
|
IsParent: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, info := range fileInfos {
|
||||||
|
name := info.Name()
|
||||||
|
isDir := info.IsDir()
|
||||||
|
fullPath := path.Join(remotePath, name)
|
||||||
|
|
||||||
|
ext := ""
|
||||||
|
if idx := strings.LastIndex(name, "."); idx > 0 {
|
||||||
|
ext = strings.ToLower(name[idx+1:])
|
||||||
|
}
|
||||||
|
entry := vfs.Entry{
|
||||||
|
Name: name,
|
||||||
|
Path: fullPath,
|
||||||
|
Extension: ext,
|
||||||
|
Mode: info.Mode(),
|
||||||
|
Size: info.Size(),
|
||||||
|
ModifiedAt: info.ModTime(),
|
||||||
|
IsDir: isDir,
|
||||||
|
IsHidden: strings.HasPrefix(name, "."),
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = append(entries, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sshState holds SSH-related state for the model.
|
||||||
|
type sshState struct {
|
||||||
|
store *remote.HostStore
|
||||||
|
inputs []textinput.Model
|
||||||
|
inputFocus int
|
||||||
|
showHelp bool
|
||||||
|
|
||||||
|
// Connection test state
|
||||||
|
testingConn bool // true while an async connection test is in progress
|
||||||
|
cancelTest func() // call to abort a pending connection test
|
||||||
|
|
||||||
|
// connectedHosts tracks which host names have active SSH connections.
|
||||||
|
// Updated when connections are established or closed.
|
||||||
|
connectedHosts map[string]bool
|
||||||
|
|
||||||
|
// activeClients stores SSH clients for hosts whose connections are kept alive
|
||||||
|
// after returning to the host list. Clients are closed on full SSH mode exit.
|
||||||
|
activeClients map[string]*remote.SSHClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// cycleInput shifts focus between dialog inputs by delta (+1/-1).
|
||||||
|
func (s *sshState) cycleInput(delta int) {
|
||||||
|
if s == nil || len(s.inputs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.inputs[s.inputFocus].Blur()
|
||||||
|
s.inputFocus = (s.inputFocus + delta) % len(s.inputs)
|
||||||
|
if s.inputFocus < 0 {
|
||||||
|
s.inputFocus += len(s.inputs)
|
||||||
|
}
|
||||||
|
s.inputs[s.inputFocus].Focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSSHState creates a new sshState with a HostStore.
|
||||||
|
func newSSHState() (*sshState, error) {
|
||||||
|
store, err := remote.NewHostStore()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sshState{
|
||||||
|
store: store,
|
||||||
|
inputs: SSHConnectDialogInputs(),
|
||||||
|
inputFocus: 0,
|
||||||
|
activeClients: make(map[string]*remote.SSHClient),
|
||||||
|
showHelp: false,
|
||||||
|
connectedHosts: make(map[string]bool),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
497
src/vcom-0.2.5/plans/extended-preview-feature.md
Normal file
497
src/vcom-0.2.5/plans/extended-preview-feature.md
Normal file
|
|
@ -0,0 +1,497 @@
|
||||||
|
# Extended Preview — PDF, Audio, Video via External Utilities
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Add rich preview support for three new file categories by leveraging external CLI tools. If a tool is not installed, the file falls back to the existing binary-file display.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[BuildPreview entry] --> B{Is directory?}
|
||||||
|
B -->|Yes| C[buildDirectoryPreview]
|
||||||
|
B -->|No| D[Open file, read header]
|
||||||
|
D --> E{Detect image?}
|
||||||
|
E -->|Yes| F[PreviewKindImage]
|
||||||
|
E -->|No| G{Detect PDF ext?}
|
||||||
|
G -->|Yes| H{pdftotext available?}
|
||||||
|
H -->|Yes| I[PreviewKindPDF - extract text]
|
||||||
|
H -->|No| J[Fallback to binary]
|
||||||
|
G -->|No| K{Detect audio ext?}
|
||||||
|
K -->|Yes| L{ffprobe available?}
|
||||||
|
L -->|Yes| M[PreviewKindAudio - show metadata]
|
||||||
|
L -->|No| N[Fallback to binary]
|
||||||
|
K -->|No| O{Detect video ext?}
|
||||||
|
O -->|Yes| P{ffprobe available?}
|
||||||
|
P -->|Yes| Q[PreviewKindVideo - show metadata]
|
||||||
|
P -->|No| R[Fallback to binary]
|
||||||
|
O -->|No| S[Is binary sample?]
|
||||||
|
S -->|Yes| T[PreviewKindBinary]
|
||||||
|
S -->|No| U[PreviewKindText + syntax highlight]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1. New PreviewKind Constants
|
||||||
|
|
||||||
|
File: [`internal/fs/preview.go`](internal/fs/preview.go) (around line 28-35)
|
||||||
|
|
||||||
|
Add three new constants to the `PreviewKind` type:
|
||||||
|
- `PreviewKindPDF PreviewKind = "pdf"`
|
||||||
|
- `PreviewKindAudio PreviewKind = "audio"`
|
||||||
|
- `PreviewKindVideo PreviewKind = "video"`
|
||||||
|
|
||||||
|
## 2. Extended Metadata Struct
|
||||||
|
|
||||||
|
File: [`internal/fs/preview.go`](internal/fs/preview.go) — `Metadata` struct (line 37-48)
|
||||||
|
|
||||||
|
Add new optional fields:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Metadata struct {
|
||||||
|
// ... existing fields ...
|
||||||
|
|
||||||
|
// Extended preview metadata
|
||||||
|
Duration string // audio/video duration (e.g. "3:42")
|
||||||
|
Bitrate string // audio/video bitrate (e.g. "320 kbps")
|
||||||
|
AudioCodec string // audio codec (e.g. "aac", "mp3")
|
||||||
|
VideoCodec string // video codec (e.g. "h264", "vp9")
|
||||||
|
SampleRate string // audio sample rate (e.g. "44100 Hz")
|
||||||
|
Channels string // audio channels (e.g. "stereo")
|
||||||
|
PageCount string // PDF page count
|
||||||
|
Dimensions string // video dimensions (e.g. "1920x1080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. New Extension Maps
|
||||||
|
|
||||||
|
File: [`internal/fs/entry.go`](internal/fs/entry.go) (around line 55-63)
|
||||||
|
|
||||||
|
Add three new extension sets:
|
||||||
|
|
||||||
|
```go
|
||||||
|
pdfExtensions = map[string]struct{}{
|
||||||
|
"pdf": {},
|
||||||
|
}
|
||||||
|
audioExtensions = map[string]struct{}{
|
||||||
|
"mp3": {}, "flac": {}, "ogg": {}, "opus": {}, "wav": {},
|
||||||
|
"aac": {}, "m4a": {}, "wma": {}, "dsf": {}, "ape": {},
|
||||||
|
}
|
||||||
|
videoExtensions = map[string]struct{}{
|
||||||
|
"mp4": {}, "mkv": {}, "mov": {}, "avi": {}, "webm": {},
|
||||||
|
"m4v": {}, "wmv": {}, "flv": {}, "ts": {}, "mts": {},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. New Categories in `Category()` Method
|
||||||
|
|
||||||
|
File: [`internal/fs/entry.go`](internal/fs/entry.go) — `Category()` method (line 102-123)
|
||||||
|
|
||||||
|
Add three new cases in the switch statement (before `default`):
|
||||||
|
|
||||||
|
```go
|
||||||
|
case hasExt(pdfExtensions, e.Extension):
|
||||||
|
return "pdf"
|
||||||
|
case hasExt(audioExtensions, e.Extension):
|
||||||
|
return "audio"
|
||||||
|
case hasExt(videoExtensions, e.Extension):
|
||||||
|
return "video"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. External Utility Detection
|
||||||
|
|
||||||
|
File: [`internal/fs/preview.go`](internal/fs/preview.go) — new helper functions
|
||||||
|
|
||||||
|
```go
|
||||||
|
func findTool(name string) string {
|
||||||
|
path, err := exec.LookPath(name)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `"os/exec"` to imports.
|
||||||
|
|
||||||
|
## 6. Modified `BuildPreview()` Flow
|
||||||
|
|
||||||
|
File: [`internal/fs/preview.go`](internal/fs/preview.go) — `BuildPreview()` (line 73)
|
||||||
|
|
||||||
|
Insert the new checks after the image detection block (after line 131) and before the `IsBinarySample()` check:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// PDF preview via pdftotext
|
||||||
|
if hasExt(pdfExtensions, entry.Extension) {
|
||||||
|
return buildPDFPreview(entry, options, preview)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio preview via ffprobe
|
||||||
|
if hasExt(audioExtensions, entry.Extension) {
|
||||||
|
return buildAudioPreview(entry, options, preview)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video preview via ffprobe
|
||||||
|
if hasExt(videoExtensions, entry.Extension) {
|
||||||
|
return buildVideoPreview(entry, options, preview)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This placement ensures:
|
||||||
|
- Images are still detected by magic bytes (works for any extension)
|
||||||
|
- PDF/audio/video get their custom handling
|
||||||
|
- Files that don't match any category fall through to the existing binary/text detection
|
||||||
|
|
||||||
|
## 7. New Preview Builder Functions
|
||||||
|
|
||||||
|
File: [`internal/fs/preview.go`](internal/fs/preview.go) — new functions
|
||||||
|
|
||||||
|
### 7a. `buildPDFPreview()`
|
||||||
|
|
||||||
|
```go
|
||||||
|
func buildPDFPreview(entry Entry, options PreviewOptions, base Preview) Preview {
|
||||||
|
pdftotext := findTool("pdftotext")
|
||||||
|
if pdftotext == "" {
|
||||||
|
base.Kind = PreviewKindBinary
|
||||||
|
base.Body = "PDF file detected.\nInstall poppler-utils (pdftotext) for text preview."
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract text
|
||||||
|
cmd := exec.Command(pdftotext, "-layout", "-nopgbrk", entry.Path, "-")
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("pdftotext error:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
text := string(out)
|
||||||
|
if len(text) > int(options.MaxPreviewBytes) {
|
||||||
|
text = text[:options.MaxPreviewBytes]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get page count via pdfinfo if available
|
||||||
|
pdfinfo := findTool("pdfinfo")
|
||||||
|
if pdfinfo != "" {
|
||||||
|
infoCmd := exec.Command(pdfinfo, entry.Path)
|
||||||
|
if infoOut, err := infoCmd.Output(); err == nil {
|
||||||
|
for _, line := range strings.Split(string(infoOut), "\n") {
|
||||||
|
if strings.HasPrefix(strings.ToLower(line), "pages:") {
|
||||||
|
base.Metadata.PageCount = strings.TrimSpace(strings.TrimPrefix(line, "Pages:"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Kind = PreviewKindPDF
|
||||||
|
base.PlainBody = text
|
||||||
|
base.Body = highlightText(entry.Path, text, options.ThemeName)
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7b. `buildAudioPreview()`
|
||||||
|
|
||||||
|
```go
|
||||||
|
func buildAudioPreview(entry Entry, options PreviewOptions, base Preview) Preview {
|
||||||
|
ffprobe := findTool("ffprobe")
|
||||||
|
if ffprobe == "" {
|
||||||
|
base.Kind = PreviewKindBinary
|
||||||
|
base.Body = "Audio file detected.\nInstall ffmpeg (ffprobe) for metadata preview."
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use ffprobe to extract metadata as JSON
|
||||||
|
cmd := exec.Command(ffprobe,
|
||||||
|
"-v", "quiet",
|
||||||
|
"-print_format", "json",
|
||||||
|
"-show_format",
|
||||||
|
"-show_streams",
|
||||||
|
entry.Path,
|
||||||
|
)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("ffprobe error:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
var info struct {
|
||||||
|
Format struct {
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
Bitrate string `json:"bit_rate"`
|
||||||
|
} `json:"format"`
|
||||||
|
Streams []struct {
|
||||||
|
CodecType string `json:"codec_type"`
|
||||||
|
CodecName string `json:"codec_name"`
|
||||||
|
SampleRate string `json:"sample_rate"`
|
||||||
|
Channels int `json:"channels"`
|
||||||
|
} `json:"streams"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(out, &info); err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("Could not parse ffprobe output:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format duration
|
||||||
|
if info.Format.Duration != "" {
|
||||||
|
if secs, err := strconv.ParseFloat(info.Format.Duration, 64); err == nil {
|
||||||
|
mins := int(secs) / 60
|
||||||
|
secsRem := int(secs) % 60
|
||||||
|
base.Metadata.Duration = fmt.Sprintf("%d:%02d", mins, secsRem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Format.Bitrate != "" {
|
||||||
|
if bps, err := strconv.ParseInt(info.Format.Bitrate, 10, 64); err == nil {
|
||||||
|
base.Metadata.Bitrate = fmt.Sprintf("%d kbps", bps/1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stream := range info.Streams {
|
||||||
|
if stream.CodecType == "audio" {
|
||||||
|
base.Metadata.AudioCodec = stream.CodecName
|
||||||
|
if stream.SampleRate != "" {
|
||||||
|
base.Metadata.SampleRate = stream.SampleRate + " Hz"
|
||||||
|
}
|
||||||
|
switch stream.Channels {
|
||||||
|
case 1:
|
||||||
|
base.Metadata.Channels = "mono"
|
||||||
|
case 2:
|
||||||
|
base.Metadata.Channels = "stereo"
|
||||||
|
case 6:
|
||||||
|
base.Metadata.Channels = "5.1"
|
||||||
|
case 8:
|
||||||
|
base.Metadata.Channels = "7.1"
|
||||||
|
default:
|
||||||
|
base.Metadata.Channels = fmt.Sprintf("%d ch", stream.Channels)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a rich metadata body
|
||||||
|
var lines []string
|
||||||
|
lines = append(lines, fmt.Sprintf(" Duration: %s", base.Metadata.Duration))
|
||||||
|
if base.Metadata.Bitrate != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Bitrate: %s", base.Metadata.Bitrate))
|
||||||
|
}
|
||||||
|
if base.Metadata.AudioCodec != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Codec: %s", base.Metadata.AudioCodec))
|
||||||
|
}
|
||||||
|
if base.Metadata.SampleRate != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Rate: %s", base.Metadata.SampleRate))
|
||||||
|
}
|
||||||
|
if base.Metadata.Channels != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Channels: %s", base.Metadata.Channels))
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Kind = PreviewKindAudio
|
||||||
|
base.Body = fmt.Sprintf("🎵 Audio File\n\n%s", strings.Join(lines, "\n"))
|
||||||
|
base.PlainBody = fmt.Sprintf("Audio File\n\nDuration: %s\nBitrate: %s\nCodec: %s\nRate: %s\nChannels: %s",
|
||||||
|
base.Metadata.Duration, base.Metadata.Bitrate, base.Metadata.AudioCodec,
|
||||||
|
base.Metadata.SampleRate, base.Metadata.Channels)
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7c. `buildVideoPreview()`
|
||||||
|
|
||||||
|
```go
|
||||||
|
func buildVideoPreview(entry Entry, options PreviewOptions, base Preview) Preview {
|
||||||
|
ffprobe := findTool("ffprobe")
|
||||||
|
if ffprobe == "" {
|
||||||
|
base.Kind = PreviewKindBinary
|
||||||
|
base.Body = "Video file detected.\nInstall ffmpeg (ffprobe) for metadata preview."
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use ffprobe to extract metadata as JSON
|
||||||
|
cmd := exec.Command(ffprobe,
|
||||||
|
"-v", "quiet",
|
||||||
|
"-print_format", "json",
|
||||||
|
"-show_format",
|
||||||
|
"-show_streams",
|
||||||
|
entry.Path,
|
||||||
|
)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("ffprobe error:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
var info struct {
|
||||||
|
Format struct {
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
Bitrate string `json:"bit_rate"`
|
||||||
|
} `json:"format"`
|
||||||
|
Streams []struct {
|
||||||
|
CodecType string `json:"codec_type"`
|
||||||
|
CodecName string `json:"codec_name"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
} `json:"streams"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(out, &info); err != nil {
|
||||||
|
base.Kind = PreviewKindError
|
||||||
|
base.Body = fmt.Sprintf("Could not parse ffprobe output:\n\n%s", err)
|
||||||
|
base.PlainBody = base.Body
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format duration
|
||||||
|
if info.Format.Duration != "" {
|
||||||
|
if secs, err := strconv.ParseFloat(info.Format.Duration, 64); err == nil {
|
||||||
|
hrs := int(secs) / 3600
|
||||||
|
mins := (int(secs) % 3600) / 60
|
||||||
|
secsRem := int(secs) % 60
|
||||||
|
if hrs > 0 {
|
||||||
|
base.Metadata.Duration = fmt.Sprintf("%d:%02d:%02d", hrs, mins, secsRem)
|
||||||
|
} else {
|
||||||
|
base.Metadata.Duration = fmt.Sprintf("%d:%02d", mins, secsRem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Format.Bitrate != "" {
|
||||||
|
if bps, err := strconv.ParseInt(info.Format.Bitrate, 10, 64); err == nil {
|
||||||
|
base.Metadata.Bitrate = fmt.Sprintf("%d kbps", bps/1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stream := range info.Streams {
|
||||||
|
switch stream.CodecType {
|
||||||
|
case "video":
|
||||||
|
base.Metadata.VideoCodec = stream.CodecName
|
||||||
|
if stream.Width > 0 && stream.Height > 0 {
|
||||||
|
base.Metadata.Dimensions = fmt.Sprintf("%dx%d", stream.Width, stream.Height)
|
||||||
|
}
|
||||||
|
case "audio":
|
||||||
|
if base.Metadata.AudioCodec == "" {
|
||||||
|
base.Metadata.AudioCodec = stream.CodecName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a rich metadata body
|
||||||
|
var lines []string
|
||||||
|
lines = append(lines, fmt.Sprintf(" Duration: %s", base.Metadata.Duration))
|
||||||
|
if base.Metadata.Bitrate != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Bitrate: %s", base.Metadata.Bitrate))
|
||||||
|
}
|
||||||
|
if base.Metadata.VideoCodec != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Video: %s", base.Metadata.VideoCodec))
|
||||||
|
}
|
||||||
|
if base.Metadata.Dimensions != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Resolution: %s", base.Metadata.Dimensions))
|
||||||
|
}
|
||||||
|
if base.Metadata.AudioCodec != "" {
|
||||||
|
lines = append(lines, fmt.Sprintf(" Audio: %s", base.Metadata.AudioCodec))
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Kind = PreviewKindVideo
|
||||||
|
base.Body = fmt.Sprintf("🎬 Video File\n\n%s", strings.Join(lines, "\n"))
|
||||||
|
base.PlainBody = fmt.Sprintf("Video File\n\nDuration: %s\nBitrate: %s\nVideo: %s\nResolution: %s\nAudio: %s",
|
||||||
|
base.Metadata.Duration, base.Metadata.Bitrate, base.Metadata.VideoCodec,
|
||||||
|
base.Metadata.Dimensions, base.Metadata.AudioCodec)
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. UI: Update `renderMetadata()`
|
||||||
|
|
||||||
|
File: [`internal/ui/model.go`](internal/ui/model.go) — `renderMetadata()` (line 2714)
|
||||||
|
|
||||||
|
Add new metadata fields to the right column after the image info block:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// After: if meta.ImageFormat != "" { ... }
|
||||||
|
|
||||||
|
if meta.Duration != "" {
|
||||||
|
rightRows = append(rightRows, fmt.Sprintf("duration: %s", meta.Duration))
|
||||||
|
}
|
||||||
|
if meta.Bitrate != "" {
|
||||||
|
rightRows = append(rightRows, fmt.Sprintf("bitrate: %s", meta.Bitrate))
|
||||||
|
}
|
||||||
|
if meta.AudioCodec != "" {
|
||||||
|
rightRows = append(rightRows, fmt.Sprintf("audio: %s", meta.AudioCodec))
|
||||||
|
}
|
||||||
|
if meta.VideoCodec != "" {
|
||||||
|
rightRows = append(rightRows, fmt.Sprintf("video: %s", meta.VideoCodec))
|
||||||
|
}
|
||||||
|
if meta.Dimensions != "" {
|
||||||
|
rightRows = append(rightRows, fmt.Sprintf("resolution: %s", meta.Dimensions))
|
||||||
|
}
|
||||||
|
if meta.SampleRate != "" {
|
||||||
|
rightRows = append(rightRows, fmt.Sprintf("rate: %s", meta.SampleRate))
|
||||||
|
}
|
||||||
|
if meta.Channels != "" {
|
||||||
|
rightRows = append(rightRows, fmt.Sprintf("channels: %s", meta.Channels))
|
||||||
|
}
|
||||||
|
if meta.PageCount != "" {
|
||||||
|
rightRows = append(rightRows, fmt.Sprintf("pages: %s", meta.PageCount))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. UI: Update `previewIcon()`
|
||||||
|
|
||||||
|
File: [`internal/ui/model.go`](internal/ui/model.go) — `previewIcon()` (line 3545)
|
||||||
|
|
||||||
|
Add new Nerd Font icons:
|
||||||
|
|
||||||
|
```go
|
||||||
|
case vfs.PreviewKindPDF:
|
||||||
|
return "" // nf-oct-file-pdf
|
||||||
|
case vfs.PreviewKindAudio:
|
||||||
|
return "" // nf-custom-audio-file
|
||||||
|
case vfs.PreviewKindVideo:
|
||||||
|
return "" // nf-custom-video-file
|
||||||
|
```
|
||||||
|
|
||||||
|
And ASCII fallback:
|
||||||
|
```go
|
||||||
|
case "pdf":
|
||||||
|
return "[P]"
|
||||||
|
case "audio":
|
||||||
|
return "[A]"
|
||||||
|
case "video":
|
||||||
|
return "[V]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. UI: Update `syncImageOverlay()`
|
||||||
|
|
||||||
|
File: [`internal/ui/model.go`](internal/ui/model.go) — `syncImageOverlay()` (line 4225)
|
||||||
|
|
||||||
|
The overlay should only show for `PreviewKindImage`, which it already checks. No change needed — video files should NOT use the image overlay since we don't have video thumbnail support yet.
|
||||||
|
|
||||||
|
## 11. Files Changed Summary
|
||||||
|
|
||||||
|
| File | Changes |
|
||||||
|
|------|---------|
|
||||||
|
| [`internal/fs/entry.go`](internal/fs/entry.go) | Add `pdfExtensions`, `audioExtensions`, `videoExtensions` maps; update `Category()` method |
|
||||||
|
| [`internal/fs/preview.go`](internal/fs/preview.go) | Add `PreviewKindPDF/Audio/Video` constants; add fields to `Metadata`; add `findTool()`, `buildPDFPreview()`, `buildAudioPreview()`, `buildVideoPreview()`; modify `BuildPreview()` insertion point; add `"os/exec"`, `"encoding/json"` imports |
|
||||||
|
| [`internal/ui/model.go`](internal/ui/model.go) | Update `renderMetadata()` with new fields; update `previewIcon()` with new icons |
|
||||||
|
|
||||||
|
## 12. Edge Cases & Considerations
|
||||||
|
|
||||||
|
- **Missing external tool**: If `pdftotext` or `ffprobe` is not installed, the user sees a helpful message telling them which package to install, and the preview falls back to `PreviewKindBinary` (same as before).
|
||||||
|
- **Large PDFs**: Text extraction respects `MaxPreviewBytes` limit — truncated output is still highlighted.
|
||||||
|
- **Corrupt files**: `ffprobe` and `pdftotext` handle errors gracefully; any non-zero exit returns `PreviewKindError` with the error message.
|
||||||
|
- **Performance**: External processes are launched synchronously in the preview command (which already runs in a goroutine via `loadPreviewCmd()`), so the UI remains responsive.
|
||||||
|
- **No new dependencies**: All tools are invoked via `os/exec` (stdlib). No Go modules needed.
|
||||||
|
|
||||||
|
## 13. Test Plan
|
||||||
|
|
||||||
|
1. Open `test.pdf` — verify text is extracted and syntax-highlighted, page count shown in metadata
|
||||||
|
2. Open `test.mp3` — verify duration, bitrate, codec, sample rate, channels shown
|
||||||
|
3. Open `test.flac` — same as above
|
||||||
|
4. Open `test.mp4` — verify duration, bitrate, video codec, resolution, audio codec shown
|
||||||
|
5. Open PDF/audio/video on a system without `pdftotext`/`ffprobe` — verify fallback message
|
||||||
|
6. Verify `go build ./...` and `go vet ./...` pass
|
||||||
44
src/vcom-0.2.5/plans/feature-roadmap.md
Normal file
44
src/vcom-0.2.5/plans/feature-roadmap.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Feature Roadmap — vcom
|
||||||
|
|
||||||
|
## Выбранные фичи (по приоритету)
|
||||||
|
|
||||||
|
### 1. Поиск/фильтрация файлов (`/`)
|
||||||
|
- [ ] **Filter mode**: при нажатии `/` открывается текстовый инпут внизу экрана (поверх footer, как модальное окно)
|
||||||
|
- [ ] Фильтрация `[]Entry` в активной панели на лету по `strings.Contains`/fuzzy-match
|
||||||
|
- [ ] Подсветка совпадений в строках (изменить `renderEntryRow` — передать query, подсветить matched part)
|
||||||
|
- [ ] `Esc` — выход из filter mode, восстановление полного списка
|
||||||
|
- [ ] `Enter` — зафиксировать фильтр (оставить отфильтрованный список), выход из filter mode
|
||||||
|
- [ ] При смене директории фильтр сбрасывается
|
||||||
|
|
||||||
|
### 2. Bulk rename (массовое переименование)
|
||||||
|
- [ ] Выделить файлы (`Shift+↑/↓`), нажать `Ctrl+R` (новая клавиша)
|
||||||
|
- [ ] Модальное окно с текстовым полем для паттерна: `prefix_%N.ext`
|
||||||
|
- [ ] Превью результата (старое имя → новое имя)
|
||||||
|
- [ ] Выполнить rename для всех выделенных
|
||||||
|
|
||||||
|
### 3. Корзина (trash support)
|
||||||
|
- [ ] `Delete/F8` — перемещать в `~/.local/share/Trash/` по freedesktop spec
|
||||||
|
- [ ] `Shift+Delete` —永久ное удаление (как сейчас)
|
||||||
|
- [ ] `browser.confirm_delete` применяется к永久ному удалению
|
||||||
|
- [ ] Новая опция конфига: `behavior.use_trash = true` (default: true)
|
||||||
|
|
||||||
|
### 4. Directory history (назад/вперед)
|
||||||
|
- [ ] `[]string` стек истории на каждую панель
|
||||||
|
- [ ] `Alt+←` / `Alt+→` — навигация назад/вперед
|
||||||
|
- [ ] При переходе в новую директорию (Enter, Backspace, клик) — push в history
|
||||||
|
- [ ] При навигации по истории — не создавать новые записи
|
||||||
|
|
||||||
|
### 5. Расширенный превью форматов
|
||||||
|
- [ ] PDF — извлечение текста через `pdftotext` (если доступен)
|
||||||
|
- [ ] Аудио — метаданные через `ffprobe` (битрейт, длительность, кодек)
|
||||||
|
- [ ] Видео — метаданные + превью через `ffmpegthumbnailer`/`ffprobe`
|
||||||
|
- [ ] Fallback если утилита не установлена
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Процесс
|
||||||
|
|
||||||
|
1. Каждая фича реализуется в отдельной ветке `feature/N-имя`
|
||||||
|
2. После реализации — commit + push
|
||||||
|
3. После апрува — merge в main
|
||||||
|
4. Порядок: 1 → 4 → 3 → 2 → 5 (от простого к сложному)
|
||||||
88
src/vcom-0.2.5/plans/mirror-and-cursor-memory.md
Normal file
88
src/vcom-0.2.5/plans/mirror-and-cursor-memory.md
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
# Feature Plan: Mirror Pane & Per-Directory Cursor Memory
|
||||||
|
|
||||||
|
## Feature 1: Mirror active pane directory to opposite pane
|
||||||
|
|
||||||
|
### Keybinding research
|
||||||
|
- **Midnight Commander (MC)**: `Alt-i` — makes the other panel equal to current directory
|
||||||
|
- **Total Commander**: `Ctrl-PgDn` opens directory under cursor in opposite panel
|
||||||
|
- **Far Manager**: `Ctrl-Left/Right` — opens in opposite panel
|
||||||
|
|
||||||
|
**Recommendation**: Bind to `p` (for "Pane"). `Ctrl-i` sends the same byte as `Tab` (ASCII 0x09) so it conflicts with Switch. `p` is free, accessible, and "Pane" reflects the meaning.
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
1. Read active pane's current path (including remote mount state)
|
||||||
|
2. Apply that path to the passive (opposite) pane
|
||||||
|
3. If active is in a remote mount, also clone the remote mount stack to the passive pane
|
||||||
|
4. Reload both panes to reflect the change
|
||||||
|
|
||||||
|
### Code changes
|
||||||
|
|
||||||
|
#### [`internal/ui/keymap.go`](internal/ui/keymap.go)
|
||||||
|
- Add `Mirror` key binding field to `KeyMap` struct
|
||||||
|
- Bind to `"p"` with help text `"p"` / `"mirror pane"`
|
||||||
|
|
||||||
|
#### [`internal/ui/model.go`](internal/ui/model.go)
|
||||||
|
- Add handler `handleMirrorPane()`:
|
||||||
|
1. Get active pane path and remote mount state
|
||||||
|
2. Switch passive pane to match (copy remote stack if applicable)
|
||||||
|
3. Reload passive pane (using `reloadPane` or `reloadRemotePane`)
|
||||||
|
4. Set status: `"Mirrored: <path>"`
|
||||||
|
- Add key match case before the `default` switch
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature 2: Per-directory cursor position memory (session scope)
|
||||||
|
|
||||||
|
### Current state
|
||||||
|
- `enterSelected()` saves `pane.Path` to history for back-nav, then reloads target dir with `selected.Name` as preserve key — but `selected.Name` is the name of the directory being entered, not a cursor anchor
|
||||||
|
- `goParent()` pops history and reloads parent using the child directory name as preserve — cursor lands on the directory we came from
|
||||||
|
- **Missing**: When navigating back into a previously-visited directory, there's no saved cursor position for it
|
||||||
|
|
||||||
|
### Design
|
||||||
|
Add a `cursorMemory` field to `BrowserPane` — a `map[string]string` mapping **directory path → last selected entry display name**.
|
||||||
|
|
||||||
|
This integrates cleanly with the existing `SetEntries(entries, preserveKey)` / `FindSelected()` infrastructure.
|
||||||
|
|
||||||
|
### Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
enterSelected() / enterRemoteDir():
|
||||||
|
1. Save to cursorMemory: pane.Path → selected entry's Name
|
||||||
|
2. Push history (existing)
|
||||||
|
3. Set path, reload (existing)
|
||||||
|
4. The reload already uses preserve=selected.Name for the new dir,
|
||||||
|
but cursorMemory will help when coming back later
|
||||||
|
|
||||||
|
reloadPane() / reloadRemotePane():
|
||||||
|
1. Try preserve key first (existing behavior)
|
||||||
|
2. If preserve is empty, check cursorMemory[pane.Path]
|
||||||
|
and use that as preserveKey instead
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code changes
|
||||||
|
|
||||||
|
#### [`internal/ui/pane.go`](internal/ui/pane.go)
|
||||||
|
- Add field to `BrowserPane`: `CursorMemory map[string]string`
|
||||||
|
- Method `SaveCursor(path string, name string)` — stores cursor position
|
||||||
|
- Method `LoadCursor(path string) string` — retrieves saved cursor
|
||||||
|
|
||||||
|
#### [`internal/ui/model.go`](internal/ui/model.go)
|
||||||
|
- In `enterSelected()` (line ~1886): after `pane.PushHistory(pane.Path)`, add `pane.SaveCursor(pane.Path, selected.Name)`
|
||||||
|
- In `enterRemoteDir()` (line ~5726): same save logic
|
||||||
|
- In `goParent()`: save cursor before navigating away (already partially done via history)
|
||||||
|
- In `reloadPane()` (line ~1672): after existing preserve logic, if no preserve key and directory has cursor memory, use it
|
||||||
|
- In `reloadRemotePane()` (line ~5535): same fallback
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files modified
|
||||||
|
| File | Changes |
|
||||||
|
|------|---------|
|
||||||
|
| `internal/ui/keymap.go` | Add `Mirror` field, binding `p` |
|
||||||
|
| `internal/ui/pane.go` | Add `CursorMemory` field + methods |
|
||||||
|
| `internal/ui/model.go` | Add `handleMirrorPane()`, save/restore cursor in navigation methods |
|
||||||
|
|
||||||
|
## Not changed
|
||||||
|
- `internal/fs/` — storage layer unaffected
|
||||||
|
- `internal/config/` — no config changes needed
|
||||||
|
- `internal/theme/` — no theme changes needed
|
||||||
193
src/vcom-0.2.5/plans/theme-selector-dialog.md
Normal file
193
src/vcom-0.2.5/plans/theme-selector-dialog.md
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
# Plan: Theme Selector Dialog with Live Preview
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
Replace the current `cycleTheme()` (simple cycle through themes on `t`) with a modal dialog that shows all themes, their base colors, supports live preview via Up/Down navigation, and commit/revert on Enter/Esc.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### 1. `internal/ui/model.go` — New modal kind
|
||||||
|
|
||||||
|
Add to `modalKind` const (around line 34):
|
||||||
|
```go
|
||||||
|
modalThemeSelect
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `internal/ui/model.go` — New theme selector state
|
||||||
|
|
||||||
|
Add new struct and field to `Model`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type themeSelectorState struct {
|
||||||
|
names []string // all theme names in order
|
||||||
|
cursor int // current cursor index in the list
|
||||||
|
original string // the theme name before opening dialog (for Esc revert)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Model struct {
|
||||||
|
// ... existing fields ...
|
||||||
|
themeSelector *themeSelectorState // nil when not in theme selector dialog
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. `internal/ui/model.go` — New handler `openThemeSelector()`
|
||||||
|
|
||||||
|
Replace the `cycleTheme()` call with `openThemeSelector()`:
|
||||||
|
|
||||||
|
1. Read current theme name from `m.cfg.UI.Theme`
|
||||||
|
2. Get all theme names via `theme.Names()`
|
||||||
|
3. Find current theme index in the list
|
||||||
|
4. Create `themeSelectorState` with names, cursor=current index, original=current theme
|
||||||
|
5. Set `m.modal.kind = modalThemeSelect`
|
||||||
|
6. Set modal title/body (body may be empty since list is rendered in `renderThemeSelectModal`)
|
||||||
|
|
||||||
|
The `t` key match (`case key.Matches(msg, m.keys.CycleTheme)`) now calls `m.openThemeSelector()` instead of `m.cycleTheme()`.
|
||||||
|
|
||||||
|
### 4. `internal/ui/model.go` — Modal key handling
|
||||||
|
|
||||||
|
Add a new case in `handleModalKey()` for `modalThemeSelect`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
case modalThemeSelect:
|
||||||
|
switch {
|
||||||
|
case msg.String() == "up" || msg.String() == "k":
|
||||||
|
// Move cursor up, clamp to 0, apply theme preview
|
||||||
|
m.themeSelector.cursor--
|
||||||
|
if m.themeSelector.cursor < 0 { m.themeSelector.cursor = 0 }
|
||||||
|
m.applyThemePreview(m.themeSelector.names[m.themeSelector.cursor])
|
||||||
|
return m, nil
|
||||||
|
case msg.String() == "down" || msg.String() == "j":
|
||||||
|
// Move cursor down, clamp to len-1, apply theme preview
|
||||||
|
m.themeSelector.cursor++
|
||||||
|
if m.themeSelector.cursor >= len(m.themeSelector.names) {
|
||||||
|
m.themeSelector.cursor = len(m.themeSelector.names) - 1
|
||||||
|
}
|
||||||
|
m.applyThemePreview(m.themeSelector.names[m.themeSelector.cursor])
|
||||||
|
return m, nil
|
||||||
|
case key.Matches(msg, m.keys.Confirm): // Enter
|
||||||
|
// Apply selected theme and save
|
||||||
|
selected := m.themeSelector.names[m.themeSelector.cursor]
|
||||||
|
m.finalizeTheme(selected)
|
||||||
|
m.themeSelector = nil
|
||||||
|
m.modal = modalState{}
|
||||||
|
m.status = fmt.Sprintf("Theme: %s", selected)
|
||||||
|
return m, nil
|
||||||
|
case msg.String() == "esc":
|
||||||
|
// Revert to original theme
|
||||||
|
m.applyThemePreview(m.themeSelector.original)
|
||||||
|
m.themeSelector = nil
|
||||||
|
m.modal = modalState{}
|
||||||
|
m.status = "Theme unchanged"
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. `internal/ui/model.go` — applyThemePreview helper
|
||||||
|
|
||||||
|
A new method that applies a theme palette to `m.palette` **without saving to config**:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (m *Model) applyThemePreview(name string) {
|
||||||
|
palette, err := theme.Resolve(name)
|
||||||
|
if err != nil {
|
||||||
|
return // silently ignore resolve errors during preview
|
||||||
|
}
|
||||||
|
m.palette = palette
|
||||||
|
// Don't update m.cfg.UI.Theme or save — that's only on Enter
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. `internal/ui/model.go` — finalizeTheme helper
|
||||||
|
|
||||||
|
A new method that applies the theme AND saves to config:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (m *Model) finalizeTheme(name string) {
|
||||||
|
palette, err := theme.Resolve(name)
|
||||||
|
if err != nil {
|
||||||
|
m.status = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.cfg.UI.Theme = name
|
||||||
|
m.palette = palette
|
||||||
|
savedPath, saveErr := config.Save(m.cfg, m.configPath)
|
||||||
|
if saveErr != nil {
|
||||||
|
m.status = fmt.Sprintf("Theme: %s (save failed: %v)", name, saveErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.configPath = savedPath
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. `internal/ui/model.go` — renderThemeSelectModal
|
||||||
|
|
||||||
|
Create a new render function `renderThemeSelectModal()`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func renderThemeSelectModal(m Model, palette theme.Palette, width int) string {
|
||||||
|
outerWidth := max(width, 8)
|
||||||
|
contentWidth := max(outerWidth-6, 1)
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
titleStyle := lipgloss.NewStyle()...
|
||||||
|
box := lipgloss.NewStyle()...
|
||||||
|
|
||||||
|
// Build theme list rows
|
||||||
|
lines := []string{titleStyle.Render("Select Theme"), spacer}
|
||||||
|
lines = append(lines, instructions)
|
||||||
|
lines = append(lines, spacer)
|
||||||
|
|
||||||
|
for i, name := range m.themeSelector.names {
|
||||||
|
resolved, err := theme.Resolve(name)
|
||||||
|
// skip if error
|
||||||
|
|
||||||
|
// Selection indicator + theme name
|
||||||
|
// Color swatches: Background, Panel, Accent, Text, Selection
|
||||||
|
// Highlight current item
|
||||||
|
}
|
||||||
|
|
||||||
|
return box.Render(strings.Join(lines, "\n"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each row shows:
|
||||||
|
- Cursor indicator (`▸` or ` `)
|
||||||
|
- Theme name
|
||||||
|
- Color swatches as small colored blocks: Background, Panel, Accent, Text, Selection
|
||||||
|
- Currently selected item is highlighted with `Selection` background color
|
||||||
|
|
||||||
|
### 8. `internal/ui/model.go` — Update View dispatch
|
||||||
|
|
||||||
|
Add a new condition in `renderModal()` (around line 3698) to dispatch to `renderThemeSelectModal` when `modalKind == modalThemeSelect`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if m.modal.kind == modalThemeSelect {
|
||||||
|
return renderThemeSelectModal(m, palette, width)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. `internal/ui/model.go` — Remove old cycleTheme (optional)
|
||||||
|
|
||||||
|
The old `cycleTheme()` method can be kept for backward compatibility or removed. The `t` key will now open the dialog instead.
|
||||||
|
|
||||||
|
### 10. Help dialog update (optional)
|
||||||
|
|
||||||
|
Update the help dialog to reflect the new behavior: `"t open theme selector"` instead of `"t cycle theme"`.
|
||||||
|
|
||||||
|
## Files modified
|
||||||
|
| File | Changes |
|
||||||
|
|------|---------|
|
||||||
|
| `internal/ui/model.go` | Add `modalThemeSelect` kind, `themeSelectorState` struct, `openThemeSelector()`, `applyThemePreview()`, `finalizeTheme()`, key handling in `handleModalKey`, `renderThemeSelectModal()`, update `renderModal()` dispatch |
|
||||||
|
| (none else) | Theme data, config saving, keymap — all remain unchanged |
|
||||||
|
|
||||||
|
## Key design points
|
||||||
|
1. **Live preview**: Every Up/Down key press instantly resolves the new theme palette and applies it to `m.palette`. The user sees the full UI change in real time.
|
||||||
|
2. **Safe revert**: On Esc, the original theme (saved in `themeSelectorState.original`) is restored.
|
||||||
|
3. **Config save only on Enter**: Only `finalizeTheme()` persists to config file.
|
||||||
|
4. **Color swatches**: Each theme row shows 5 small colored blocks (Background, Panel, Accent, Text, Selection) so the user can visually compare themes.
|
||||||
|
5. **Clean state management**: `themeSelector` is `nil` when the dialog is closed, making it easy to check state.
|
||||||
|
|
||||||
|
## Not changed
|
||||||
|
- `internal/theme/` — storage layer unchanged
|
||||||
|
- `internal/config/` — no config changes
|
||||||
|
- `internal/ui/keymap.go` — `t` binding unchanged, only behavior changes
|
||||||
|
- `internal/ui/pane.go` — no changes
|
||||||
37
src/vcom-0.2.5/scripts/build-deb.sh
Executable file
37
src/vcom-0.2.5/scripts/build-deb.sh
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "usage: $0 <version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
version="$1"
|
||||||
|
pkgroot="target/debian/pkgroot"
|
||||||
|
outdir="target/debian"
|
||||||
|
|
||||||
|
rm -rf "$pkgroot"
|
||||||
|
mkdir -p \
|
||||||
|
"$pkgroot/DEBIAN" \
|
||||||
|
"$pkgroot/usr/bin" \
|
||||||
|
"$pkgroot/usr/share/doc/vcom" \
|
||||||
|
"$pkgroot/usr/share/licenses/vcom"
|
||||||
|
|
||||||
|
install -Dm755 "target/release/vcom" "$pkgroot/usr/bin/vcom"
|
||||||
|
install -Dm644 "README.md" "$pkgroot/usr/share/doc/vcom/README.md"
|
||||||
|
install -Dm644 "vcom.toml" "$pkgroot/usr/share/doc/vcom/vcom.toml"
|
||||||
|
install -Dm644 "LICENSE" "$pkgroot/usr/share/licenses/vcom/LICENSE"
|
||||||
|
|
||||||
|
cat > "$pkgroot/DEBIAN/control" <<EOF
|
||||||
|
Package: vcom
|
||||||
|
Version: ${version}
|
||||||
|
Section: utils
|
||||||
|
Priority: optional
|
||||||
|
Architecture: amd64
|
||||||
|
Maintainer: Roman Vrubel <roman@vrubel.dev>
|
||||||
|
Depends: ueberzug | ueberzugpp
|
||||||
|
Description: Terminal file manager inspired by Midnight Commander
|
||||||
|
A two-pane terminal file manager with inspect mode and text previews.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
dpkg-deb --build "$pkgroot" "$outdir/vcom_${version}_amd64.deb"
|
||||||
45
src/vcom-0.2.5/vcom.toml
Normal file
45
src/vcom-0.2.5/vcom.toml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
[startup]
|
||||||
|
left_path = ''
|
||||||
|
right_path = ''
|
||||||
|
|
||||||
|
[ui]
|
||||||
|
app_title = 'vcom'
|
||||||
|
theme = 'github-dark'
|
||||||
|
icon_mode = 'auto'
|
||||||
|
show_title_bar = true
|
||||||
|
show_footer = true
|
||||||
|
border = 'rounded'
|
||||||
|
path_display = 'smart'
|
||||||
|
pane_gap = 1
|
||||||
|
center_width_percent = 30
|
||||||
|
|
||||||
|
[browser]
|
||||||
|
show_hidden = true
|
||||||
|
dirs_first = true
|
||||||
|
human_readable_size = true
|
||||||
|
|
||||||
|
[browser.sort]
|
||||||
|
by = 'name'
|
||||||
|
reverse = false
|
||||||
|
|
||||||
|
[browser.columns]
|
||||||
|
name = true
|
||||||
|
size = true
|
||||||
|
modified = true
|
||||||
|
created = false
|
||||||
|
permissions = false
|
||||||
|
extension = false
|
||||||
|
|
||||||
|
[preview]
|
||||||
|
show_metadata = true
|
||||||
|
wrap_text = false
|
||||||
|
max_preview_bytes = 65536
|
||||||
|
directory_preview_limit = 80
|
||||||
|
|
||||||
|
[behavior]
|
||||||
|
confirm_delete = true
|
||||||
|
confirm_overwrite = true
|
||||||
|
calculate_dir_size_on_space = true
|
||||||
|
follow_symlinks = false
|
||||||
|
auto_refresh = true
|
||||||
|
auto_refresh_interval = 5
|
||||||
17
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/.editorconfig
generated
vendored
Normal file
17
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/.editorconfig
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.xml]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
28
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/.gitignore
generated
vendored
Normal file
28
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
.git
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.hermit
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
/cmd/chroma/chroma
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
|
.glide/
|
||||||
|
|
||||||
|
_models/
|
||||||
|
|
||||||
|
_examples/
|
||||||
|
*.min.*
|
||||||
|
build/
|
||||||
|
|
||||||
|
cmd/chromad/static/chroma.wasm
|
||||||
|
cmd/chromad/static/wasm_exec.js
|
||||||
89
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/.golangci.yml
generated
vendored
Normal file
89
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/.golangci.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
run:
|
||||||
|
tests: true
|
||||||
|
|
||||||
|
output:
|
||||||
|
print-issued-lines: false
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- lll
|
||||||
|
- gocyclo
|
||||||
|
- dupl
|
||||||
|
- gochecknoglobals
|
||||||
|
- funlen
|
||||||
|
- godox
|
||||||
|
- wsl
|
||||||
|
- gocognit
|
||||||
|
- nolintlint
|
||||||
|
- testpackage
|
||||||
|
- godot
|
||||||
|
- nestif
|
||||||
|
- paralleltest
|
||||||
|
- nlreturn
|
||||||
|
- cyclop
|
||||||
|
- gci
|
||||||
|
- gofumpt
|
||||||
|
- errorlint
|
||||||
|
- exhaustive
|
||||||
|
- wrapcheck
|
||||||
|
- stylecheck
|
||||||
|
- thelper
|
||||||
|
- nonamedreturns
|
||||||
|
- revive
|
||||||
|
- dupword
|
||||||
|
- exhaustruct
|
||||||
|
- varnamelen
|
||||||
|
- forcetypeassert
|
||||||
|
- ireturn
|
||||||
|
- maintidx
|
||||||
|
- govet
|
||||||
|
- testableexamples
|
||||||
|
- musttag
|
||||||
|
- depguard
|
||||||
|
- goconst
|
||||||
|
- perfsprint
|
||||||
|
- mnd
|
||||||
|
- predeclared
|
||||||
|
- recvcheck
|
||||||
|
- tenv
|
||||||
|
- err113
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 10
|
||||||
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
goconst:
|
||||||
|
min-len: 8
|
||||||
|
min-occurrences: 3
|
||||||
|
forbidigo:
|
||||||
|
#forbid:
|
||||||
|
# - (Must)?NewLexer$
|
||||||
|
exclude_godoc_examples: false
|
||||||
|
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-dirs:
|
||||||
|
- _examples
|
||||||
|
max-per-linter: 0
|
||||||
|
max-same: 0
|
||||||
|
exclude-use-default: false
|
||||||
|
exclude:
|
||||||
|
# Captured by errcheck.
|
||||||
|
- '^(G104|G204):'
|
||||||
|
# Very commonly not checked.
|
||||||
|
- 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
|
||||||
|
- 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos) should have comment or be unexported'
|
||||||
|
- 'composite literal uses unkeyed fields'
|
||||||
|
- 'declaration of "err" shadows declaration'
|
||||||
|
- 'should not use dot imports'
|
||||||
|
- 'Potential file inclusion via variable'
|
||||||
|
- 'should have comment or be unexported'
|
||||||
|
- 'comment on exported var .* should be of the form'
|
||||||
|
- 'at least one file in a package should have a package comment'
|
||||||
|
- 'string literal contains the Unicode'
|
||||||
|
- 'methods on the same type should have the same receiver name'
|
||||||
|
- '_TokenType_name should be _TokenTypeName'
|
||||||
|
- '`_TokenType_map` should be `_TokenTypeMap`'
|
||||||
|
- 'rewrite if-else to switch statement'
|
||||||
34
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/.goreleaser.yml
generated
vendored
Normal file
34
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/.goreleaser.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
project_name: chroma
|
||||||
|
release:
|
||||||
|
github:
|
||||||
|
owner: alecthomas
|
||||||
|
name: chroma
|
||||||
|
brews:
|
||||||
|
- install: bin.install "chroma"
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
builds:
|
||||||
|
- goos:
|
||||||
|
- linux
|
||||||
|
- darwin
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- arm64
|
||||||
|
- amd64
|
||||||
|
- "386"
|
||||||
|
goarm:
|
||||||
|
- "6"
|
||||||
|
dir: ./cmd/chroma
|
||||||
|
main: .
|
||||||
|
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
|
||||||
|
binary: chroma
|
||||||
|
archives:
|
||||||
|
- format: tar.gz
|
||||||
|
name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||||
|
files:
|
||||||
|
- COPYING
|
||||||
|
- README*
|
||||||
|
snapshot:
|
||||||
|
name_template: SNAPSHOT-{{ .Commit }}
|
||||||
|
checksum:
|
||||||
|
name_template: "{{ .ProjectName }}-{{ .Version }}-checksums.txt"
|
||||||
11
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/AGENTS.md
generated
vendored
Normal file
11
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/AGENTS.md
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
Chroma is a syntax highlighting library, tool and web playground for Go. It is based on Pygments and includes importers for it, so most of the same concepts from Pygments apply to Chroma.
|
||||||
|
|
||||||
|
This project is written in Go, uses Hermit to manage tooling, and Just for helper commands. Helper scripts are in ./scripts.
|
||||||
|
|
||||||
|
Language definitions are XML files defined in ./lexers/embedded/*.xml.
|
||||||
|
|
||||||
|
Styles/themes are defined in ./styles/*.xml.
|
||||||
|
|
||||||
|
The CLI can be run with `chroma`.
|
||||||
|
|
||||||
|
The web playground can be run with `chromad --csrf-key=moo`. It blocks, so should generally be run in the background. It also does not hot reload, so has to be manually restarted. The playground has two modes - for local development it uses the server itself to render, while for production running `just chromad` will compile ./cmd/libchromawasm into a WASM module that is bundled into `chromad`.
|
||||||
24
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/Bitfile
generated
vendored
Normal file
24
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/Bitfile
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
VERSION = %(git describe --tags --dirty --always)%
|
||||||
|
export CGOENABLED = 0
|
||||||
|
|
||||||
|
tokentype_enumer.go: types.go
|
||||||
|
build: go generate
|
||||||
|
|
||||||
|
# Regenerate the list of lexers in the README
|
||||||
|
README.md: lexers/*.go lexers/*/*.xml table.py
|
||||||
|
build: ./table.py
|
||||||
|
-clean
|
||||||
|
|
||||||
|
implicit %{1}%{2}.min.%{3}: **/*.{css,js}
|
||||||
|
build: esbuild --bundle %{IN} --minify --outfile=%{OUT}
|
||||||
|
|
||||||
|
implicit build/%{1}: cmd/*
|
||||||
|
cd cmd/%{1}
|
||||||
|
inputs: cmd/%{1}/**/* **/*.go
|
||||||
|
build: go build -ldflags="-X 'main.version=%{VERSION}'" -o ../../build/%{1} .
|
||||||
|
|
||||||
|
#upload: chromad
|
||||||
|
# build:
|
||||||
|
# scp chromad root@swapoff.org:
|
||||||
|
# ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart'
|
||||||
|
# touch upload
|
||||||
19
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/COPYING
generated
vendored
Normal file
19
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/COPYING
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (C) 2017 Alec Thomas
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
64
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/Dockerfile
generated
vendored
Normal file
64
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/Dockerfile
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Multi-stage Dockerfile for chromad Go application using Hermit-managed tools
|
||||||
|
|
||||||
|
# Build stage
|
||||||
|
FROM ubuntu:24.04 AS builder
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the entire project (including bin directory with Hermit tools)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Make Hermit tools executable and add to PATH
|
||||||
|
ENV PATH="/app/bin:${PATH}"
|
||||||
|
|
||||||
|
# Set Go environment variables for static compilation
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
ENV GOOS=linux
|
||||||
|
ENV GOARCH=amd64
|
||||||
|
|
||||||
|
# Build the application using just
|
||||||
|
RUN just chromad
|
||||||
|
|
||||||
|
# Runtime stage
|
||||||
|
FROM alpine:3.23 AS runtime
|
||||||
|
|
||||||
|
# Install ca-certificates for HTTPS requests
|
||||||
|
RUN apk --no-cache add ca-certificates curl
|
||||||
|
|
||||||
|
# Create a non-root user
|
||||||
|
RUN addgroup -g 1001 chromad && \
|
||||||
|
adduser -D -s /bin/sh -u 1001 -G chromad chromad
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the binary from build stage
|
||||||
|
COPY --from=builder /app/build/chromad /app/chromad
|
||||||
|
|
||||||
|
# Change ownership to non-root user
|
||||||
|
RUN chown chromad:chromad /app/chromad
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER chromad
|
||||||
|
|
||||||
|
# Expose port (default is 8080, but can be overridden via PORT env var)
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Set default environment variables
|
||||||
|
ENV PORT=8080
|
||||||
|
ENV CHROMA_CSRF_KEY="testtest"
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD curl -fsSL http://127.0.0.1:8080/ > /dev/null
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["sh", "-c", "./chromad --csrf-key=$CHROMA_CSRF_KEY --bind=0.0.0.0:$PORT"]
|
||||||
55
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/Justfile
generated
vendored
Normal file
55
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/Justfile
generated
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
set positional-arguments := true
|
||||||
|
set shell := ["bash", "-c"]
|
||||||
|
|
||||||
|
version := `git describe --tags --dirty --always`
|
||||||
|
export GOOS := env("GOOS", "linux")
|
||||||
|
export GOARCH := env("GOARCH", "amd64")
|
||||||
|
|
||||||
|
_help:
|
||||||
|
@just -l
|
||||||
|
|
||||||
|
# Generate README.md from lexer definitions
|
||||||
|
readme:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
GOOS= GOARCH= ./table.py
|
||||||
|
|
||||||
|
# Generate tokentype_string.go
|
||||||
|
tokentype-string:
|
||||||
|
go generate
|
||||||
|
|
||||||
|
# Format JavaScript files
|
||||||
|
format-js:
|
||||||
|
biome format --write cmd/chromad/static/index.js cmd/chromad/static/chroma.js
|
||||||
|
|
||||||
|
# Build chromad binary
|
||||||
|
chromad: wasm-exec chroma-wasm
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
rm -rf build
|
||||||
|
mk cmd/chromad/static/index.min.js : cmd/chromad/static/{index,chroma}.js -- \
|
||||||
|
esbuild --platform=browser --format=esm --bundle cmd/chromad/static/index.js --minify --external:./wasm_exec.js --outfile=cmd/chromad/static/index.min.js
|
||||||
|
mk cmd/chromad/static/index.min.css : cmd/chromad/static/index.css -- \
|
||||||
|
esbuild --bundle cmd/chromad/static/index.css --minify --outfile=cmd/chromad/static/index.min.css
|
||||||
|
cd cmd/chromad && CGOENABLED=0 go build -ldflags="-X 'main.version={{ version }}'" -o ../../build/chromad .
|
||||||
|
|
||||||
|
# Copy wasm_exec.js from TinyGo
|
||||||
|
wasm-exec:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
tinygoroot=$(tinygo env TINYGOROOT)
|
||||||
|
mk cmd/chromad/static/wasm_exec.js : "$tinygoroot/targets/wasm_exec.js" -- \
|
||||||
|
install -m644 "$tinygoroot/targets/wasm_exec.js" cmd/chromad/static/wasm_exec.js
|
||||||
|
|
||||||
|
# Build WASM binary
|
||||||
|
chroma-wasm:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
if type tinygo > /dev/null 2>&1; then
|
||||||
|
mk cmd/chromad/static/chroma.wasm : cmd/libchromawasm/main.go -- \
|
||||||
|
tinygo build -no-debug -target wasm -o cmd/chromad/static/chroma.wasm cmd/libchromawasm/main.go
|
||||||
|
else
|
||||||
|
mk cmd/chromad/static/chroma.wasm : cmd/libchromawasm/main.go -- \
|
||||||
|
GOOS=js GOARCH=wasm go build -o cmd/chromad/static/chroma.wasm cmd/libchromawasm/main.go
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload chromad to server
|
||||||
|
upload: chromad
|
||||||
|
scp build/chromad root@swapoff.org:
|
||||||
|
ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart'
|
||||||
307
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/README.md
generated
vendored
Normal file
307
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,307 @@
|
||||||
|

|
||||||
|
|
||||||
|
# A general purpose syntax highlighter in pure Go
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/alecthomas/chroma/v2) [](https://github.com/alecthomas/chroma/actions/workflows/ci.yml) [](https://invite.slack.golangbridge.org/)
|
||||||
|
|
||||||
|
|
||||||
|
Chroma takes source code and other structured text and converts it into syntax
|
||||||
|
highlighted HTML, ANSI-coloured text, etc.
|
||||||
|
|
||||||
|
Chroma is based heavily on [Pygments](http://pygments.org/), and includes
|
||||||
|
translators for Pygments lexers and styles.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
|
1. [Supported languages](#supported-languages)
|
||||||
|
2. [Try it](#try-it)
|
||||||
|
3. [Using the library](#using-the-library)
|
||||||
|
1. [Quick start](#quick-start)
|
||||||
|
2. [Identifying the language](#identifying-the-language)
|
||||||
|
3. [Formatting the output](#formatting-the-output)
|
||||||
|
4. [The HTML formatter](#the-html-formatter)
|
||||||
|
4. [More detail](#more-detail)
|
||||||
|
1. [Lexers](#lexers)
|
||||||
|
2. [Formatters](#formatters)
|
||||||
|
3. [Styles](#styles)
|
||||||
|
5. [Command-line interface](#command-line-interface)
|
||||||
|
6. [Testing lexers](#testing-lexers)
|
||||||
|
7. [What's missing compared to Pygments?](#whats-missing-compared-to-pygments)
|
||||||
|
|
||||||
|
<!-- /TOC -->
|
||||||
|
|
||||||
|
## Supported languages
|
||||||
|
|
||||||
|
| Prefix | Language
|
||||||
|
| :----: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
| A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Agda, AL, Alloy, Angular2, ANTLR, ApacheConf, APL, AppleScript, ArangoDB AQL, Arduino, ArmAsm, ATL, AutoHotkey, AutoIt, Awk
|
||||||
|
| B | Ballerina, Bash, Bash Session, Batchfile, Beef, BibTeX, Bicep, BlitzBasic, BNF, BQN, Brainfuck
|
||||||
|
| C | C, C#, C++, C3, Caddyfile, Caddyfile Directives, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Chapel, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Core, Crystal, CSS, CSV, CUE, Cython
|
||||||
|
| D | D, Dart, Dax, Desktop file, Diff, Django/Jinja, dns, Docker, DTD, Dylan
|
||||||
|
| E | EBNF, Elixir, Elm, EmacsLisp, Erlang
|
||||||
|
| F | Factor, Fennel, Fish, Forth, Fortran, FortranFixed, FSharp
|
||||||
|
| G | GAS, GDScript, GDScript3, Gemtext, Genshi, Genshi HTML, Genshi Text, Gherkin, Gleam, GLSL, Gnuplot, Go, Go HTML Template, Go Template, Go Text Template, GraphQL, Groff, Groovy
|
||||||
|
| H | Handlebars, Hare, Haskell, Haxe, HCL, Hexdump, HLB, HLSL, HolyC, HTML, HTTP, Hy
|
||||||
|
| I | Idris, Igor, INI, Io, ISCdhcpd
|
||||||
|
| J | J, Janet, Java, JavaScript, JSON, JSONata, Jsonnet, Julia, Jungle
|
||||||
|
| K | Kakoune, Kotlin
|
||||||
|
| L | Lean4, Lighttpd configuration file, LLVM, lox, Lua
|
||||||
|
| M | Makefile, Mako, markdown, Markless, Mason, Materialize SQL dialect, Mathematica, Matlab, MCFunction, Meson, Metal, MiniZinc, MLIR, Modelica, Modula-2, Mojo, MonkeyC, MoonScript, MorrowindScript, Myghty, MySQL
|
||||||
|
| N | NASM, Natural, NDISASM, Newspeak, Nginx configuration file, Nim, Nix, NSIS, Nu
|
||||||
|
| O | Objective-C, ObjectPascal, OCaml, Octave, Odin, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org Mode
|
||||||
|
| P | PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Plutus Core, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerQuery, PowerShell, Prolog, Promela, PromQL, properties, Protocol Buffer, Protocol Buffer Text Format, PRQL, PSL, Puppet, Python, Python 2
|
||||||
|
| Q | QBasic, QML
|
||||||
|
| R | R, Racket, Ragel, Raku, react, ReasonML, reg, Rego, reStructuredText, Rexx, RGBDS Assembly, Ring, RPGLE, RPMSpec, Ruby, Rust
|
||||||
|
| S | SAS, Sass, Scala, Scheme, Scilab, SCSS, Sed, Sieve, Smali, Smalltalk, Smarty, SNBT, Snobol, Solidity, SourcePawn, SPARQL, SQL, SquidConf, Standard ML, stas, Stylus, Svelte, Swift, SYSTEMD, systemverilog
|
||||||
|
| T | TableGen, Tal, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData, Typst
|
||||||
|
| U | ucode
|
||||||
|
| V | V, V shell, Vala, VB.net, verilog, VHDL, VHS, VimL, vue
|
||||||
|
| W | WDTE, WebAssembly Text Format, WebGPU Shading Language, WebVTT, Whiley
|
||||||
|
| X | XML, Xorg
|
||||||
|
| Y | YAML, YANG
|
||||||
|
| Z | Z80 Assembly, Zed, Zig
|
||||||
|
|
||||||
|
_I will attempt to keep this section up to date, but an authoritative list can be
|
||||||
|
displayed with `chroma --list`._
|
||||||
|
|
||||||
|
## Try it
|
||||||
|
|
||||||
|
Try out various languages and styles on the [Chroma Playground](https://swapoff.org/chroma/playground/).
|
||||||
|
|
||||||
|
## Using the library
|
||||||
|
|
||||||
|
This is version 2 of Chroma, use the import path:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/alecthomas/chroma/v2"
|
||||||
|
```
|
||||||
|
|
||||||
|
Chroma, like Pygments, has the concepts of
|
||||||
|
[lexers](https://github.com/alecthomas/chroma/tree/master/lexers),
|
||||||
|
[formatters](https://github.com/alecthomas/chroma/tree/master/formatters) and
|
||||||
|
[styles](https://github.com/alecthomas/chroma/tree/master/styles).
|
||||||
|
|
||||||
|
Lexers convert source text into a stream of tokens, styles specify how token
|
||||||
|
types are mapped to colours, and formatters convert tokens and styles into
|
||||||
|
formatted output.
|
||||||
|
|
||||||
|
A package exists for each of these, containing a global `Registry` variable
|
||||||
|
with all of the registered implementations. There are also helper functions
|
||||||
|
for using the registry in each package, such as looking up lexers by name or
|
||||||
|
matching filenames, etc.
|
||||||
|
|
||||||
|
In all cases, if a lexer, formatter or style can not be determined, `nil` will
|
||||||
|
be returned. In this situation you may want to default to the `Fallback`
|
||||||
|
value in each respective package, which provides sane defaults.
|
||||||
|
|
||||||
|
### Quick start
|
||||||
|
|
||||||
|
A convenience function exists that can be used to simply format some source
|
||||||
|
text, without any effort:
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Identifying the language
|
||||||
|
|
||||||
|
To highlight code, you'll first have to identify what language the code is
|
||||||
|
written in. There are three primary ways to do that:
|
||||||
|
|
||||||
|
1. Detect the language from its filename.
|
||||||
|
|
||||||
|
```go
|
||||||
|
lexer := lexers.Match("foo.go")
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Explicitly specify the language by its Chroma syntax ID (a full list is available from `lexers.Names()`).
|
||||||
|
|
||||||
|
```go
|
||||||
|
lexer := lexers.Get("go")
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Detect the language from its content.
|
||||||
|
|
||||||
|
```go
|
||||||
|
lexer := lexers.Analyse("package main\n\nfunc main()\n{\n}\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
In all cases, `nil` will be returned if the language can not be identified.
|
||||||
|
|
||||||
|
```go
|
||||||
|
if lexer == nil {
|
||||||
|
lexer = lexers.Fallback
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point, it should be noted that some lexers can be extremely chatty. To
|
||||||
|
mitigate this, you can use the coalescing lexer to coalesce runs of identical
|
||||||
|
token types into a single token:
|
||||||
|
|
||||||
|
```go
|
||||||
|
lexer = chroma.Coalesce(lexer)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formatting the output
|
||||||
|
|
||||||
|
Once a language is identified you will need to pick a formatter and a style (theme).
|
||||||
|
|
||||||
|
```go
|
||||||
|
style := styles.Get("swapoff")
|
||||||
|
if style == nil {
|
||||||
|
style = styles.Fallback
|
||||||
|
}
|
||||||
|
formatter := formatters.Get("html")
|
||||||
|
if formatter == nil {
|
||||||
|
formatter = formatters.Fallback
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then obtain an iterator over the tokens:
|
||||||
|
|
||||||
|
```go
|
||||||
|
contents, err := ioutil.ReadAll(r)
|
||||||
|
iterator, err := lexer.Tokenise(nil, string(contents))
|
||||||
|
```
|
||||||
|
|
||||||
|
And finally, format the tokens from the iterator:
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := formatter.Format(w, style, iterator)
|
||||||
|
```
|
||||||
|
|
||||||
|
### The HTML formatter
|
||||||
|
|
||||||
|
By default the `html` registered formatter generates standalone HTML with
|
||||||
|
embedded CSS. More flexibility is available through the `formatters/html` package.
|
||||||
|
|
||||||
|
Firstly, the output generated by the formatter can be customised with the
|
||||||
|
following constructor options:
|
||||||
|
|
||||||
|
- `Standalone()` - generate standalone HTML with embedded CSS.
|
||||||
|
- `WithClasses()` - use classes rather than inlined style attributes.
|
||||||
|
- `ClassPrefix(prefix)` - prefix each generated CSS class.
|
||||||
|
- `TabWidth(width)` - Set the rendered tab width, in characters.
|
||||||
|
- `WithLineNumbers()` - Render line numbers (style with `LineNumbers`).
|
||||||
|
- `WithLinkableLineNumbers()` - Make the line numbers linkable and be a link to themselves.
|
||||||
|
- `HighlightLines(ranges)` - Highlight lines in these ranges (style with `LineHighlight`).
|
||||||
|
- `LineNumbersInTable()` - Use a table for formatting line numbers and code, rather than spans.
|
||||||
|
|
||||||
|
If `WithClasses()` is used, the corresponding CSS can be obtained from the formatter with:
|
||||||
|
|
||||||
|
```go
|
||||||
|
formatter := html.New(html.WithClasses(true))
|
||||||
|
err := formatter.WriteCSS(w, style)
|
||||||
|
```
|
||||||
|
|
||||||
|
## More detail
|
||||||
|
|
||||||
|
### Lexers
|
||||||
|
|
||||||
|
See the [Pygments documentation](http://pygments.org/docs/lexerdevelopment/)
|
||||||
|
for details on implementing lexers. Most concepts apply directly to Chroma,
|
||||||
|
but see existing lexer implementations for real examples.
|
||||||
|
|
||||||
|
In many cases lexers can be automatically converted directly from Pygments by
|
||||||
|
using the included Python 3 script `pygments2chroma_xml.py`. I use something like
|
||||||
|
the following:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
uv run --script _tools/pygments2chroma_xml.py \
|
||||||
|
pygments.lexers.jvm.KotlinLexer \
|
||||||
|
> lexers/embedded/kotlin.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
A list of all lexers available in Pygments can be found in [pygments-lexers.txt](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt).
|
||||||
|
|
||||||
|
### Formatters
|
||||||
|
|
||||||
|
Chroma supports HTML output, as well as terminal output in 8 colour, 256 colour, and true-colour.
|
||||||
|
|
||||||
|
A `noop` formatter is included that outputs the token text only, and a `tokens`
|
||||||
|
formatter outputs raw tokens. The latter is useful for debugging lexers.
|
||||||
|
|
||||||
|
### Styles
|
||||||
|
|
||||||
|
Chroma styles are defined in XML. The style entries use the
|
||||||
|
[same syntax](http://pygments.org/docs/styles/) as Pygments.
|
||||||
|
|
||||||
|
All Pygments styles have been converted to Chroma using the `_tools/style.py`
|
||||||
|
script.
|
||||||
|
|
||||||
|
When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles),
|
||||||
|
know that the `Background` token type provides the default style for tokens. It does so
|
||||||
|
by defining a foreground color and background color.
|
||||||
|
|
||||||
|
For example, this gives each token name not defined in the style a default color
|
||||||
|
of `#f8f8f8` and uses `#000000` for the highlighted code block's background:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<entry type="Background" style="#f8f8f2 bg:#000000"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, token types in a style file are hierarchical. For instance, when `CommentSpecial` is not defined, Chroma uses the token style from `Comment`. So when several comment tokens use the same color, you'll only need to define `Comment` and override the one that has a different color.
|
||||||
|
|
||||||
|
For a quick overview of the available styles and how they look, check out the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/).
|
||||||
|
|
||||||
|
## Command-line interface
|
||||||
|
|
||||||
|
A command-line interface to Chroma is included.
|
||||||
|
|
||||||
|
Binaries are available to install from [the releases page](https://github.com/alecthomas/chroma/releases).
|
||||||
|
|
||||||
|
The CLI can be used as a preprocessor to colorise output of `less(1)`,
|
||||||
|
see documentation for the `LESSOPEN` environment variable.
|
||||||
|
|
||||||
|
The `--fail` flag can be used to suppress output and return with exit status
|
||||||
|
1 to facilitate falling back to some other preprocessor in case chroma
|
||||||
|
does not resolve a specific lexer to use for the given file. For example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export LESSOPEN='| p() { chroma --fail "$1" || cat "$1"; }; p "%s"'
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `cat` with your favourite fallback preprocessor.
|
||||||
|
|
||||||
|
When invoked as `.lessfilter`, the `--fail` flag is automatically turned
|
||||||
|
on under the hood for easy integration with [lesspipe shipping with
|
||||||
|
Debian and derivatives](https://manpages.debian.org/lesspipe#USER_DEFINED_FILTERS);
|
||||||
|
for that setup the `chroma` executable can be just symlinked to `~/.lessfilter`.
|
||||||
|
|
||||||
|
## Projects using Chroma
|
||||||
|
|
||||||
|
* [`moor`](https://github.com/walles/moor) is a full-blown pager that colorizes
|
||||||
|
its input using Chroma
|
||||||
|
* [Hugo](https://gohugo.io/) is a static site generator that [uses Chroma for syntax
|
||||||
|
highlighting code examples](https://gohugo.io/content-management/syntax-highlighting/)
|
||||||
|
|
||||||
|
## Testing lexers
|
||||||
|
|
||||||
|
If you edit some lexers and want to try it, open a shell in `cmd/chromad` and run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go run . --csrf-key=securekey
|
||||||
|
```
|
||||||
|
|
||||||
|
A Link will be printed. Open it in your Browser. Now you can test on the Playground with your local changes.
|
||||||
|
|
||||||
|
If you want to run the tests and the lexers, open a shell in the root directory and run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go test ./lexers
|
||||||
|
```
|
||||||
|
|
||||||
|
When updating or adding a lexer, please add tests. See [lexers/README.md](lexers/README.md) for more.
|
||||||
|
|
||||||
|
## What's missing compared to Pygments?
|
||||||
|
|
||||||
|
- Quite a few lexers, for various reasons (pull-requests welcome):
|
||||||
|
- Pygments lexers for complex languages often include custom code to
|
||||||
|
handle certain aspects, such as Raku's ability to nest code inside
|
||||||
|
regular expressions. These require time and effort to convert.
|
||||||
|
- I mostly only converted languages I had heard of, to reduce the porting cost.
|
||||||
|
- Some more esoteric features of Pygments are omitted for simplicity.
|
||||||
|
- Though the Chroma API supports content detection, very few languages support them.
|
||||||
|
I have plans to implement a statistical analyser at some point, but not enough time.
|
||||||
6
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/biome.json
generated
vendored
Normal file
6
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/biome.json
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.0.5/schema.json",
|
||||||
|
"formatter": {
|
||||||
|
"indentStyle": "space"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/chroma.jpg
generated
vendored
Normal file
BIN
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/chroma.jpg
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
35
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/coalesce.go
generated
vendored
Normal file
35
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/coalesce.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package chroma
|
||||||
|
|
||||||
|
// Coalesce is a Lexer interceptor that collapses runs of common types into a single token.
|
||||||
|
func Coalesce(lexer Lexer) Lexer { return &coalescer{lexer} }
|
||||||
|
|
||||||
|
type coalescer struct{ Lexer }
|
||||||
|
|
||||||
|
func (d *coalescer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) {
|
||||||
|
var prev Token
|
||||||
|
it, err := d.Lexer.Tokenise(options, text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func() Token {
|
||||||
|
for token := it(); token != (EOF); token = it() {
|
||||||
|
if len(token.Value) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if prev == EOF {
|
||||||
|
prev = token
|
||||||
|
} else {
|
||||||
|
if prev.Type == token.Type && len(prev.Value) < 8192 {
|
||||||
|
prev.Value += token.Value
|
||||||
|
} else {
|
||||||
|
out := prev
|
||||||
|
prev = token
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out := prev
|
||||||
|
prev = EOF
|
||||||
|
return out
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
192
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/colour.go
generated
vendored
Normal file
192
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/colour.go
generated
vendored
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
package chroma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ANSI2RGB maps ANSI colour names, as supported by Chroma, to hex RGB values.
|
||||||
|
var ANSI2RGB = map[string]string{
|
||||||
|
"#ansiblack": "000000",
|
||||||
|
"#ansidarkred": "7f0000",
|
||||||
|
"#ansidarkgreen": "007f00",
|
||||||
|
"#ansibrown": "7f7fe0",
|
||||||
|
"#ansidarkblue": "00007f",
|
||||||
|
"#ansipurple": "7f007f",
|
||||||
|
"#ansiteal": "007f7f",
|
||||||
|
"#ansilightgray": "e5e5e5",
|
||||||
|
// Normal
|
||||||
|
"#ansidarkgray": "555555",
|
||||||
|
"#ansired": "ff0000",
|
||||||
|
"#ansigreen": "00ff00",
|
||||||
|
"#ansiyellow": "ffff00",
|
||||||
|
"#ansiblue": "0000ff",
|
||||||
|
"#ansifuchsia": "ff00ff",
|
||||||
|
"#ansiturquoise": "00ffff",
|
||||||
|
"#ansiwhite": "ffffff",
|
||||||
|
|
||||||
|
// Aliases without the "ansi" prefix, because...why?
|
||||||
|
"#black": "000000",
|
||||||
|
"#darkred": "7f0000",
|
||||||
|
"#darkgreen": "007f00",
|
||||||
|
"#brown": "7f7fe0",
|
||||||
|
"#darkblue": "00007f",
|
||||||
|
"#purple": "7f007f",
|
||||||
|
"#teal": "007f7f",
|
||||||
|
"#lightgray": "e5e5e5",
|
||||||
|
// Normal
|
||||||
|
"#darkgray": "555555",
|
||||||
|
"#red": "ff0000",
|
||||||
|
"#green": "00ff00",
|
||||||
|
"#yellow": "ffff00",
|
||||||
|
"#blue": "0000ff",
|
||||||
|
"#fuchsia": "ff00ff",
|
||||||
|
"#turquoise": "00ffff",
|
||||||
|
"#white": "ffffff",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colour represents an RGB colour.
|
||||||
|
type Colour int32
|
||||||
|
|
||||||
|
// NewColour creates a Colour directly from RGB values.
|
||||||
|
func NewColour(r, g, b uint8) Colour {
|
||||||
|
return ParseColour(fmt.Sprintf("%02x%02x%02x", r, g, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance between this colour and another.
|
||||||
|
//
|
||||||
|
// This uses the approach described here (https://www.compuphase.com/cmetric.htm).
|
||||||
|
// This is not as accurate as LAB, et. al. but is *vastly* simpler and sufficient for our needs.
|
||||||
|
func (c Colour) Distance(e2 Colour) float64 {
|
||||||
|
ar, ag, ab := int64(c.Red()), int64(c.Green()), int64(c.Blue())
|
||||||
|
br, bg, bb := int64(e2.Red()), int64(e2.Green()), int64(e2.Blue())
|
||||||
|
rmean := (ar + br) / 2
|
||||||
|
r := ar - br
|
||||||
|
g := ag - bg
|
||||||
|
b := ab - bb
|
||||||
|
return math.Sqrt(float64((((512 + rmean) * r * r) >> 8) + 4*g*g + (((767 - rmean) * b * b) >> 8)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brighten returns a copy of this colour with its brightness adjusted.
|
||||||
|
//
|
||||||
|
// If factor is negative, the colour is darkened.
|
||||||
|
//
|
||||||
|
// Uses approach described here (http://www.pvladov.com/2012/09/make-color-lighter-or-darker.html).
|
||||||
|
func (c Colour) Brighten(factor float64) Colour {
|
||||||
|
r := float64(c.Red())
|
||||||
|
g := float64(c.Green())
|
||||||
|
b := float64(c.Blue())
|
||||||
|
|
||||||
|
if factor < 0 {
|
||||||
|
factor++
|
||||||
|
r *= factor
|
||||||
|
g *= factor
|
||||||
|
b *= factor
|
||||||
|
} else {
|
||||||
|
r = (255-r)*factor + r
|
||||||
|
g = (255-g)*factor + g
|
||||||
|
b = (255-b)*factor + b
|
||||||
|
}
|
||||||
|
return NewColour(uint8(r), uint8(g), uint8(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BrightenOrDarken brightens a colour if it is < 0.5 brightness or darkens if > 0.5 brightness.
|
||||||
|
func (c Colour) BrightenOrDarken(factor float64) Colour {
|
||||||
|
if c.Brightness() < 0.5 {
|
||||||
|
return c.Brighten(factor)
|
||||||
|
}
|
||||||
|
return c.Brighten(-factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClampBrightness returns a copy of this colour with its brightness adjusted such that
|
||||||
|
// it falls within the range [min, max] (or very close to it due to rounding errors).
|
||||||
|
// The supplied values use the same [0.0, 1.0] range as Brightness.
|
||||||
|
func (c Colour) ClampBrightness(min, max float64) Colour {
|
||||||
|
if !c.IsSet() {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
min = math.Max(min, 0)
|
||||||
|
max = math.Min(max, 1)
|
||||||
|
current := c.Brightness()
|
||||||
|
target := math.Min(math.Max(current, min), max)
|
||||||
|
if current == target {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
r := float64(c.Red())
|
||||||
|
g := float64(c.Green())
|
||||||
|
b := float64(c.Blue())
|
||||||
|
rgb := r + g + b
|
||||||
|
if target > current {
|
||||||
|
// Solve for x: target == ((255-r)*x + r + (255-g)*x + g + (255-b)*x + b) / 255 / 3
|
||||||
|
return c.Brighten((target*255*3 - rgb) / (255*3 - rgb))
|
||||||
|
}
|
||||||
|
// Solve for x: target == (r*(x+1) + g*(x+1) + b*(x+1)) / 255 / 3
|
||||||
|
return c.Brighten((target*255*3)/rgb - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brightness of the colour (roughly) in the range 0.0 to 1.0.
|
||||||
|
func (c Colour) Brightness() float64 {
|
||||||
|
return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseColour in the forms #rgb, #rrggbb, #ansi<colour>, or #<colour>.
|
||||||
|
// Will return an "unset" colour if invalid.
|
||||||
|
func ParseColour(colour string) Colour {
|
||||||
|
colour = normaliseColour(colour)
|
||||||
|
n, err := strconv.ParseUint(colour, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return Colour(n + 1) //nolint:gosec
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParseColour is like ParseColour except it panics if the colour is invalid.
|
||||||
|
//
|
||||||
|
// Will panic if colour is in an invalid format.
|
||||||
|
func MustParseColour(colour string) Colour {
|
||||||
|
parsed := ParseColour(colour)
|
||||||
|
if !parsed.IsSet() {
|
||||||
|
panic(fmt.Errorf("invalid colour %q", colour))
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns true if the colour is set.
|
||||||
|
func (c Colour) IsSet() bool { return c != 0 }
|
||||||
|
|
||||||
|
func (c Colour) String() string { return fmt.Sprintf("#%06x", int(c-1)) }
|
||||||
|
func (c Colour) GoString() string { return fmt.Sprintf("Colour(0x%06x)", int(c-1)) }
|
||||||
|
|
||||||
|
// Red component of colour.
|
||||||
|
func (c Colour) Red() uint8 { return uint8(((c - 1) >> 16) & 0xff) } //nolint:gosec
|
||||||
|
|
||||||
|
// Green component of colour.
|
||||||
|
func (c Colour) Green() uint8 { return uint8(((c - 1) >> 8) & 0xff) } //nolint:gosec
|
||||||
|
|
||||||
|
// Blue component of colour.
|
||||||
|
func (c Colour) Blue() uint8 { return uint8((c - 1) & 0xff) } //nolint:gosec
|
||||||
|
|
||||||
|
// Colours is an orderable set of colours.
|
||||||
|
type Colours []Colour
|
||||||
|
|
||||||
|
func (c Colours) Len() int { return len(c) }
|
||||||
|
func (c Colours) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||||
|
func (c Colours) Less(i, j int) bool { return c[i] < c[j] }
|
||||||
|
|
||||||
|
// Convert colours to #rrggbb.
|
||||||
|
func normaliseColour(colour string) string {
|
||||||
|
if ansi, ok := ANSI2RGB[colour]; ok {
|
||||||
|
return ansi
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(colour, "#") {
|
||||||
|
colour = colour[1:]
|
||||||
|
if len(colour) == 3 {
|
||||||
|
return colour[0:1] + colour[0:1] + colour[1:2] + colour[1:2] + colour[2:3] + colour[2:3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return colour
|
||||||
|
}
|
||||||
161
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/delegate.go
generated
vendored
Normal file
161
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/delegate.go
generated
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
package chroma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type delegatingLexer struct {
|
||||||
|
root Lexer
|
||||||
|
language Lexer
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegatingLexer combines two lexers to handle the common case of a language embedded inside another, such as PHP
|
||||||
|
// inside HTML or PHP inside plain text.
|
||||||
|
//
|
||||||
|
// It takes two lexer as arguments: a root lexer and a language lexer. First everything is scanned using the language
|
||||||
|
// lexer, which must return "Other" for unrecognised tokens. Then all "Other" tokens are lexed using the root lexer.
|
||||||
|
// Finally, these two sets of tokens are merged.
|
||||||
|
//
|
||||||
|
// The lexers from the template lexer package use this base lexer.
|
||||||
|
func DelegatingLexer(root Lexer, language Lexer) Lexer {
|
||||||
|
return &delegatingLexer{
|
||||||
|
root: root,
|
||||||
|
language: language,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *delegatingLexer) SetTracing(enable bool) {
|
||||||
|
if l, ok := d.language.(TracingLexer); ok {
|
||||||
|
l.SetTracing(enable)
|
||||||
|
}
|
||||||
|
if l, ok := d.root.(TracingLexer); ok {
|
||||||
|
l.SetTracing(enable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *delegatingLexer) AnalyseText(text string) float32 {
|
||||||
|
return d.root.AnalyseText(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *delegatingLexer) SetAnalyser(analyser func(text string) float32) Lexer {
|
||||||
|
d.root.SetAnalyser(analyser)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *delegatingLexer) SetRegistry(r *LexerRegistry) Lexer {
|
||||||
|
d.root.SetRegistry(r)
|
||||||
|
d.language.SetRegistry(r)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *delegatingLexer) Config() *Config {
|
||||||
|
return d.language.Config()
|
||||||
|
}
|
||||||
|
|
||||||
|
// An insertion is the character range where language tokens should be inserted.
|
||||||
|
type insertion struct {
|
||||||
|
start, end int
|
||||||
|
tokens []Token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *delegatingLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { // nolint: gocognit
|
||||||
|
tokens, err := Tokenise(Coalesce(d.language), options, text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Compute insertions and gather "Other" tokens.
|
||||||
|
others := &bytes.Buffer{}
|
||||||
|
insertions := []*insertion{}
|
||||||
|
var insert *insertion
|
||||||
|
offset := 0
|
||||||
|
var last Token
|
||||||
|
for _, t := range tokens {
|
||||||
|
if t.Type == Other {
|
||||||
|
if last != EOF && insert != nil && last.Type != Other {
|
||||||
|
insert.end = offset
|
||||||
|
}
|
||||||
|
others.WriteString(t.Value)
|
||||||
|
} else {
|
||||||
|
if last == EOF || last.Type == Other {
|
||||||
|
insert = &insertion{start: offset}
|
||||||
|
insertions = append(insertions, insert)
|
||||||
|
}
|
||||||
|
insert.tokens = append(insert.tokens, t)
|
||||||
|
}
|
||||||
|
last = t
|
||||||
|
offset += len(t.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(insertions) == 0 {
|
||||||
|
return d.root.Tokenise(options, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lex the other tokens.
|
||||||
|
rootTokens, err := Tokenise(Coalesce(d.root), options, others.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interleave the two sets of tokens.
|
||||||
|
var out []Token
|
||||||
|
offset = 0 // Offset into text.
|
||||||
|
tokenIndex := 0
|
||||||
|
nextToken := func() Token {
|
||||||
|
if tokenIndex >= len(rootTokens) {
|
||||||
|
return EOF
|
||||||
|
}
|
||||||
|
t := rootTokens[tokenIndex]
|
||||||
|
tokenIndex++
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
insertionIndex := 0
|
||||||
|
nextInsertion := func() *insertion {
|
||||||
|
if insertionIndex >= len(insertions) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
i := insertions[insertionIndex]
|
||||||
|
insertionIndex++
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
t := nextToken()
|
||||||
|
i := nextInsertion()
|
||||||
|
for t != EOF || i != nil {
|
||||||
|
// fmt.Printf("%d->%d:%q %d->%d:%q\n", offset, offset+len(t.Value), t.Value, i.start, i.end, Stringify(i.tokens...))
|
||||||
|
if t == EOF || (i != nil && i.start < offset+len(t.Value)) {
|
||||||
|
var l Token
|
||||||
|
l, t = splitToken(t, i.start-offset)
|
||||||
|
if l != EOF {
|
||||||
|
out = append(out, l)
|
||||||
|
offset += len(l.Value)
|
||||||
|
}
|
||||||
|
out = append(out, i.tokens...)
|
||||||
|
offset += i.end - i.start
|
||||||
|
if t == EOF {
|
||||||
|
t = nextToken()
|
||||||
|
}
|
||||||
|
i = nextInsertion()
|
||||||
|
} else {
|
||||||
|
out = append(out, t)
|
||||||
|
offset += len(t.Value)
|
||||||
|
t = nextToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Literator(out...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitToken(t Token, offset int) (l Token, r Token) {
|
||||||
|
if t == EOF {
|
||||||
|
return EOF, EOF
|
||||||
|
}
|
||||||
|
if offset == 0 {
|
||||||
|
return EOF, t
|
||||||
|
}
|
||||||
|
if offset == len(t.Value) {
|
||||||
|
return t, EOF
|
||||||
|
}
|
||||||
|
l = t.Clone()
|
||||||
|
r = t.Clone()
|
||||||
|
l.Value = l.Value[:offset]
|
||||||
|
r.Value = r.Value[offset:]
|
||||||
|
return
|
||||||
|
}
|
||||||
7
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/doc.go
generated
vendored
Normal file
7
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Package chroma takes source code and other structured text and converts it into syntax highlighted HTML, ANSI-
|
||||||
|
// coloured text, etc.
|
||||||
|
//
|
||||||
|
// Chroma is based heavily on Pygments, and includes translators for Pygments lexers and styles.
|
||||||
|
//
|
||||||
|
// For more information, go here: https://github.com/alecthomas/chroma
|
||||||
|
package chroma
|
||||||
233
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/emitters.go
generated
vendored
Normal file
233
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/emitters.go
generated
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
package chroma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Emitter takes group matches and returns tokens.
|
||||||
|
type Emitter interface {
|
||||||
|
// Emit tokens for the given regex groups.
|
||||||
|
Emit(groups []string, state *LexerState) Iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatingEmitter is an Emitter that can validate against a compiled rule.
|
||||||
|
type ValidatingEmitter interface {
|
||||||
|
Emitter
|
||||||
|
ValidateEmitter(rule *CompiledRule) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerialisableEmitter is an Emitter that can be serialised and deserialised to/from JSON.
|
||||||
|
type SerialisableEmitter interface {
|
||||||
|
Emitter
|
||||||
|
EmitterKind() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmitterFunc is a function that is an Emitter.
|
||||||
|
type EmitterFunc func(groups []string, state *LexerState) Iterator
|
||||||
|
|
||||||
|
// Emit tokens for groups.
|
||||||
|
func (e EmitterFunc) Emit(groups []string, state *LexerState) Iterator {
|
||||||
|
return e(groups, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Emitters []Emitter
|
||||||
|
|
||||||
|
type byGroupsEmitter struct {
|
||||||
|
Emitters
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ValidatingEmitter = (*byGroupsEmitter)(nil)
|
||||||
|
|
||||||
|
// ByGroups emits a token for each matching group in the rule's regex.
|
||||||
|
func ByGroups(emitters ...Emitter) Emitter {
|
||||||
|
return &byGroupsEmitter{Emitters: emitters}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *byGroupsEmitter) EmitterKind() string { return "bygroups" }
|
||||||
|
|
||||||
|
func (b *byGroupsEmitter) ValidateEmitter(rule *CompiledRule) error {
|
||||||
|
if len(rule.Regexp.GetGroupNumbers())-1 != len(b.Emitters) {
|
||||||
|
return fmt.Errorf("number of groups %d does not match number of emitters %d", len(rule.Regexp.GetGroupNumbers())-1, len(b.Emitters))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *byGroupsEmitter) Emit(groups []string, state *LexerState) Iterator {
|
||||||
|
iterators := make([]Iterator, 0, len(groups)-1)
|
||||||
|
if len(b.Emitters) != len(groups)-1 {
|
||||||
|
iterators = append(iterators, Error.Emit(groups, state))
|
||||||
|
// panic(errors.Errorf("number of groups %q does not match number of emitters %v", groups, emitters))
|
||||||
|
} else {
|
||||||
|
for i, group := range groups[1:] {
|
||||||
|
if b.Emitters[i] != nil {
|
||||||
|
iterators = append(iterators, b.Emitters[i].Emit([]string{group}, state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Concaterator(iterators...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByGroupNames emits a token for each named matching group in the rule's regex.
|
||||||
|
func ByGroupNames(emitters map[string]Emitter) Emitter {
|
||||||
|
return EmitterFunc(func(groups []string, state *LexerState) Iterator {
|
||||||
|
iterators := make([]Iterator, 0, len(state.NamedGroups)-1)
|
||||||
|
if len(state.NamedGroups)-1 == 0 {
|
||||||
|
if emitter, ok := emitters[`0`]; ok {
|
||||||
|
iterators = append(iterators, emitter.Emit(groups, state))
|
||||||
|
} else {
|
||||||
|
iterators = append(iterators, Error.Emit(groups, state))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ruleRegex := state.Rules[state.State][state.Rule].Regexp
|
||||||
|
for i := 1; i < len(state.NamedGroups); i++ {
|
||||||
|
groupName := ruleRegex.GroupNameFromNumber(i)
|
||||||
|
group := state.NamedGroups[groupName]
|
||||||
|
if emitter, ok := emitters[groupName]; ok {
|
||||||
|
if emitter != nil {
|
||||||
|
iterators = append(iterators, emitter.Emit([]string{group}, state))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iterators = append(iterators, Error.Emit([]string{group}, state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Concaterator(iterators...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsingByGroup emits tokens for the matched groups in the regex using a
|
||||||
|
// sublexer. Used when lexing code blocks where the name of a sublexer is
|
||||||
|
// contained within the block, for example on a Markdown text block or SQL
|
||||||
|
// language block.
|
||||||
|
//
|
||||||
|
// An attempt to load the sublexer will be made using the captured value from
|
||||||
|
// the text of the matched sublexerNameGroup. If a sublexer matching the
|
||||||
|
// sublexerNameGroup is available, then tokens for the matched codeGroup will
|
||||||
|
// be emitted using the sublexer. Otherwise, if no sublexer is available, then
|
||||||
|
// tokens will be emitted from the passed emitter.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// var Markdown = internal.Register(MustNewLexer(
|
||||||
|
// &Config{
|
||||||
|
// Name: "markdown",
|
||||||
|
// Aliases: []string{"md", "mkd"},
|
||||||
|
// Filenames: []string{"*.md", "*.mkd", "*.markdown"},
|
||||||
|
// MimeTypes: []string{"text/x-markdown"},
|
||||||
|
// },
|
||||||
|
// Rules{
|
||||||
|
// "root": {
|
||||||
|
// {"^(```)(\\w+)(\\n)([\\w\\W]*?)(^```$)",
|
||||||
|
// UsingByGroup(
|
||||||
|
// 2, 4,
|
||||||
|
// String, String, String, Text, String,
|
||||||
|
// ),
|
||||||
|
// nil,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// ))
|
||||||
|
//
|
||||||
|
// See the lexers/markdown.go for the complete example.
|
||||||
|
//
|
||||||
|
// Note: panic's if the number of emitters does not equal the number of matched
|
||||||
|
// groups in the regex.
|
||||||
|
func UsingByGroup(sublexerNameGroup, codeGroup int, emitters ...Emitter) Emitter {
|
||||||
|
return &usingByGroup{
|
||||||
|
SublexerNameGroup: sublexerNameGroup,
|
||||||
|
CodeGroup: codeGroup,
|
||||||
|
Emitters: emitters,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type usingByGroup struct {
|
||||||
|
SublexerNameGroup int `xml:"sublexer_name_group"`
|
||||||
|
CodeGroup int `xml:"code_group"`
|
||||||
|
Emitters Emitters `xml:"emitters"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usingByGroup) EmitterKind() string { return "usingbygroup" }
|
||||||
|
func (u *usingByGroup) Emit(groups []string, state *LexerState) Iterator {
|
||||||
|
// bounds check
|
||||||
|
if len(u.Emitters) != len(groups)-1 {
|
||||||
|
panic("UsingByGroup expects number of emitters to be the same as len(groups)-1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab sublexer
|
||||||
|
sublexer := state.Registry.Get(groups[u.SublexerNameGroup])
|
||||||
|
|
||||||
|
// build iterators
|
||||||
|
iterators := make([]Iterator, len(groups)-1)
|
||||||
|
for i, group := range groups[1:] {
|
||||||
|
if i == u.CodeGroup-1 && sublexer != nil {
|
||||||
|
var err error
|
||||||
|
iterators[i], err = sublexer.Tokenise(nil, groups[u.CodeGroup])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else if u.Emitters[i] != nil {
|
||||||
|
iterators[i] = u.Emitters[i].Emit([]string{group}, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Concaterator(iterators...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsingLexer returns an Emitter that uses a given Lexer for parsing and emitting.
|
||||||
|
//
|
||||||
|
// This Emitter is not serialisable.
|
||||||
|
func UsingLexer(lexer Lexer) Emitter {
|
||||||
|
return EmitterFunc(func(groups []string, _ *LexerState) Iterator {
|
||||||
|
it, err := lexer.Tokenise(&TokeniseOptions{State: "root", Nested: true}, groups[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type usingEmitter struct {
|
||||||
|
Lexer string `xml:"lexer,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usingEmitter) EmitterKind() string { return "using" }
|
||||||
|
|
||||||
|
func (u *usingEmitter) Emit(groups []string, state *LexerState) Iterator {
|
||||||
|
if state.Registry == nil {
|
||||||
|
panic(fmt.Sprintf("no LexerRegistry available for Using(%q)", u.Lexer))
|
||||||
|
}
|
||||||
|
lexer := state.Registry.Get(u.Lexer)
|
||||||
|
if lexer == nil {
|
||||||
|
panic(fmt.Sprintf("no such lexer %q", u.Lexer))
|
||||||
|
}
|
||||||
|
it, err := lexer.Tokenise(&TokeniseOptions{State: "root", Nested: true}, groups[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using returns an Emitter that uses a given Lexer reference for parsing and emitting.
|
||||||
|
//
|
||||||
|
// The referenced lexer must be stored in the same LexerRegistry.
|
||||||
|
func Using(lexer string) Emitter {
|
||||||
|
return &usingEmitter{Lexer: lexer}
|
||||||
|
}
|
||||||
|
|
||||||
|
type usingSelfEmitter struct {
|
||||||
|
State string `xml:"state,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usingSelfEmitter) EmitterKind() string { return "usingself" }
|
||||||
|
|
||||||
|
func (u *usingSelfEmitter) Emit(groups []string, state *LexerState) Iterator {
|
||||||
|
it, err := state.Lexer.Tokenise(&TokeniseOptions{State: u.State, Nested: true}, groups[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsingSelf is like Using, but uses the current Lexer.
|
||||||
|
func UsingSelf(stateName string) Emitter {
|
||||||
|
return &usingSelfEmitter{stateName}
|
||||||
|
}
|
||||||
43
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatter.go
generated
vendored
Normal file
43
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package chroma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Formatter for Chroma lexers.
|
||||||
|
type Formatter interface {
|
||||||
|
// Format returns a formatting function for tokens.
|
||||||
|
//
|
||||||
|
// If the iterator panics, the Formatter should recover.
|
||||||
|
Format(w io.Writer, style *Style, iterator Iterator) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A FormatterFunc is a Formatter implemented as a function.
|
||||||
|
//
|
||||||
|
// Guards against iterator panics.
|
||||||
|
type FormatterFunc func(w io.Writer, style *Style, iterator Iterator) error
|
||||||
|
|
||||||
|
func (f FormatterFunc) Format(w io.Writer, s *Style, it Iterator) (err error) { // nolint
|
||||||
|
defer func() {
|
||||||
|
if perr := recover(); perr != nil {
|
||||||
|
err = perr.(error)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return f(w, s, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
type recoveringFormatter struct {
|
||||||
|
Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r recoveringFormatter) Format(w io.Writer, s *Style, it Iterator) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if perr := recover(); perr != nil {
|
||||||
|
err = perr.(error)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return r.Formatter.Format(w, s, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoveringFormatter wraps a formatter with panic recovery.
|
||||||
|
func RecoveringFormatter(formatter Formatter) Formatter { return recoveringFormatter{formatter} }
|
||||||
57
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/api.go
generated
vendored
Normal file
57
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/api.go
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
|
"github.com/alecthomas/chroma/v2/formatters/svg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// NoOp formatter.
|
||||||
|
NoOp = Register("noop", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, iterator chroma.Iterator) error {
|
||||||
|
for t := iterator(); t != chroma.EOF; t = iterator() {
|
||||||
|
if _, err := io.WriteString(w, t.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
// Default HTML formatter outputs self-contained HTML.
|
||||||
|
htmlFull = Register("html", html.New(html.Standalone(true), html.WithClasses(true))) // nolint
|
||||||
|
SVG = Register("svg", svg.New(svg.EmbedFont("Liberation Mono", svg.FontLiberationMono, svg.WOFF)))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fallback formatter.
|
||||||
|
var Fallback = NoOp
|
||||||
|
|
||||||
|
// Registry of Formatters.
|
||||||
|
var Registry = map[string]chroma.Formatter{}
|
||||||
|
|
||||||
|
// Names of registered formatters.
|
||||||
|
func Names() []string {
|
||||||
|
out := []string{}
|
||||||
|
for name := range Registry {
|
||||||
|
out = append(out, name)
|
||||||
|
}
|
||||||
|
sort.Strings(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get formatter by name.
|
||||||
|
//
|
||||||
|
// If the given formatter is not found, the Fallback formatter will be returned.
|
||||||
|
func Get(name string) chroma.Formatter {
|
||||||
|
if f, ok := Registry[name]; ok {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
return Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a named formatter.
|
||||||
|
func Register(name string, formatter chroma.Formatter) chroma.Formatter {
|
||||||
|
Registry[name] = formatter
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
648
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/html/html.go
generated
vendored
Normal file
648
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/html/html.go
generated
vendored
Normal file
|
|
@ -0,0 +1,648 @@
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option sets an option of the HTML formatter.
|
||||||
|
type Option func(f *Formatter)
|
||||||
|
|
||||||
|
// Standalone configures the HTML formatter for generating a standalone HTML document.
|
||||||
|
func Standalone(b bool) Option { return func(f *Formatter) { f.standalone = b } }
|
||||||
|
|
||||||
|
// ClassPrefix sets the CSS class prefix.
|
||||||
|
func ClassPrefix(prefix string) Option { return func(f *Formatter) { f.prefix = prefix } }
|
||||||
|
|
||||||
|
// WithClasses emits HTML using CSS classes, rather than inline styles.
|
||||||
|
func WithClasses(b bool) Option { return func(f *Formatter) { f.Classes = b } }
|
||||||
|
|
||||||
|
// WithAllClasses disables an optimisation that omits redundant CSS classes.
|
||||||
|
func WithAllClasses(b bool) Option { return func(f *Formatter) { f.allClasses = b } }
|
||||||
|
|
||||||
|
// WithCustomCSS sets user's custom CSS styles.
|
||||||
|
func WithCustomCSS(css map[chroma.TokenType]string) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.customCSS = css
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCSSComments adds prefixe comments to the css classes. Defaults to true.
|
||||||
|
func WithCSSComments(b bool) Option { return func(f *Formatter) { f.writeCSSComments = b } }
|
||||||
|
|
||||||
|
// TabWidth sets the number of characters for a tab. Defaults to 8.
|
||||||
|
func TabWidth(width int) Option { return func(f *Formatter) { f.tabWidth = width } }
|
||||||
|
|
||||||
|
// PreventSurroundingPre prevents the surrounding pre tags around the generated code.
|
||||||
|
func PreventSurroundingPre(b bool) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.preventSurroundingPre = b
|
||||||
|
|
||||||
|
if b {
|
||||||
|
f.preWrapper = nopPreWrapper
|
||||||
|
} else {
|
||||||
|
f.preWrapper = defaultPreWrapper
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InlineCode creates inline code wrapped in a code tag.
|
||||||
|
func InlineCode(b bool) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.inlineCode = b
|
||||||
|
f.preWrapper = preWrapper{
|
||||||
|
start: func(code bool, styleAttr string) string {
|
||||||
|
if code {
|
||||||
|
return fmt.Sprintf(`<code%s>`, styleAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ``
|
||||||
|
},
|
||||||
|
end: func(code bool) string {
|
||||||
|
if code {
|
||||||
|
return `</code>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return ``
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPreWrapper allows control of the surrounding pre tags.
|
||||||
|
func WithPreWrapper(wrapper PreWrapper) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.preWrapper = wrapper
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapLongLines wraps long lines.
|
||||||
|
func WrapLongLines(b bool) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.wrapLongLines = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLineNumbers formats output with line numbers.
|
||||||
|
func WithLineNumbers(b bool) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.lineNumbers = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineNumbersInTable will, when combined with WithLineNumbers, separate the line numbers
|
||||||
|
// and code in table td's, which make them copy-and-paste friendly.
|
||||||
|
func LineNumbersInTable(b bool) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.lineNumbersInTable = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLinkableLineNumbers decorates the line numbers HTML elements with an "id"
|
||||||
|
// attribute so they can be linked.
|
||||||
|
func WithLinkableLineNumbers(b bool, prefix string) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.linkableLineNumbers = b
|
||||||
|
f.lineNumbersIDPrefix = prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HighlightLines higlights the given line ranges with the Highlight style.
|
||||||
|
//
|
||||||
|
// A range is the beginning and ending of a range as 1-based line numbers, inclusive.
|
||||||
|
func HighlightLines(ranges [][2]int) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.highlightRanges = ranges
|
||||||
|
sort.Sort(f.highlightRanges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseLineNumber sets the initial number to start line numbering at. Defaults to 1.
|
||||||
|
func BaseLineNumber(n int) Option {
|
||||||
|
return func(f *Formatter) {
|
||||||
|
f.baseLineNumber = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New HTML formatter.
|
||||||
|
func New(options ...Option) *Formatter {
|
||||||
|
f := &Formatter{
|
||||||
|
baseLineNumber: 1,
|
||||||
|
preWrapper: defaultPreWrapper,
|
||||||
|
writeCSSComments: true,
|
||||||
|
}
|
||||||
|
f.styleCache = newStyleCache(f)
|
||||||
|
for _, option := range options {
|
||||||
|
option(f)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreWrapper defines the operations supported in WithPreWrapper.
|
||||||
|
type PreWrapper interface {
|
||||||
|
// Start is called to write a start <pre> element.
|
||||||
|
// The code flag tells whether this block surrounds
|
||||||
|
// highlighted code. This will be false when surrounding
|
||||||
|
// line numbers.
|
||||||
|
Start(code bool, styleAttr string) string
|
||||||
|
|
||||||
|
// End is called to write the end </pre> element.
|
||||||
|
End(code bool) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type preWrapper struct {
|
||||||
|
start func(code bool, styleAttr string) string
|
||||||
|
end func(code bool) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p preWrapper) Start(code bool, styleAttr string) string {
|
||||||
|
return p.start(code, styleAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p preWrapper) End(code bool) string {
|
||||||
|
return p.end(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
nopPreWrapper = preWrapper{
|
||||||
|
start: func(code bool, styleAttr string) string { return "" },
|
||||||
|
end: func(code bool) string { return "" },
|
||||||
|
}
|
||||||
|
defaultPreWrapper = preWrapper{
|
||||||
|
start: func(code bool, styleAttr string) string {
|
||||||
|
if code {
|
||||||
|
return fmt.Sprintf(`<pre%s><code>`, styleAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<pre%s>`, styleAttr)
|
||||||
|
},
|
||||||
|
end: func(code bool) string {
|
||||||
|
if code {
|
||||||
|
return `</code></pre>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `</pre>`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Formatter that generates HTML.
|
||||||
|
type Formatter struct {
|
||||||
|
styleCache *styleCache
|
||||||
|
standalone bool
|
||||||
|
prefix string
|
||||||
|
Classes bool // Exported field to detect when classes are being used
|
||||||
|
allClasses bool
|
||||||
|
customCSS map[chroma.TokenType]string
|
||||||
|
writeCSSComments bool
|
||||||
|
preWrapper PreWrapper
|
||||||
|
inlineCode bool
|
||||||
|
preventSurroundingPre bool
|
||||||
|
tabWidth int
|
||||||
|
wrapLongLines bool
|
||||||
|
lineNumbers bool
|
||||||
|
lineNumbersInTable bool
|
||||||
|
linkableLineNumbers bool
|
||||||
|
lineNumbersIDPrefix string
|
||||||
|
highlightRanges highlightRanges
|
||||||
|
baseLineNumber int
|
||||||
|
}
|
||||||
|
|
||||||
|
type highlightRanges [][2]int
|
||||||
|
|
||||||
|
func (h highlightRanges) Len() int { return len(h) }
|
||||||
|
func (h highlightRanges) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||||
|
func (h highlightRanges) Less(i, j int) bool { return h[i][0] < h[j][0] }
|
||||||
|
|
||||||
|
func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) {
|
||||||
|
return f.writeHTML(w, style, iterator.Tokens())
|
||||||
|
}
|
||||||
|
|
||||||
|
// We deliberately don't use html/template here because it is two orders of magnitude slower (benchmarked).
|
||||||
|
//
|
||||||
|
// OTOH we need to be super careful about correct escaping...
|
||||||
|
func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.Token) (err error) { // nolint: gocyclo
|
||||||
|
css := f.styleCache.get(style, true)
|
||||||
|
if f.standalone {
|
||||||
|
fmt.Fprint(w, "<html>\n")
|
||||||
|
if f.Classes {
|
||||||
|
fmt.Fprint(w, "<style type=\"text/css\">\n")
|
||||||
|
err = f.WriteCSS(w, style)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "body { %s; }\n", css[chroma.Background])
|
||||||
|
fmt.Fprint(w, "</style>")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "<body%s>\n", f.styleAttr(css, chroma.Background))
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapInTable := f.lineNumbers && f.lineNumbersInTable
|
||||||
|
|
||||||
|
lines := chroma.SplitTokensIntoLines(tokens)
|
||||||
|
lineDigits := len(strconv.Itoa(f.baseLineNumber + len(lines) - 1))
|
||||||
|
highlightIndex := 0
|
||||||
|
|
||||||
|
if wrapInTable {
|
||||||
|
// List line numbers in its own <td>
|
||||||
|
fmt.Fprintf(w, "<div%s>\n", f.styleAttr(css, chroma.PreWrapper))
|
||||||
|
fmt.Fprintf(w, "<table%s><tr>", f.styleAttr(css, chroma.LineTable))
|
||||||
|
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD))
|
||||||
|
fmt.Fprintf(w, "%s", f.preWrapper.Start(false, f.styleAttr(css, chroma.PreWrapper)))
|
||||||
|
for index := range lines {
|
||||||
|
line := f.baseLineNumber + index
|
||||||
|
highlight, next := f.shouldHighlight(highlightIndex, line)
|
||||||
|
if next {
|
||||||
|
highlightIndex++
|
||||||
|
}
|
||||||
|
if highlight {
|
||||||
|
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "<span%s%s>%s\n</span>", f.styleAttr(css, chroma.LineNumbersTable), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(css, lineDigits, line))
|
||||||
|
|
||||||
|
if highlight {
|
||||||
|
fmt.Fprintf(w, "</span>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, f.preWrapper.End(false))
|
||||||
|
fmt.Fprint(w, "</td>\n")
|
||||||
|
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD, "width:100%"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "%s", f.preWrapper.Start(true, f.styleAttr(css, chroma.PreWrapper)))
|
||||||
|
|
||||||
|
highlightIndex = 0
|
||||||
|
for index, tokens := range lines {
|
||||||
|
// 1-based line number.
|
||||||
|
line := f.baseLineNumber + index
|
||||||
|
highlight, next := f.shouldHighlight(highlightIndex, line)
|
||||||
|
if next {
|
||||||
|
highlightIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(f.preventSurroundingPre || f.inlineCode) {
|
||||||
|
// Start of Line
|
||||||
|
fmt.Fprint(w, `<span`)
|
||||||
|
|
||||||
|
if highlight {
|
||||||
|
// Line + LineHighlight
|
||||||
|
if f.Classes {
|
||||||
|
fmt.Fprintf(w, ` class="%s %s"`, f.class(chroma.Line), f.class(chroma.LineHighlight))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, ` style="%s %s"`, css[chroma.Line], css[chroma.LineHighlight])
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, `>`)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "%s>", f.styleAttr(css, chroma.Line))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line number
|
||||||
|
if f.lineNumbers && !wrapInTable {
|
||||||
|
fmt.Fprintf(w, "<span%s%s>%s</span>", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(css, lineDigits, line))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, `<span%s>`, f.styleAttr(css, chroma.CodeLine))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, token := range tokens {
|
||||||
|
html := html.EscapeString(token.String())
|
||||||
|
attr := f.styleAttr(css, token.Type)
|
||||||
|
if attr != "" {
|
||||||
|
html = fmt.Sprintf("<span%s>%s</span>", attr, html)
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, html)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(f.preventSurroundingPre || f.inlineCode) {
|
||||||
|
fmt.Fprint(w, `</span>`) // End of CodeLine
|
||||||
|
|
||||||
|
fmt.Fprint(w, `</span>`) // End of Line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s", f.preWrapper.End(true))
|
||||||
|
|
||||||
|
if wrapInTable {
|
||||||
|
fmt.Fprint(w, "</td></tr></table>\n")
|
||||||
|
fmt.Fprint(w, "</div>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.standalone {
|
||||||
|
fmt.Fprint(w, "\n</body>\n")
|
||||||
|
fmt.Fprint(w, "</html>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) lineIDAttribute(line int) string {
|
||||||
|
if !f.linkableLineNumbers {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(" id=\"%s\"", f.lineID(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) lineTitleWithLinkIfNeeded(css map[chroma.TokenType]string, lineDigits, line int) string {
|
||||||
|
title := fmt.Sprintf("%*d", lineDigits, line)
|
||||||
|
if !f.linkableLineNumbers {
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("<a%s href=\"#%s\">%s</a>", f.styleAttr(css, chroma.LineLink), f.lineID(line), title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) lineID(line int) string {
|
||||||
|
return fmt.Sprintf("%s%d", f.lineNumbersIDPrefix, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) shouldHighlight(highlightIndex, line int) (bool, bool) {
|
||||||
|
next := false
|
||||||
|
for highlightIndex < len(f.highlightRanges) && line > f.highlightRanges[highlightIndex][1] {
|
||||||
|
highlightIndex++
|
||||||
|
next = true
|
||||||
|
}
|
||||||
|
if highlightIndex < len(f.highlightRanges) {
|
||||||
|
hrange := f.highlightRanges[highlightIndex]
|
||||||
|
if line >= hrange[0] && line <= hrange[1] {
|
||||||
|
return true, next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, next
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) class(t chroma.TokenType) string {
|
||||||
|
for t != 0 {
|
||||||
|
if cls, ok := chroma.StandardTypes[t]; ok {
|
||||||
|
if cls != "" {
|
||||||
|
return f.prefix + cls
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
t = t.Parent()
|
||||||
|
}
|
||||||
|
if cls := chroma.StandardTypes[t]; cls != "" {
|
||||||
|
return f.prefix + cls
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType, extraCSS ...string) string {
|
||||||
|
if f.Classes {
|
||||||
|
cls := f.class(tt)
|
||||||
|
if cls == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(` class="%s"`, cls)
|
||||||
|
}
|
||||||
|
if _, ok := styles[tt]; !ok {
|
||||||
|
tt = tt.SubCategory()
|
||||||
|
if _, ok := styles[tt]; !ok {
|
||||||
|
tt = tt.Category()
|
||||||
|
if _, ok := styles[tt]; !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
css := []string{styles[tt]}
|
||||||
|
css = append(css, extraCSS...)
|
||||||
|
return fmt.Sprintf(` style="%s"`, strings.Join(css, ";"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) tabWidthStyle() string {
|
||||||
|
if f.tabWidth != 0 && f.tabWidth != 8 {
|
||||||
|
return fmt.Sprintf("-moz-tab-size: %[1]d; -o-tab-size: %[1]d; tab-size: %[1]d;", f.tabWidth)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) writeCSSRule(w io.Writer, comment string, selector string, styles string) error {
|
||||||
|
if styles == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if f.writeCSSComments && comment != "" {
|
||||||
|
if _, err := fmt.Fprintf(w, "/* %s */ ", comment); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(w, "%s { %s }\n", selector, styles); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteCSS writes CSS style definitions (without any surrounding HTML).
|
||||||
|
func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
|
||||||
|
css := f.styleCache.get(style, false)
|
||||||
|
|
||||||
|
// Special-case background as it is mapped to the outer ".chroma" class.
|
||||||
|
if err := f.writeCSSRule(w, chroma.Background.String(), fmt.Sprintf(".%sbg", f.prefix), css[chroma.Background]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Special-case PreWrapper as it is the ".chroma" class.
|
||||||
|
if err := f.writeCSSRule(w, chroma.PreWrapper.String(), fmt.Sprintf(".%schroma", f.prefix), css[chroma.PreWrapper]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Special-case code column of table to expand width.
|
||||||
|
if f.lineNumbers && f.lineNumbersInTable {
|
||||||
|
selector := fmt.Sprintf(".%schroma .%s:last-child", f.prefix, f.class(chroma.LineTableTD))
|
||||||
|
if err := f.writeCSSRule(w, chroma.LineTableTD.String(), selector, "width: 100%;"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Special-case line number highlighting when targeted.
|
||||||
|
if f.lineNumbers || f.lineNumbersInTable {
|
||||||
|
targetedLineCSS := StyleEntryToCSS(style.Get(chroma.LineHighlight))
|
||||||
|
for _, tt := range []chroma.TokenType{chroma.LineNumbers, chroma.LineNumbersTable} {
|
||||||
|
comment := fmt.Sprintf("%s targeted by URL anchor", tt)
|
||||||
|
selector := fmt.Sprintf(".%schroma .%s:target", f.prefix, f.class(tt))
|
||||||
|
if err := f.writeCSSRule(w, comment, selector, targetedLineCSS); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tts := []int{}
|
||||||
|
for tt := range css {
|
||||||
|
tts = append(tts, int(tt))
|
||||||
|
}
|
||||||
|
sort.Ints(tts)
|
||||||
|
for _, ti := range tts {
|
||||||
|
tt := chroma.TokenType(ti)
|
||||||
|
switch tt {
|
||||||
|
case chroma.Background, chroma.PreWrapper:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
class := f.class(tt)
|
||||||
|
if class == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := f.writeCSSRule(w, tt.String(), fmt.Sprintf(".%schroma .%s", f.prefix, class), css[tt]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string {
|
||||||
|
classes := map[chroma.TokenType]string{}
|
||||||
|
bg := style.Get(chroma.Background)
|
||||||
|
// Convert the style.
|
||||||
|
for t := range chroma.StandardTypes {
|
||||||
|
entry := style.Get(t)
|
||||||
|
if t != chroma.Background {
|
||||||
|
entry = entry.Sub(bg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherit from custom CSS provided by user
|
||||||
|
tokenCategory := t.Category()
|
||||||
|
tokenSubCategory := t.SubCategory()
|
||||||
|
if t != tokenCategory {
|
||||||
|
if css, ok := f.customCSS[tokenCategory]; ok {
|
||||||
|
classes[t] = css
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tokenCategory != tokenSubCategory {
|
||||||
|
if css, ok := f.customCSS[tokenSubCategory]; ok {
|
||||||
|
classes[t] += css
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add custom CSS provided by user
|
||||||
|
if css, ok := f.customCSS[t]; ok {
|
||||||
|
classes[t] += css
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.allClasses && entry.IsZero() && classes[t] == `` {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
styleEntryCSS := StyleEntryToCSS(entry)
|
||||||
|
if styleEntryCSS != `` && classes[t] != `` {
|
||||||
|
styleEntryCSS += `;`
|
||||||
|
}
|
||||||
|
classes[t] = styleEntryCSS + classes[t]
|
||||||
|
}
|
||||||
|
classes[chroma.Background] += `;` + f.tabWidthStyle()
|
||||||
|
classes[chroma.PreWrapper] += classes[chroma.Background]
|
||||||
|
classes[chroma.PreWrapper] += ` -webkit-text-size-adjust: none;`
|
||||||
|
// Make PreWrapper a grid to show highlight style with full width.
|
||||||
|
if len(f.highlightRanges) > 0 && f.customCSS[chroma.PreWrapper] == `` {
|
||||||
|
classes[chroma.PreWrapper] += `display: grid;`
|
||||||
|
}
|
||||||
|
// Make PreWrapper wrap long lines.
|
||||||
|
if f.wrapLongLines {
|
||||||
|
classes[chroma.PreWrapper] += `white-space: pre-wrap; word-break: break-word;`
|
||||||
|
}
|
||||||
|
lineNumbersStyle := `white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;`
|
||||||
|
// All rules begin with default rules followed by user provided rules
|
||||||
|
classes[chroma.Line] = `display: flex;` + classes[chroma.Line]
|
||||||
|
classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers]
|
||||||
|
classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable]
|
||||||
|
classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTable]
|
||||||
|
classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD]
|
||||||
|
classes[chroma.LineLink] = "outline: none; text-decoration: none; color: inherit" + classes[chroma.LineLink]
|
||||||
|
return classes
|
||||||
|
}
|
||||||
|
|
||||||
|
// StyleEntryToCSS converts a chroma.StyleEntry to CSS attributes.
|
||||||
|
func StyleEntryToCSS(e chroma.StyleEntry) string {
|
||||||
|
styles := []string{}
|
||||||
|
if e.Colour.IsSet() {
|
||||||
|
styles = append(styles, "color: "+e.Colour.String())
|
||||||
|
}
|
||||||
|
if e.Background.IsSet() {
|
||||||
|
styles = append(styles, "background-color: "+e.Background.String())
|
||||||
|
}
|
||||||
|
if e.Bold == chroma.Yes {
|
||||||
|
styles = append(styles, "font-weight: bold")
|
||||||
|
}
|
||||||
|
if e.Italic == chroma.Yes {
|
||||||
|
styles = append(styles, "font-style: italic")
|
||||||
|
}
|
||||||
|
if e.Underline == chroma.Yes {
|
||||||
|
styles = append(styles, "text-decoration: underline")
|
||||||
|
}
|
||||||
|
return strings.Join(styles, "; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress CSS attributes - remove spaces, transform 6-digit colours to 3.
|
||||||
|
func compressStyle(s string) string {
|
||||||
|
parts := strings.Split(s, ";")
|
||||||
|
out := []string{}
|
||||||
|
for _, p := range parts {
|
||||||
|
p = strings.Join(strings.Fields(p), " ")
|
||||||
|
p = strings.Replace(p, ": ", ":", 1)
|
||||||
|
if strings.Contains(p, "#") {
|
||||||
|
c := p[len(p)-6:]
|
||||||
|
if c[0] == c[1] && c[2] == c[3] && c[4] == c[5] {
|
||||||
|
p = p[:len(p)-6] + c[0:1] + c[2:3] + c[4:5]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
return strings.Join(out, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
const styleCacheLimit = 32
|
||||||
|
|
||||||
|
type styleCacheEntry struct {
|
||||||
|
style *chroma.Style
|
||||||
|
compressed bool
|
||||||
|
cache map[chroma.TokenType]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type styleCache struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
// LRU cache of compiled (and possibly compressed) styles. This is a slice
|
||||||
|
// because the cache size is small, and a slice is sufficiently fast for
|
||||||
|
// small N.
|
||||||
|
cache []styleCacheEntry
|
||||||
|
f *Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStyleCache(f *Formatter) *styleCache {
|
||||||
|
return &styleCache{f: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *styleCache) get(style *chroma.Style, compress bool) map[chroma.TokenType]string {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
// Look for an existing entry.
|
||||||
|
for i := len(l.cache) - 1; i >= 0; i-- {
|
||||||
|
entry := l.cache[i]
|
||||||
|
if entry.style == style && entry.compressed == compress {
|
||||||
|
// Top of the cache, no need to adjust the order.
|
||||||
|
if i == len(l.cache)-1 {
|
||||||
|
return entry.cache
|
||||||
|
}
|
||||||
|
// Move this entry to the end of the LRU
|
||||||
|
copy(l.cache[i:], l.cache[i+1:])
|
||||||
|
l.cache[len(l.cache)-1] = entry
|
||||||
|
return entry.cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No entry, create one.
|
||||||
|
cached := l.f.styleToCSS(style)
|
||||||
|
if !l.f.Classes {
|
||||||
|
for t, style := range cached {
|
||||||
|
cached[t] = compressStyle(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if compress {
|
||||||
|
for t, style := range cached {
|
||||||
|
cached[t] = compressStyle(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Evict the oldest entry.
|
||||||
|
if len(l.cache) >= styleCacheLimit {
|
||||||
|
l.cache = l.cache[0:copy(l.cache, l.cache[1:])]
|
||||||
|
}
|
||||||
|
l.cache = append(l.cache, styleCacheEntry{style: style, cache: cached, compressed: compress})
|
||||||
|
return cached
|
||||||
|
}
|
||||||
39
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/json.go
generated
vendored
Normal file
39
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/json.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSON formatter outputs the raw token structures as JSON.
|
||||||
|
var JSON = Register("json", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, it chroma.Iterator) error {
|
||||||
|
if _, err := fmt.Fprintln(w, "["); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for t := it(); t != chroma.EOF; t = it() {
|
||||||
|
if i > 0 {
|
||||||
|
if _, err := fmt.Fprintln(w, ","); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
bytes, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprint(w, " "+string(bytes)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintln(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintln(w, "]"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
51
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/svg/font_liberation_mono.go
generated
vendored
Normal file
51
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/svg/font_liberation_mono.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
222
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/svg/svg.go
generated
vendored
Normal file
222
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/svg/svg.go
generated
vendored
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
// Package svg contains an SVG formatter.
|
||||||
|
package svg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option sets an option of the SVG formatter.
|
||||||
|
type Option func(f *Formatter)
|
||||||
|
|
||||||
|
// FontFamily sets the font-family.
|
||||||
|
func FontFamily(fontFamily string) Option { return func(f *Formatter) { f.fontFamily = fontFamily } }
|
||||||
|
|
||||||
|
// EmbedFontFile embeds given font file
|
||||||
|
func EmbedFontFile(fontFamily string, fileName string) (option Option, err error) {
|
||||||
|
var format FontFormat
|
||||||
|
switch path.Ext(fileName) {
|
||||||
|
case ".woff":
|
||||||
|
format = WOFF
|
||||||
|
case ".woff2":
|
||||||
|
format = WOFF2
|
||||||
|
case ".ttf":
|
||||||
|
format = TRUETYPE
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unexpected font file suffix")
|
||||||
|
}
|
||||||
|
|
||||||
|
var content []byte
|
||||||
|
if content, err = os.ReadFile(fileName); err == nil {
|
||||||
|
option = EmbedFont(fontFamily, base64.StdEncoding.EncodeToString(content), format)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbedFont embeds given base64 encoded font
|
||||||
|
func EmbedFont(fontFamily string, font string, format FontFormat) Option {
|
||||||
|
return func(f *Formatter) { f.fontFamily = fontFamily; f.embeddedFont = font; f.fontFormat = format }
|
||||||
|
}
|
||||||
|
|
||||||
|
// New SVG formatter.
|
||||||
|
func New(options ...Option) *Formatter {
|
||||||
|
f := &Formatter{fontFamily: "Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace"}
|
||||||
|
for _, option := range options {
|
||||||
|
option(f)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatter that generates SVG.
|
||||||
|
type Formatter struct {
|
||||||
|
fontFamily string
|
||||||
|
embeddedFont string
|
||||||
|
fontFormat FontFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) {
|
||||||
|
f.writeSVG(w, style, iterator.Tokens())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var svgEscaper = strings.NewReplacer(
|
||||||
|
`&`, "&",
|
||||||
|
`<`, "<",
|
||||||
|
`>`, ">",
|
||||||
|
`"`, """,
|
||||||
|
` `, " ",
|
||||||
|
` `, "    ",
|
||||||
|
)
|
||||||
|
|
||||||
|
// EscapeString escapes special characters.
|
||||||
|
func escapeString(s string) string {
|
||||||
|
return svgEscaper.Replace(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) writeSVG(w io.Writer, style *chroma.Style, tokens []chroma.Token) { // nolint: gocyclo
|
||||||
|
svgStyles := f.styleToSVG(style)
|
||||||
|
lines := chroma.SplitTokensIntoLines(tokens)
|
||||||
|
|
||||||
|
fmt.Fprint(w, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
|
||||||
|
fmt.Fprint(w, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
|
||||||
|
fmt.Fprintf(w, "<svg width=\"%dpx\" height=\"%dpx\" xmlns=\"http://www.w3.org/2000/svg\">\n", 8*maxLineWidth(lines), 10+int(16.8*float64(len(lines)+1)))
|
||||||
|
|
||||||
|
if f.embeddedFont != "" {
|
||||||
|
f.writeFontStyle(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "<rect width=\"100%%\" height=\"100%%\" fill=\"%s\"/>\n", style.Get(chroma.Background).Background.String())
|
||||||
|
fmt.Fprintf(w, "<g font-family=\"%s\" font-size=\"14px\" fill=\"%s\">\n", f.fontFamily, style.Get(chroma.Text).Colour.String())
|
||||||
|
|
||||||
|
f.writeTokenBackgrounds(w, lines, style)
|
||||||
|
|
||||||
|
for index, tokens := range lines {
|
||||||
|
fmt.Fprintf(w, "<text x=\"0\" y=\"%fem\" xml:space=\"preserve\">", 1.2*float64(index+1))
|
||||||
|
|
||||||
|
for _, token := range tokens {
|
||||||
|
text := escapeString(token.String())
|
||||||
|
attr := f.styleAttr(svgStyles, token.Type)
|
||||||
|
if attr != "" {
|
||||||
|
text = fmt.Sprintf("<tspan %s>%s</tspan>", attr, text)
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, text)
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, "</text>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(w, "\n</g>\n")
|
||||||
|
fmt.Fprint(w, "</svg>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxLineWidth(lines [][]chroma.Token) int {
|
||||||
|
maxWidth := 0
|
||||||
|
for _, tokens := range lines {
|
||||||
|
length := 0
|
||||||
|
for _, token := range tokens {
|
||||||
|
length += len(strings.ReplaceAll(token.String(), ` `, " "))
|
||||||
|
}
|
||||||
|
if length > maxWidth {
|
||||||
|
maxWidth = length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no background attribute for text in SVG so simply calculate the position and text
|
||||||
|
// of tokens with a background color that differs from the default and add a rectangle for each before
|
||||||
|
// adding the token.
|
||||||
|
func (f *Formatter) writeTokenBackgrounds(w io.Writer, lines [][]chroma.Token, style *chroma.Style) {
|
||||||
|
for index, tokens := range lines {
|
||||||
|
lineLength := 0
|
||||||
|
for _, token := range tokens {
|
||||||
|
length := len(strings.ReplaceAll(token.String(), ` `, " "))
|
||||||
|
tokenBackground := style.Get(token.Type).Background
|
||||||
|
if tokenBackground.IsSet() && tokenBackground != style.Get(chroma.Background).Background {
|
||||||
|
fmt.Fprintf(w, "<rect id=\"%s\" x=\"%dch\" y=\"%fem\" width=\"%dch\" height=\"1.2em\" fill=\"%s\" />\n", escapeString(token.String()), lineLength, 1.2*float64(index)+0.25, length, style.Get(token.Type).Background.String())
|
||||||
|
}
|
||||||
|
lineLength += length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FontFormat int
|
||||||
|
|
||||||
|
// https://transfonter.org/formats
|
||||||
|
const (
|
||||||
|
WOFF FontFormat = iota
|
||||||
|
WOFF2
|
||||||
|
TRUETYPE
|
||||||
|
)
|
||||||
|
|
||||||
|
var fontFormats = [...]string{
|
||||||
|
"woff",
|
||||||
|
"woff2",
|
||||||
|
"truetype",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) writeFontStyle(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, `<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: '%s';
|
||||||
|
src: url(data:application/x-font-%s;charset=utf-8;base64,%s) format('%s');'
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
</style>`, f.fontFamily, fontFormats[f.fontFormat], f.embeddedFont, fontFormats[f.fontFormat])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType) string {
|
||||||
|
if _, ok := styles[tt]; !ok {
|
||||||
|
tt = tt.SubCategory()
|
||||||
|
if _, ok := styles[tt]; !ok {
|
||||||
|
tt = tt.Category()
|
||||||
|
if _, ok := styles[tt]; !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return styles[tt]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) styleToSVG(style *chroma.Style) map[chroma.TokenType]string {
|
||||||
|
converted := map[chroma.TokenType]string{}
|
||||||
|
bg := style.Get(chroma.Background)
|
||||||
|
// Convert the style.
|
||||||
|
for t := range chroma.StandardTypes {
|
||||||
|
entry := style.Get(t)
|
||||||
|
if t != chroma.Background {
|
||||||
|
entry = entry.Sub(bg)
|
||||||
|
}
|
||||||
|
if entry.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
converted[t] = StyleEntryToSVG(entry)
|
||||||
|
}
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
// StyleEntryToSVG converts a chroma.StyleEntry to SVG attributes.
|
||||||
|
func StyleEntryToSVG(e chroma.StyleEntry) string {
|
||||||
|
var styles []string
|
||||||
|
|
||||||
|
if e.Colour.IsSet() {
|
||||||
|
styles = append(styles, "fill=\""+e.Colour.String()+"\"")
|
||||||
|
}
|
||||||
|
if e.Bold == chroma.Yes {
|
||||||
|
styles = append(styles, "font-weight=\"bold\"")
|
||||||
|
}
|
||||||
|
if e.Italic == chroma.Yes {
|
||||||
|
styles = append(styles, "font-style=\"italic\"")
|
||||||
|
}
|
||||||
|
if e.Underline == chroma.Yes {
|
||||||
|
styles = append(styles, "text-decoration=\"underline\"")
|
||||||
|
}
|
||||||
|
return strings.Join(styles, " ")
|
||||||
|
}
|
||||||
18
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/tokens.go
generated
vendored
Normal file
18
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/tokens.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tokens formatter outputs the raw token structures.
|
||||||
|
var Tokens = Register("tokens", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, it chroma.Iterator) error {
|
||||||
|
for t := it(); t != chroma.EOF; t = it() {
|
||||||
|
if _, err := fmt.Fprintln(w, t.GoString()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
284
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/tty_indexed.go
generated
vendored
Normal file
284
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/tty_indexed.go
generated
vendored
Normal file
|
|
@ -0,0 +1,284 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ttyTable struct {
|
||||||
|
foreground map[chroma.Colour]string
|
||||||
|
background map[chroma.Colour]string
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = chroma.MustParseColour
|
||||||
|
|
||||||
|
var ttyTables = map[int]*ttyTable{
|
||||||
|
8: {
|
||||||
|
foreground: map[chroma.Colour]string{
|
||||||
|
c("#000000"): "\033[30m", c("#7f0000"): "\033[31m", c("#007f00"): "\033[32m", c("#7f7fe0"): "\033[33m",
|
||||||
|
c("#00007f"): "\033[34m", c("#7f007f"): "\033[35m", c("#007f7f"): "\033[36m", c("#e5e5e5"): "\033[37m",
|
||||||
|
c("#555555"): "\033[1m\033[30m", c("#ff0000"): "\033[1m\033[31m", c("#00ff00"): "\033[1m\033[32m", c("#ffff00"): "\033[1m\033[33m",
|
||||||
|
c("#0000ff"): "\033[1m\033[34m", c("#ff00ff"): "\033[1m\033[35m", c("#00ffff"): "\033[1m\033[36m", c("#ffffff"): "\033[1m\033[37m",
|
||||||
|
},
|
||||||
|
background: map[chroma.Colour]string{
|
||||||
|
c("#000000"): "\033[40m", c("#7f0000"): "\033[41m", c("#007f00"): "\033[42m", c("#7f7fe0"): "\033[43m",
|
||||||
|
c("#00007f"): "\033[44m", c("#7f007f"): "\033[45m", c("#007f7f"): "\033[46m", c("#e5e5e5"): "\033[47m",
|
||||||
|
c("#555555"): "\033[1m\033[40m", c("#ff0000"): "\033[1m\033[41m", c("#00ff00"): "\033[1m\033[42m", c("#ffff00"): "\033[1m\033[43m",
|
||||||
|
c("#0000ff"): "\033[1m\033[44m", c("#ff00ff"): "\033[1m\033[45m", c("#00ffff"): "\033[1m\033[46m", c("#ffffff"): "\033[1m\033[47m",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
16: {
|
||||||
|
foreground: map[chroma.Colour]string{
|
||||||
|
c("#000000"): "\033[30m", c("#7f0000"): "\033[31m", c("#007f00"): "\033[32m", c("#7f7fe0"): "\033[33m",
|
||||||
|
c("#00007f"): "\033[34m", c("#7f007f"): "\033[35m", c("#007f7f"): "\033[36m", c("#e5e5e5"): "\033[37m",
|
||||||
|
c("#555555"): "\033[90m", c("#ff0000"): "\033[91m", c("#00ff00"): "\033[92m", c("#ffff00"): "\033[93m",
|
||||||
|
c("#0000ff"): "\033[94m", c("#ff00ff"): "\033[95m", c("#00ffff"): "\033[96m", c("#ffffff"): "\033[97m",
|
||||||
|
},
|
||||||
|
background: map[chroma.Colour]string{
|
||||||
|
c("#000000"): "\033[40m", c("#7f0000"): "\033[41m", c("#007f00"): "\033[42m", c("#7f7fe0"): "\033[43m",
|
||||||
|
c("#00007f"): "\033[44m", c("#7f007f"): "\033[45m", c("#007f7f"): "\033[46m", c("#e5e5e5"): "\033[47m",
|
||||||
|
c("#555555"): "\033[100m", c("#ff0000"): "\033[101m", c("#00ff00"): "\033[102m", c("#ffff00"): "\033[103m",
|
||||||
|
c("#0000ff"): "\033[104m", c("#ff00ff"): "\033[105m", c("#00ffff"): "\033[106m", c("#ffffff"): "\033[107m",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
256: {
|
||||||
|
foreground: map[chroma.Colour]string{
|
||||||
|
c("#000000"): "\033[38;5;0m", c("#800000"): "\033[38;5;1m", c("#008000"): "\033[38;5;2m", c("#808000"): "\033[38;5;3m",
|
||||||
|
c("#000080"): "\033[38;5;4m", c("#800080"): "\033[38;5;5m", c("#008080"): "\033[38;5;6m", c("#c0c0c0"): "\033[38;5;7m",
|
||||||
|
c("#808080"): "\033[38;5;8m", c("#ff0000"): "\033[38;5;9m", c("#00ff00"): "\033[38;5;10m", c("#ffff00"): "\033[38;5;11m",
|
||||||
|
c("#0000ff"): "\033[38;5;12m", c("#ff00ff"): "\033[38;5;13m", c("#00ffff"): "\033[38;5;14m", c("#ffffff"): "\033[38;5;15m",
|
||||||
|
c("#000000"): "\033[38;5;16m", c("#00005f"): "\033[38;5;17m", c("#000087"): "\033[38;5;18m", c("#0000af"): "\033[38;5;19m",
|
||||||
|
c("#0000d7"): "\033[38;5;20m", c("#0000ff"): "\033[38;5;21m", c("#005f00"): "\033[38;5;22m", c("#005f5f"): "\033[38;5;23m",
|
||||||
|
c("#005f87"): "\033[38;5;24m", c("#005faf"): "\033[38;5;25m", c("#005fd7"): "\033[38;5;26m", c("#005fff"): "\033[38;5;27m",
|
||||||
|
c("#008700"): "\033[38;5;28m", c("#00875f"): "\033[38;5;29m", c("#008787"): "\033[38;5;30m", c("#0087af"): "\033[38;5;31m",
|
||||||
|
c("#0087d7"): "\033[38;5;32m", c("#0087ff"): "\033[38;5;33m", c("#00af00"): "\033[38;5;34m", c("#00af5f"): "\033[38;5;35m",
|
||||||
|
c("#00af87"): "\033[38;5;36m", c("#00afaf"): "\033[38;5;37m", c("#00afd7"): "\033[38;5;38m", c("#00afff"): "\033[38;5;39m",
|
||||||
|
c("#00d700"): "\033[38;5;40m", c("#00d75f"): "\033[38;5;41m", c("#00d787"): "\033[38;5;42m", c("#00d7af"): "\033[38;5;43m",
|
||||||
|
c("#00d7d7"): "\033[38;5;44m", c("#00d7ff"): "\033[38;5;45m", c("#00ff00"): "\033[38;5;46m", c("#00ff5f"): "\033[38;5;47m",
|
||||||
|
c("#00ff87"): "\033[38;5;48m", c("#00ffaf"): "\033[38;5;49m", c("#00ffd7"): "\033[38;5;50m", c("#00ffff"): "\033[38;5;51m",
|
||||||
|
c("#5f0000"): "\033[38;5;52m", c("#5f005f"): "\033[38;5;53m", c("#5f0087"): "\033[38;5;54m", c("#5f00af"): "\033[38;5;55m",
|
||||||
|
c("#5f00d7"): "\033[38;5;56m", c("#5f00ff"): "\033[38;5;57m", c("#5f5f00"): "\033[38;5;58m", c("#5f5f5f"): "\033[38;5;59m",
|
||||||
|
c("#5f5f87"): "\033[38;5;60m", c("#5f5faf"): "\033[38;5;61m", c("#5f5fd7"): "\033[38;5;62m", c("#5f5fff"): "\033[38;5;63m",
|
||||||
|
c("#5f8700"): "\033[38;5;64m", c("#5f875f"): "\033[38;5;65m", c("#5f8787"): "\033[38;5;66m", c("#5f87af"): "\033[38;5;67m",
|
||||||
|
c("#5f87d7"): "\033[38;5;68m", c("#5f87ff"): "\033[38;5;69m", c("#5faf00"): "\033[38;5;70m", c("#5faf5f"): "\033[38;5;71m",
|
||||||
|
c("#5faf87"): "\033[38;5;72m", c("#5fafaf"): "\033[38;5;73m", c("#5fafd7"): "\033[38;5;74m", c("#5fafff"): "\033[38;5;75m",
|
||||||
|
c("#5fd700"): "\033[38;5;76m", c("#5fd75f"): "\033[38;5;77m", c("#5fd787"): "\033[38;5;78m", c("#5fd7af"): "\033[38;5;79m",
|
||||||
|
c("#5fd7d7"): "\033[38;5;80m", c("#5fd7ff"): "\033[38;5;81m", c("#5fff00"): "\033[38;5;82m", c("#5fff5f"): "\033[38;5;83m",
|
||||||
|
c("#5fff87"): "\033[38;5;84m", c("#5fffaf"): "\033[38;5;85m", c("#5fffd7"): "\033[38;5;86m", c("#5fffff"): "\033[38;5;87m",
|
||||||
|
c("#870000"): "\033[38;5;88m", c("#87005f"): "\033[38;5;89m", c("#870087"): "\033[38;5;90m", c("#8700af"): "\033[38;5;91m",
|
||||||
|
c("#8700d7"): "\033[38;5;92m", c("#8700ff"): "\033[38;5;93m", c("#875f00"): "\033[38;5;94m", c("#875f5f"): "\033[38;5;95m",
|
||||||
|
c("#875f87"): "\033[38;5;96m", c("#875faf"): "\033[38;5;97m", c("#875fd7"): "\033[38;5;98m", c("#875fff"): "\033[38;5;99m",
|
||||||
|
c("#878700"): "\033[38;5;100m", c("#87875f"): "\033[38;5;101m", c("#878787"): "\033[38;5;102m", c("#8787af"): "\033[38;5;103m",
|
||||||
|
c("#8787d7"): "\033[38;5;104m", c("#8787ff"): "\033[38;5;105m", c("#87af00"): "\033[38;5;106m", c("#87af5f"): "\033[38;5;107m",
|
||||||
|
c("#87af87"): "\033[38;5;108m", c("#87afaf"): "\033[38;5;109m", c("#87afd7"): "\033[38;5;110m", c("#87afff"): "\033[38;5;111m",
|
||||||
|
c("#87d700"): "\033[38;5;112m", c("#87d75f"): "\033[38;5;113m", c("#87d787"): "\033[38;5;114m", c("#87d7af"): "\033[38;5;115m",
|
||||||
|
c("#87d7d7"): "\033[38;5;116m", c("#87d7ff"): "\033[38;5;117m", c("#87ff00"): "\033[38;5;118m", c("#87ff5f"): "\033[38;5;119m",
|
||||||
|
c("#87ff87"): "\033[38;5;120m", c("#87ffaf"): "\033[38;5;121m", c("#87ffd7"): "\033[38;5;122m", c("#87ffff"): "\033[38;5;123m",
|
||||||
|
c("#af0000"): "\033[38;5;124m", c("#af005f"): "\033[38;5;125m", c("#af0087"): "\033[38;5;126m", c("#af00af"): "\033[38;5;127m",
|
||||||
|
c("#af00d7"): "\033[38;5;128m", c("#af00ff"): "\033[38;5;129m", c("#af5f00"): "\033[38;5;130m", c("#af5f5f"): "\033[38;5;131m",
|
||||||
|
c("#af5f87"): "\033[38;5;132m", c("#af5faf"): "\033[38;5;133m", c("#af5fd7"): "\033[38;5;134m", c("#af5fff"): "\033[38;5;135m",
|
||||||
|
c("#af8700"): "\033[38;5;136m", c("#af875f"): "\033[38;5;137m", c("#af8787"): "\033[38;5;138m", c("#af87af"): "\033[38;5;139m",
|
||||||
|
c("#af87d7"): "\033[38;5;140m", c("#af87ff"): "\033[38;5;141m", c("#afaf00"): "\033[38;5;142m", c("#afaf5f"): "\033[38;5;143m",
|
||||||
|
c("#afaf87"): "\033[38;5;144m", c("#afafaf"): "\033[38;5;145m", c("#afafd7"): "\033[38;5;146m", c("#afafff"): "\033[38;5;147m",
|
||||||
|
c("#afd700"): "\033[38;5;148m", c("#afd75f"): "\033[38;5;149m", c("#afd787"): "\033[38;5;150m", c("#afd7af"): "\033[38;5;151m",
|
||||||
|
c("#afd7d7"): "\033[38;5;152m", c("#afd7ff"): "\033[38;5;153m", c("#afff00"): "\033[38;5;154m", c("#afff5f"): "\033[38;5;155m",
|
||||||
|
c("#afff87"): "\033[38;5;156m", c("#afffaf"): "\033[38;5;157m", c("#afffd7"): "\033[38;5;158m", c("#afffff"): "\033[38;5;159m",
|
||||||
|
c("#d70000"): "\033[38;5;160m", c("#d7005f"): "\033[38;5;161m", c("#d70087"): "\033[38;5;162m", c("#d700af"): "\033[38;5;163m",
|
||||||
|
c("#d700d7"): "\033[38;5;164m", c("#d700ff"): "\033[38;5;165m", c("#d75f00"): "\033[38;5;166m", c("#d75f5f"): "\033[38;5;167m",
|
||||||
|
c("#d75f87"): "\033[38;5;168m", c("#d75faf"): "\033[38;5;169m", c("#d75fd7"): "\033[38;5;170m", c("#d75fff"): "\033[38;5;171m",
|
||||||
|
c("#d78700"): "\033[38;5;172m", c("#d7875f"): "\033[38;5;173m", c("#d78787"): "\033[38;5;174m", c("#d787af"): "\033[38;5;175m",
|
||||||
|
c("#d787d7"): "\033[38;5;176m", c("#d787ff"): "\033[38;5;177m", c("#d7af00"): "\033[38;5;178m", c("#d7af5f"): "\033[38;5;179m",
|
||||||
|
c("#d7af87"): "\033[38;5;180m", c("#d7afaf"): "\033[38;5;181m", c("#d7afd7"): "\033[38;5;182m", c("#d7afff"): "\033[38;5;183m",
|
||||||
|
c("#d7d700"): "\033[38;5;184m", c("#d7d75f"): "\033[38;5;185m", c("#d7d787"): "\033[38;5;186m", c("#d7d7af"): "\033[38;5;187m",
|
||||||
|
c("#d7d7d7"): "\033[38;5;188m", c("#d7d7ff"): "\033[38;5;189m", c("#d7ff00"): "\033[38;5;190m", c("#d7ff5f"): "\033[38;5;191m",
|
||||||
|
c("#d7ff87"): "\033[38;5;192m", c("#d7ffaf"): "\033[38;5;193m", c("#d7ffd7"): "\033[38;5;194m", c("#d7ffff"): "\033[38;5;195m",
|
||||||
|
c("#ff0000"): "\033[38;5;196m", c("#ff005f"): "\033[38;5;197m", c("#ff0087"): "\033[38;5;198m", c("#ff00af"): "\033[38;5;199m",
|
||||||
|
c("#ff00d7"): "\033[38;5;200m", c("#ff00ff"): "\033[38;5;201m", c("#ff5f00"): "\033[38;5;202m", c("#ff5f5f"): "\033[38;5;203m",
|
||||||
|
c("#ff5f87"): "\033[38;5;204m", c("#ff5faf"): "\033[38;5;205m", c("#ff5fd7"): "\033[38;5;206m", c("#ff5fff"): "\033[38;5;207m",
|
||||||
|
c("#ff8700"): "\033[38;5;208m", c("#ff875f"): "\033[38;5;209m", c("#ff8787"): "\033[38;5;210m", c("#ff87af"): "\033[38;5;211m",
|
||||||
|
c("#ff87d7"): "\033[38;5;212m", c("#ff87ff"): "\033[38;5;213m", c("#ffaf00"): "\033[38;5;214m", c("#ffaf5f"): "\033[38;5;215m",
|
||||||
|
c("#ffaf87"): "\033[38;5;216m", c("#ffafaf"): "\033[38;5;217m", c("#ffafd7"): "\033[38;5;218m", c("#ffafff"): "\033[38;5;219m",
|
||||||
|
c("#ffd700"): "\033[38;5;220m", c("#ffd75f"): "\033[38;5;221m", c("#ffd787"): "\033[38;5;222m", c("#ffd7af"): "\033[38;5;223m",
|
||||||
|
c("#ffd7d7"): "\033[38;5;224m", c("#ffd7ff"): "\033[38;5;225m", c("#ffff00"): "\033[38;5;226m", c("#ffff5f"): "\033[38;5;227m",
|
||||||
|
c("#ffff87"): "\033[38;5;228m", c("#ffffaf"): "\033[38;5;229m", c("#ffffd7"): "\033[38;5;230m", c("#ffffff"): "\033[38;5;231m",
|
||||||
|
c("#080808"): "\033[38;5;232m", c("#121212"): "\033[38;5;233m", c("#1c1c1c"): "\033[38;5;234m", c("#262626"): "\033[38;5;235m",
|
||||||
|
c("#303030"): "\033[38;5;236m", c("#3a3a3a"): "\033[38;5;237m", c("#444444"): "\033[38;5;238m", c("#4e4e4e"): "\033[38;5;239m",
|
||||||
|
c("#585858"): "\033[38;5;240m", c("#626262"): "\033[38;5;241m", c("#6c6c6c"): "\033[38;5;242m", c("#767676"): "\033[38;5;243m",
|
||||||
|
c("#808080"): "\033[38;5;244m", c("#8a8a8a"): "\033[38;5;245m", c("#949494"): "\033[38;5;246m", c("#9e9e9e"): "\033[38;5;247m",
|
||||||
|
c("#a8a8a8"): "\033[38;5;248m", c("#b2b2b2"): "\033[38;5;249m", c("#bcbcbc"): "\033[38;5;250m", c("#c6c6c6"): "\033[38;5;251m",
|
||||||
|
c("#d0d0d0"): "\033[38;5;252m", c("#dadada"): "\033[38;5;253m", c("#e4e4e4"): "\033[38;5;254m", c("#eeeeee"): "\033[38;5;255m",
|
||||||
|
},
|
||||||
|
background: map[chroma.Colour]string{
|
||||||
|
c("#000000"): "\033[48;5;0m", c("#800000"): "\033[48;5;1m", c("#008000"): "\033[48;5;2m", c("#808000"): "\033[48;5;3m",
|
||||||
|
c("#000080"): "\033[48;5;4m", c("#800080"): "\033[48;5;5m", c("#008080"): "\033[48;5;6m", c("#c0c0c0"): "\033[48;5;7m",
|
||||||
|
c("#808080"): "\033[48;5;8m", c("#ff0000"): "\033[48;5;9m", c("#00ff00"): "\033[48;5;10m", c("#ffff00"): "\033[48;5;11m",
|
||||||
|
c("#0000ff"): "\033[48;5;12m", c("#ff00ff"): "\033[48;5;13m", c("#00ffff"): "\033[48;5;14m", c("#ffffff"): "\033[48;5;15m",
|
||||||
|
c("#000000"): "\033[48;5;16m", c("#00005f"): "\033[48;5;17m", c("#000087"): "\033[48;5;18m", c("#0000af"): "\033[48;5;19m",
|
||||||
|
c("#0000d7"): "\033[48;5;20m", c("#0000ff"): "\033[48;5;21m", c("#005f00"): "\033[48;5;22m", c("#005f5f"): "\033[48;5;23m",
|
||||||
|
c("#005f87"): "\033[48;5;24m", c("#005faf"): "\033[48;5;25m", c("#005fd7"): "\033[48;5;26m", c("#005fff"): "\033[48;5;27m",
|
||||||
|
c("#008700"): "\033[48;5;28m", c("#00875f"): "\033[48;5;29m", c("#008787"): "\033[48;5;30m", c("#0087af"): "\033[48;5;31m",
|
||||||
|
c("#0087d7"): "\033[48;5;32m", c("#0087ff"): "\033[48;5;33m", c("#00af00"): "\033[48;5;34m", c("#00af5f"): "\033[48;5;35m",
|
||||||
|
c("#00af87"): "\033[48;5;36m", c("#00afaf"): "\033[48;5;37m", c("#00afd7"): "\033[48;5;38m", c("#00afff"): "\033[48;5;39m",
|
||||||
|
c("#00d700"): "\033[48;5;40m", c("#00d75f"): "\033[48;5;41m", c("#00d787"): "\033[48;5;42m", c("#00d7af"): "\033[48;5;43m",
|
||||||
|
c("#00d7d7"): "\033[48;5;44m", c("#00d7ff"): "\033[48;5;45m", c("#00ff00"): "\033[48;5;46m", c("#00ff5f"): "\033[48;5;47m",
|
||||||
|
c("#00ff87"): "\033[48;5;48m", c("#00ffaf"): "\033[48;5;49m", c("#00ffd7"): "\033[48;5;50m", c("#00ffff"): "\033[48;5;51m",
|
||||||
|
c("#5f0000"): "\033[48;5;52m", c("#5f005f"): "\033[48;5;53m", c("#5f0087"): "\033[48;5;54m", c("#5f00af"): "\033[48;5;55m",
|
||||||
|
c("#5f00d7"): "\033[48;5;56m", c("#5f00ff"): "\033[48;5;57m", c("#5f5f00"): "\033[48;5;58m", c("#5f5f5f"): "\033[48;5;59m",
|
||||||
|
c("#5f5f87"): "\033[48;5;60m", c("#5f5faf"): "\033[48;5;61m", c("#5f5fd7"): "\033[48;5;62m", c("#5f5fff"): "\033[48;5;63m",
|
||||||
|
c("#5f8700"): "\033[48;5;64m", c("#5f875f"): "\033[48;5;65m", c("#5f8787"): "\033[48;5;66m", c("#5f87af"): "\033[48;5;67m",
|
||||||
|
c("#5f87d7"): "\033[48;5;68m", c("#5f87ff"): "\033[48;5;69m", c("#5faf00"): "\033[48;5;70m", c("#5faf5f"): "\033[48;5;71m",
|
||||||
|
c("#5faf87"): "\033[48;5;72m", c("#5fafaf"): "\033[48;5;73m", c("#5fafd7"): "\033[48;5;74m", c("#5fafff"): "\033[48;5;75m",
|
||||||
|
c("#5fd700"): "\033[48;5;76m", c("#5fd75f"): "\033[48;5;77m", c("#5fd787"): "\033[48;5;78m", c("#5fd7af"): "\033[48;5;79m",
|
||||||
|
c("#5fd7d7"): "\033[48;5;80m", c("#5fd7ff"): "\033[48;5;81m", c("#5fff00"): "\033[48;5;82m", c("#5fff5f"): "\033[48;5;83m",
|
||||||
|
c("#5fff87"): "\033[48;5;84m", c("#5fffaf"): "\033[48;5;85m", c("#5fffd7"): "\033[48;5;86m", c("#5fffff"): "\033[48;5;87m",
|
||||||
|
c("#870000"): "\033[48;5;88m", c("#87005f"): "\033[48;5;89m", c("#870087"): "\033[48;5;90m", c("#8700af"): "\033[48;5;91m",
|
||||||
|
c("#8700d7"): "\033[48;5;92m", c("#8700ff"): "\033[48;5;93m", c("#875f00"): "\033[48;5;94m", c("#875f5f"): "\033[48;5;95m",
|
||||||
|
c("#875f87"): "\033[48;5;96m", c("#875faf"): "\033[48;5;97m", c("#875fd7"): "\033[48;5;98m", c("#875fff"): "\033[48;5;99m",
|
||||||
|
c("#878700"): "\033[48;5;100m", c("#87875f"): "\033[48;5;101m", c("#878787"): "\033[48;5;102m", c("#8787af"): "\033[48;5;103m",
|
||||||
|
c("#8787d7"): "\033[48;5;104m", c("#8787ff"): "\033[48;5;105m", c("#87af00"): "\033[48;5;106m", c("#87af5f"): "\033[48;5;107m",
|
||||||
|
c("#87af87"): "\033[48;5;108m", c("#87afaf"): "\033[48;5;109m", c("#87afd7"): "\033[48;5;110m", c("#87afff"): "\033[48;5;111m",
|
||||||
|
c("#87d700"): "\033[48;5;112m", c("#87d75f"): "\033[48;5;113m", c("#87d787"): "\033[48;5;114m", c("#87d7af"): "\033[48;5;115m",
|
||||||
|
c("#87d7d7"): "\033[48;5;116m", c("#87d7ff"): "\033[48;5;117m", c("#87ff00"): "\033[48;5;118m", c("#87ff5f"): "\033[48;5;119m",
|
||||||
|
c("#87ff87"): "\033[48;5;120m", c("#87ffaf"): "\033[48;5;121m", c("#87ffd7"): "\033[48;5;122m", c("#87ffff"): "\033[48;5;123m",
|
||||||
|
c("#af0000"): "\033[48;5;124m", c("#af005f"): "\033[48;5;125m", c("#af0087"): "\033[48;5;126m", c("#af00af"): "\033[48;5;127m",
|
||||||
|
c("#af00d7"): "\033[48;5;128m", c("#af00ff"): "\033[48;5;129m", c("#af5f00"): "\033[48;5;130m", c("#af5f5f"): "\033[48;5;131m",
|
||||||
|
c("#af5f87"): "\033[48;5;132m", c("#af5faf"): "\033[48;5;133m", c("#af5fd7"): "\033[48;5;134m", c("#af5fff"): "\033[48;5;135m",
|
||||||
|
c("#af8700"): "\033[48;5;136m", c("#af875f"): "\033[48;5;137m", c("#af8787"): "\033[48;5;138m", c("#af87af"): "\033[48;5;139m",
|
||||||
|
c("#af87d7"): "\033[48;5;140m", c("#af87ff"): "\033[48;5;141m", c("#afaf00"): "\033[48;5;142m", c("#afaf5f"): "\033[48;5;143m",
|
||||||
|
c("#afaf87"): "\033[48;5;144m", c("#afafaf"): "\033[48;5;145m", c("#afafd7"): "\033[48;5;146m", c("#afafff"): "\033[48;5;147m",
|
||||||
|
c("#afd700"): "\033[48;5;148m", c("#afd75f"): "\033[48;5;149m", c("#afd787"): "\033[48;5;150m", c("#afd7af"): "\033[48;5;151m",
|
||||||
|
c("#afd7d7"): "\033[48;5;152m", c("#afd7ff"): "\033[48;5;153m", c("#afff00"): "\033[48;5;154m", c("#afff5f"): "\033[48;5;155m",
|
||||||
|
c("#afff87"): "\033[48;5;156m", c("#afffaf"): "\033[48;5;157m", c("#afffd7"): "\033[48;5;158m", c("#afffff"): "\033[48;5;159m",
|
||||||
|
c("#d70000"): "\033[48;5;160m", c("#d7005f"): "\033[48;5;161m", c("#d70087"): "\033[48;5;162m", c("#d700af"): "\033[48;5;163m",
|
||||||
|
c("#d700d7"): "\033[48;5;164m", c("#d700ff"): "\033[48;5;165m", c("#d75f00"): "\033[48;5;166m", c("#d75f5f"): "\033[48;5;167m",
|
||||||
|
c("#d75f87"): "\033[48;5;168m", c("#d75faf"): "\033[48;5;169m", c("#d75fd7"): "\033[48;5;170m", c("#d75fff"): "\033[48;5;171m",
|
||||||
|
c("#d78700"): "\033[48;5;172m", c("#d7875f"): "\033[48;5;173m", c("#d78787"): "\033[48;5;174m", c("#d787af"): "\033[48;5;175m",
|
||||||
|
c("#d787d7"): "\033[48;5;176m", c("#d787ff"): "\033[48;5;177m", c("#d7af00"): "\033[48;5;178m", c("#d7af5f"): "\033[48;5;179m",
|
||||||
|
c("#d7af87"): "\033[48;5;180m", c("#d7afaf"): "\033[48;5;181m", c("#d7afd7"): "\033[48;5;182m", c("#d7afff"): "\033[48;5;183m",
|
||||||
|
c("#d7d700"): "\033[48;5;184m", c("#d7d75f"): "\033[48;5;185m", c("#d7d787"): "\033[48;5;186m", c("#d7d7af"): "\033[48;5;187m",
|
||||||
|
c("#d7d7d7"): "\033[48;5;188m", c("#d7d7ff"): "\033[48;5;189m", c("#d7ff00"): "\033[48;5;190m", c("#d7ff5f"): "\033[48;5;191m",
|
||||||
|
c("#d7ff87"): "\033[48;5;192m", c("#d7ffaf"): "\033[48;5;193m", c("#d7ffd7"): "\033[48;5;194m", c("#d7ffff"): "\033[48;5;195m",
|
||||||
|
c("#ff0000"): "\033[48;5;196m", c("#ff005f"): "\033[48;5;197m", c("#ff0087"): "\033[48;5;198m", c("#ff00af"): "\033[48;5;199m",
|
||||||
|
c("#ff00d7"): "\033[48;5;200m", c("#ff00ff"): "\033[48;5;201m", c("#ff5f00"): "\033[48;5;202m", c("#ff5f5f"): "\033[48;5;203m",
|
||||||
|
c("#ff5f87"): "\033[48;5;204m", c("#ff5faf"): "\033[48;5;205m", c("#ff5fd7"): "\033[48;5;206m", c("#ff5fff"): "\033[48;5;207m",
|
||||||
|
c("#ff8700"): "\033[48;5;208m", c("#ff875f"): "\033[48;5;209m", c("#ff8787"): "\033[48;5;210m", c("#ff87af"): "\033[48;5;211m",
|
||||||
|
c("#ff87d7"): "\033[48;5;212m", c("#ff87ff"): "\033[48;5;213m", c("#ffaf00"): "\033[48;5;214m", c("#ffaf5f"): "\033[48;5;215m",
|
||||||
|
c("#ffaf87"): "\033[48;5;216m", c("#ffafaf"): "\033[48;5;217m", c("#ffafd7"): "\033[48;5;218m", c("#ffafff"): "\033[48;5;219m",
|
||||||
|
c("#ffd700"): "\033[48;5;220m", c("#ffd75f"): "\033[48;5;221m", c("#ffd787"): "\033[48;5;222m", c("#ffd7af"): "\033[48;5;223m",
|
||||||
|
c("#ffd7d7"): "\033[48;5;224m", c("#ffd7ff"): "\033[48;5;225m", c("#ffff00"): "\033[48;5;226m", c("#ffff5f"): "\033[48;5;227m",
|
||||||
|
c("#ffff87"): "\033[48;5;228m", c("#ffffaf"): "\033[48;5;229m", c("#ffffd7"): "\033[48;5;230m", c("#ffffff"): "\033[48;5;231m",
|
||||||
|
c("#080808"): "\033[48;5;232m", c("#121212"): "\033[48;5;233m", c("#1c1c1c"): "\033[48;5;234m", c("#262626"): "\033[48;5;235m",
|
||||||
|
c("#303030"): "\033[48;5;236m", c("#3a3a3a"): "\033[48;5;237m", c("#444444"): "\033[48;5;238m", c("#4e4e4e"): "\033[48;5;239m",
|
||||||
|
c("#585858"): "\033[48;5;240m", c("#626262"): "\033[48;5;241m", c("#6c6c6c"): "\033[48;5;242m", c("#767676"): "\033[48;5;243m",
|
||||||
|
c("#808080"): "\033[48;5;244m", c("#8a8a8a"): "\033[48;5;245m", c("#949494"): "\033[48;5;246m", c("#9e9e9e"): "\033[48;5;247m",
|
||||||
|
c("#a8a8a8"): "\033[48;5;248m", c("#b2b2b2"): "\033[48;5;249m", c("#bcbcbc"): "\033[48;5;250m", c("#c6c6c6"): "\033[48;5;251m",
|
||||||
|
c("#d0d0d0"): "\033[48;5;252m", c("#dadada"): "\033[48;5;253m", c("#e4e4e4"): "\033[48;5;254m", c("#eeeeee"): "\033[48;5;255m",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func entryToEscapeSequence(table *ttyTable, entry chroma.StyleEntry) string {
|
||||||
|
out := ""
|
||||||
|
if entry.Bold == chroma.Yes {
|
||||||
|
out += "\033[1m"
|
||||||
|
}
|
||||||
|
if entry.Underline == chroma.Yes {
|
||||||
|
out += "\033[4m"
|
||||||
|
}
|
||||||
|
if entry.Italic == chroma.Yes {
|
||||||
|
out += "\033[3m"
|
||||||
|
}
|
||||||
|
if entry.Colour.IsSet() {
|
||||||
|
out += table.foreground[findClosest(table, entry.Colour)]
|
||||||
|
}
|
||||||
|
if entry.Background.IsSet() {
|
||||||
|
out += table.background[findClosest(table, entry.Background)]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func findClosest(table *ttyTable, seeking chroma.Colour) chroma.Colour {
|
||||||
|
closestColour := chroma.Colour(0)
|
||||||
|
closest := float64(math.MaxFloat64)
|
||||||
|
for colour := range table.foreground {
|
||||||
|
distance := colour.Distance(seeking)
|
||||||
|
if distance < closest {
|
||||||
|
closest = distance
|
||||||
|
closestColour = colour
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestColour
|
||||||
|
}
|
||||||
|
|
||||||
|
func styleToEscapeSequence(table *ttyTable, style *chroma.Style) map[chroma.TokenType]string {
|
||||||
|
style = clearBackground(style)
|
||||||
|
out := map[chroma.TokenType]string{}
|
||||||
|
for _, ttype := range style.Types() {
|
||||||
|
entry := style.Get(ttype)
|
||||||
|
out[ttype] = entryToEscapeSequence(table, entry)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the background colour.
|
||||||
|
func clearBackground(style *chroma.Style) *chroma.Style {
|
||||||
|
builder := style.Builder()
|
||||||
|
bg := builder.Get(chroma.Background)
|
||||||
|
bg.Background = 0
|
||||||
|
bg.NoInherit = true
|
||||||
|
builder.AddEntry(chroma.Background, bg)
|
||||||
|
style, _ = builder.Build()
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexedTTYFormatter struct {
|
||||||
|
table *ttyTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) (err error) {
|
||||||
|
theme := styleToEscapeSequence(c.table, style)
|
||||||
|
for token := it(); token != chroma.EOF; token = it() {
|
||||||
|
clr, ok := theme[token.Type]
|
||||||
|
|
||||||
|
// This search mimics how styles.Get() is used in tty_truecolour.go.
|
||||||
|
if !ok {
|
||||||
|
clr, ok = theme[token.Type.SubCategory()]
|
||||||
|
if !ok {
|
||||||
|
clr, ok = theme[token.Type.Category()]
|
||||||
|
if !ok {
|
||||||
|
clr, ok = theme[chroma.Text]
|
||||||
|
if !ok {
|
||||||
|
clr = theme[chroma.Background]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToken(w, clr, token.Value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TTY is an 8-colour terminal formatter.
|
||||||
|
//
|
||||||
|
// The Lab colour space is used to map RGB values to the most appropriate index colour.
|
||||||
|
var TTY = Register("terminal", &indexedTTYFormatter{ttyTables[8]})
|
||||||
|
|
||||||
|
// TTY8 is an 8-colour terminal formatter.
|
||||||
|
//
|
||||||
|
// The Lab colour space is used to map RGB values to the most appropriate index colour.
|
||||||
|
var TTY8 = Register("terminal8", &indexedTTYFormatter{ttyTables[8]})
|
||||||
|
|
||||||
|
// TTY16 is a 16-colour terminal formatter.
|
||||||
|
//
|
||||||
|
// It uses \033[3xm for normal colours and \033[90Xm for bright colours.
|
||||||
|
//
|
||||||
|
// The Lab colour space is used to map RGB values to the most appropriate index colour.
|
||||||
|
var TTY16 = Register("terminal16", &indexedTTYFormatter{ttyTables[16]})
|
||||||
|
|
||||||
|
// TTY256 is a 256-colour terminal formatter.
|
||||||
|
//
|
||||||
|
// The Lab colour space is used to map RGB values to the most appropriate index colour.
|
||||||
|
var TTY256 = Register("terminal256", &indexedTTYFormatter{ttyTables[256]})
|
||||||
76
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/tty_truecolour.go
generated
vendored
Normal file
76
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/formatters/tty_truecolour.go
generated
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TTY16m is a true-colour terminal formatter.
|
||||||
|
var TTY16m = Register("terminal16m", chroma.FormatterFunc(trueColourFormatter))
|
||||||
|
|
||||||
|
var crOrCrLf = regexp.MustCompile(`\r?\n`)
|
||||||
|
|
||||||
|
// Print the text with the given formatting, resetting the formatting at the end
|
||||||
|
// of each line and resuming it on the next line.
|
||||||
|
//
|
||||||
|
// This way, a pager (like https://github.com/walles/moar for example) can show
|
||||||
|
// any line in the output by itself, and it will get the right formatting.
|
||||||
|
func writeToken(w io.Writer, formatting string, text string) {
|
||||||
|
if formatting == "" {
|
||||||
|
fmt.Fprint(w, text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newlineIndices := crOrCrLf.FindAllStringIndex(text, -1)
|
||||||
|
|
||||||
|
afterLastNewline := 0
|
||||||
|
for _, indices := range newlineIndices {
|
||||||
|
newlineStart, afterNewline := indices[0], indices[1]
|
||||||
|
fmt.Fprint(w, formatting)
|
||||||
|
fmt.Fprint(w, text[afterLastNewline:newlineStart])
|
||||||
|
fmt.Fprint(w, "\033[0m")
|
||||||
|
fmt.Fprint(w, text[newlineStart:afterNewline])
|
||||||
|
afterLastNewline = afterNewline
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterLastNewline < len(text) {
|
||||||
|
// Print whatever is left after the last newline
|
||||||
|
fmt.Fprint(w, formatting)
|
||||||
|
fmt.Fprint(w, text[afterLastNewline:])
|
||||||
|
fmt.Fprint(w, "\033[0m")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trueColourFormatter(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
|
||||||
|
style = clearBackground(style)
|
||||||
|
for token := it(); token != chroma.EOF; token = it() {
|
||||||
|
entry := style.Get(token.Type)
|
||||||
|
if entry.IsZero() {
|
||||||
|
fmt.Fprint(w, token.Value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
formatting := ""
|
||||||
|
if entry.Bold == chroma.Yes {
|
||||||
|
formatting += "\033[1m"
|
||||||
|
}
|
||||||
|
if entry.Underline == chroma.Yes {
|
||||||
|
formatting += "\033[4m"
|
||||||
|
}
|
||||||
|
if entry.Italic == chroma.Yes {
|
||||||
|
formatting += "\033[3m"
|
||||||
|
}
|
||||||
|
if entry.Colour.IsSet() {
|
||||||
|
formatting += fmt.Sprintf("\033[38;2;%d;%d;%dm", entry.Colour.Red(), entry.Colour.Green(), entry.Colour.Blue())
|
||||||
|
}
|
||||||
|
if entry.Background.IsSet() {
|
||||||
|
formatting += fmt.Sprintf("\033[48;2;%d;%d;%dm", entry.Background.Red(), entry.Background.Green(), entry.Background.Blue())
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToken(w, formatting, token.Value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
93
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/iterator.go
generated
vendored
Normal file
93
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/iterator.go
generated
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
package chroma
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// An Iterator across tokens.
|
||||||
|
//
|
||||||
|
// EOF will be returned at the end of the Token stream.
|
||||||
|
//
|
||||||
|
// If an error occurs within an Iterator, it may propagate this in a panic. Formatters should recover.
|
||||||
|
type Iterator func() Token
|
||||||
|
|
||||||
|
// Tokens consumes all tokens from the iterator and returns them as a slice.
|
||||||
|
func (i Iterator) Tokens() []Token {
|
||||||
|
var out []Token
|
||||||
|
for t := i(); t != EOF; t = i() {
|
||||||
|
out = append(out, t)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stdlib converts a Chroma iterator to a Go 1.23-compatible iterator.
|
||||||
|
func (i Iterator) Stdlib() func(yield func(Token) bool) {
|
||||||
|
return func(yield func(Token) bool) {
|
||||||
|
for t := i(); t != EOF; t = i() {
|
||||||
|
if !yield(t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concaterator concatenates tokens from a series of iterators.
|
||||||
|
func Concaterator(iterators ...Iterator) Iterator {
|
||||||
|
return func() Token {
|
||||||
|
for len(iterators) > 0 {
|
||||||
|
t := iterators[0]()
|
||||||
|
if t != EOF {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
iterators = iterators[1:]
|
||||||
|
}
|
||||||
|
return EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Literator converts a sequence of literal Tokens into an Iterator.
|
||||||
|
func Literator(tokens ...Token) Iterator {
|
||||||
|
return func() Token {
|
||||||
|
if len(tokens) == 0 {
|
||||||
|
return EOF
|
||||||
|
}
|
||||||
|
token := tokens[0]
|
||||||
|
tokens = tokens[1:]
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitTokensIntoLines splits tokens containing newlines in two.
|
||||||
|
func SplitTokensIntoLines(tokens []Token) (out [][]Token) {
|
||||||
|
var line []Token // nolint: prealloc
|
||||||
|
tokenLoop:
|
||||||
|
for _, token := range tokens {
|
||||||
|
for strings.Contains(token.Value, "\n") {
|
||||||
|
parts := strings.SplitAfterN(token.Value, "\n", 2)
|
||||||
|
// Token becomes the tail.
|
||||||
|
token.Value = parts[1]
|
||||||
|
|
||||||
|
// Append the head to the line and flush the line.
|
||||||
|
clone := token.Clone()
|
||||||
|
clone.Value = parts[0]
|
||||||
|
line = append(line, clone)
|
||||||
|
out = append(out, line)
|
||||||
|
line = nil
|
||||||
|
|
||||||
|
// If the tail token is empty, don't emit it.
|
||||||
|
if len(token.Value) == 0 {
|
||||||
|
continue tokenLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line = append(line, token)
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
out = append(out, line)
|
||||||
|
}
|
||||||
|
// Strip empty trailing token line.
|
||||||
|
if len(out) > 0 {
|
||||||
|
last := out[len(out)-1]
|
||||||
|
if len(last) == 1 && last[0].Value == "" {
|
||||||
|
out = out[:len(out)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
179
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexer.go
generated
vendored
Normal file
179
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
package chroma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultOptions = &TokeniseOptions{
|
||||||
|
State: "root",
|
||||||
|
EnsureLF: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config for a lexer.
|
||||||
|
type Config struct {
|
||||||
|
// Name of the lexer.
|
||||||
|
Name string `xml:"name,omitempty"`
|
||||||
|
|
||||||
|
// Shortcuts for the lexer
|
||||||
|
Aliases []string `xml:"alias,omitempty"`
|
||||||
|
|
||||||
|
// File name globs
|
||||||
|
Filenames []string `xml:"filename,omitempty"`
|
||||||
|
|
||||||
|
// Secondary file name globs
|
||||||
|
AliasFilenames []string `xml:"alias_filename,omitempty"`
|
||||||
|
|
||||||
|
// MIME types
|
||||||
|
MimeTypes []string `xml:"mime_type,omitempty"`
|
||||||
|
|
||||||
|
// Regex matching is case-insensitive.
|
||||||
|
CaseInsensitive bool `xml:"case_insensitive,omitempty"`
|
||||||
|
|
||||||
|
// Regex matches all characters.
|
||||||
|
DotAll bool `xml:"dot_all,omitempty"`
|
||||||
|
|
||||||
|
// Regex does not match across lines ($ matches EOL).
|
||||||
|
//
|
||||||
|
// Defaults to multiline.
|
||||||
|
NotMultiline bool `xml:"not_multiline,omitempty"`
|
||||||
|
|
||||||
|
// Don't strip leading and trailing newlines from the input.
|
||||||
|
// DontStripNL bool
|
||||||
|
|
||||||
|
// Strip all leading and trailing whitespace from the input
|
||||||
|
// StripAll bool
|
||||||
|
|
||||||
|
// Make sure that the input ends with a newline. This
|
||||||
|
// is required for some lexers that consume input linewise.
|
||||||
|
EnsureNL bool `xml:"ensure_nl,omitempty"`
|
||||||
|
|
||||||
|
// If given and greater than 0, expand tabs in the input.
|
||||||
|
// TabSize int
|
||||||
|
|
||||||
|
// Priority of lexer.
|
||||||
|
//
|
||||||
|
// If this is 0 it will be treated as a default of 1.
|
||||||
|
Priority float32 `xml:"priority,omitempty"`
|
||||||
|
|
||||||
|
// Analyse is a list of regexes to match against the input.
|
||||||
|
//
|
||||||
|
// If a match is found, the score is returned if single attribute is set to true,
|
||||||
|
// otherwise the sum of all the score of matching patterns will be
|
||||||
|
// used as the final score.
|
||||||
|
Analyse *AnalyseConfig `xml:"analyse,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnalyseConfig defines the list of regexes analysers.
|
||||||
|
type AnalyseConfig struct {
|
||||||
|
Regexes []RegexConfig `xml:"regex,omitempty"`
|
||||||
|
// If true, the first matching score is returned.
|
||||||
|
First bool `xml:"first,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegexConfig defines a single regex pattern and its score in case of match.
|
||||||
|
type RegexConfig struct {
|
||||||
|
Pattern string `xml:"pattern,attr"`
|
||||||
|
Score float32 `xml:"score,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token output to formatter.
|
||||||
|
type Token struct {
|
||||||
|
Type TokenType `json:"type"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) String() string { return t.Value }
|
||||||
|
func (t *Token) GoString() string { return fmt.Sprintf("&Token{%s, %q}", t.Type, t.Value) }
|
||||||
|
|
||||||
|
// Clone returns a clone of the Token.
|
||||||
|
func (t *Token) Clone() Token {
|
||||||
|
return *t
|
||||||
|
}
|
||||||
|
|
||||||
|
// EOF is returned by lexers at the end of input.
|
||||||
|
var EOF Token
|
||||||
|
|
||||||
|
// TokeniseOptions contains options for tokenisers.
|
||||||
|
type TokeniseOptions struct {
|
||||||
|
// State to start tokenisation in. Defaults to "root".
|
||||||
|
State string
|
||||||
|
// Nested tokenisation.
|
||||||
|
Nested bool
|
||||||
|
|
||||||
|
// If true, all EOLs are converted into LF
|
||||||
|
// by replacing CRLF and CR
|
||||||
|
EnsureLF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Lexer for tokenising source code.
|
||||||
|
type Lexer interface {
|
||||||
|
// Config describing the features of the Lexer.
|
||||||
|
Config() *Config
|
||||||
|
// Tokenise returns an Iterator over tokens in text.
|
||||||
|
Tokenise(options *TokeniseOptions, text string) (Iterator, error)
|
||||||
|
// SetRegistry sets the registry this Lexer is associated with.
|
||||||
|
//
|
||||||
|
// The registry should be used by the Lexer if it needs to look up other
|
||||||
|
// lexers.
|
||||||
|
SetRegistry(registry *LexerRegistry) Lexer
|
||||||
|
// SetAnalyser sets a function the Lexer should use for scoring how
|
||||||
|
// likely a fragment of text is to match this lexer, between 0.0 and 1.0.
|
||||||
|
// A value of 1 indicates high confidence.
|
||||||
|
//
|
||||||
|
// Lexers may ignore this if they implement their own analysers.
|
||||||
|
SetAnalyser(analyser func(text string) float32) Lexer
|
||||||
|
// AnalyseText scores how likely a fragment of text is to match
|
||||||
|
// this lexer, between 0.0 and 1.0. A value of 1 indicates high confidence.
|
||||||
|
AnalyseText(text string) float32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace is the trace of a tokenisation process.
|
||||||
|
type Trace struct {
|
||||||
|
Lexer string `json:"lexer"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Rule int `json:"rule"`
|
||||||
|
Pattern string `json:"pattern"`
|
||||||
|
Pos int `json:"pos"`
|
||||||
|
Length int `json:"length"`
|
||||||
|
Elapsed float64 `json:"elapsedMs"` // Elapsed time spent matching for this rule.
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracingLexer is a Lexer that can trace its tokenisation process.
|
||||||
|
type TracingLexer interface {
|
||||||
|
Lexer
|
||||||
|
SetTracing(enable bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lexers is a slice of lexers sortable by name.
|
||||||
|
type Lexers []Lexer
|
||||||
|
|
||||||
|
func (l Lexers) Len() int { return len(l) }
|
||||||
|
func (l Lexers) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||||
|
func (l Lexers) Less(i, j int) bool {
|
||||||
|
return strings.ToLower(l[i].Config().Name) < strings.ToLower(l[j].Config().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrioritisedLexers is a slice of lexers sortable by priority.
|
||||||
|
type PrioritisedLexers []Lexer
|
||||||
|
|
||||||
|
func (l PrioritisedLexers) Len() int { return len(l) }
|
||||||
|
func (l PrioritisedLexers) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||||
|
func (l PrioritisedLexers) Less(i, j int) bool {
|
||||||
|
ip := l[i].Config().Priority
|
||||||
|
if ip == 0 {
|
||||||
|
ip = 1
|
||||||
|
}
|
||||||
|
jp := l[j].Config().Priority
|
||||||
|
if jp == 0 {
|
||||||
|
jp = 1
|
||||||
|
}
|
||||||
|
return ip > jp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyser determines how appropriate this lexer is for the given text.
|
||||||
|
type Analyser interface {
|
||||||
|
AnalyseText(text string) float32
|
||||||
|
}
|
||||||
46
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/README.md
generated
vendored
Normal file
46
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Chroma lexers
|
||||||
|
|
||||||
|
All lexers in Chroma should now be defined in XML unless they require custom code.
|
||||||
|
|
||||||
|
## Lexer tests
|
||||||
|
|
||||||
|
The tests in this directory feed a known input `testdata/<name>.actual` into the parser for `<name>` and check
|
||||||
|
that its output matches `<name>.expected`.
|
||||||
|
|
||||||
|
It is also possible to perform several tests on a same parser `<name>`, by placing know inputs `*.actual` into a
|
||||||
|
directory `testdata/<name>/`.
|
||||||
|
|
||||||
|
### Running the tests
|
||||||
|
|
||||||
|
Run the tests as normal:
|
||||||
|
```go
|
||||||
|
go test ./lexers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update existing tests
|
||||||
|
|
||||||
|
When you add a new test data file (`*.actual`), you need to regenerate all tests. That's how Chroma creates the `*.expected` test file based on the corresponding lexer.
|
||||||
|
|
||||||
|
To regenerate all tests, type in your terminal:
|
||||||
|
|
||||||
|
```go
|
||||||
|
RECORD=true go test ./lexers
|
||||||
|
```
|
||||||
|
|
||||||
|
This first sets the `RECORD` environment variable to `true`. Then it runs `go test` on the `./lexers` directory of the Chroma project.
|
||||||
|
|
||||||
|
(That environment variable tells Chroma it needs to output test data. After running `go test ./lexers` you can remove or reset that variable.)
|
||||||
|
|
||||||
|
#### Windows users
|
||||||
|
|
||||||
|
Windows users will find that the `RECORD=true go test ./lexers` command fails in both the standard command prompt terminal and in PowerShell.
|
||||||
|
|
||||||
|
Instead we have to perform both steps separately:
|
||||||
|
|
||||||
|
- Set the `RECORD` environment variable to `true`.
|
||||||
|
+ In the regular command prompt window, the `set` command sets an environment variable for the current session: `set RECORD=true`. See [this page](https://superuser.com/questions/212150/how-to-set-env-variable-in-windows-cmd-line) for more.
|
||||||
|
+ In PowerShell, you can use the `$env:RECORD = 'true'` command for that. See [this article](https://mcpmag.com/articles/2019/03/28/environment-variables-in-powershell.aspx) for more.
|
||||||
|
+ You can also make a persistent environment variable by hand in the Windows computer settings. See [this article](https://www.computerhope.com/issues/ch000549.htm) for how.
|
||||||
|
- When the environment variable is set, run `go test ./lexers`.
|
||||||
|
|
||||||
|
Chroma will now regenerate the test files and print its results to the console window.
|
||||||
275
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/caddyfile.go
generated
vendored
Normal file
275
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/caddyfile.go
generated
vendored
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
package lexers
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma/v2" // nolint
|
||||||
|
)
|
||||||
|
|
||||||
|
// Matcher token stub for docs, or
|
||||||
|
// Named matcher: @name, or
|
||||||
|
// Path matcher: /foo, or
|
||||||
|
// Wildcard path matcher: *
|
||||||
|
// nolint: gosec
|
||||||
|
var caddyfileMatcherTokenRegexp = `(\[\<matcher\>\]|@[^\s]+|/[^\s]+|\*)`
|
||||||
|
|
||||||
|
// Comment at start of line, or
|
||||||
|
// Comment preceded by whitespace
|
||||||
|
var caddyfileCommentRegexp = `(^|\s+)#.*\n`
|
||||||
|
|
||||||
|
// caddyfileCommon are the rules common to both of the lexer variants
|
||||||
|
func caddyfileCommonRules() Rules {
|
||||||
|
return Rules{
|
||||||
|
"site_block_common": {
|
||||||
|
Include("site_body"),
|
||||||
|
// Any other directive
|
||||||
|
{`[^\s#]+`, Keyword, Push("directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"site_body": {
|
||||||
|
// Import keyword
|
||||||
|
{`\b(import|invoke)\b( [^\s#]+)`, ByGroups(Keyword, Text), Push("subdirective")},
|
||||||
|
// Matcher definition
|
||||||
|
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
||||||
|
// Matcher token stub for docs
|
||||||
|
{`\[\<matcher\>\]`, NameDecorator, Push("matcher")},
|
||||||
|
// These cannot have matchers but may have things that look like
|
||||||
|
// matchers in their arguments, so we just parse as a subdirective.
|
||||||
|
{`\b(try_files|tls|log|bind)\b`, Keyword, Push("subdirective")},
|
||||||
|
// These are special, they can nest more directives
|
||||||
|
{`\b(handle_errors|handle_path|handle_response|replace_status|handle|route)\b`, Keyword, Push("nested_directive")},
|
||||||
|
// uri directive has special syntax
|
||||||
|
{`\b(uri)\b`, Keyword, Push("uri_directive")},
|
||||||
|
},
|
||||||
|
"matcher": {
|
||||||
|
{`\{`, Punctuation, Push("block")},
|
||||||
|
// Not can be one-liner
|
||||||
|
{`not`, Keyword, Push("deep_not_matcher")},
|
||||||
|
// Heredoc for CEL expression
|
||||||
|
Include("heredoc"),
|
||||||
|
// Backtick for CEL expression
|
||||||
|
{"`", StringBacktick, Push("backticks")},
|
||||||
|
// Any other same-line matcher
|
||||||
|
{`[^\s#]+`, Keyword, Push("arguments")},
|
||||||
|
// Terminators
|
||||||
|
{`\s*\n`, Text, Pop(1)},
|
||||||
|
{`\}`, Punctuation, Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
// Using double quotes doesn't stop at spaces
|
||||||
|
{`"`, StringDouble, Push("double_quotes")},
|
||||||
|
// Using backticks doesn't stop at spaces
|
||||||
|
{"`", StringBacktick, Push("backticks")},
|
||||||
|
// Not can be one-liner
|
||||||
|
{`not`, Keyword, Push("not_matcher")},
|
||||||
|
// Directives & matcher definitions
|
||||||
|
Include("site_body"),
|
||||||
|
// Any directive
|
||||||
|
{`[^\s#]+`, Keyword, Push("subdirective")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"nested_block": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
// Using double quotes doesn't stop at spaces
|
||||||
|
{`"`, StringDouble, Push("double_quotes")},
|
||||||
|
// Using backticks doesn't stop at spaces
|
||||||
|
{"`", StringBacktick, Push("backticks")},
|
||||||
|
// Not can be one-liner
|
||||||
|
{`not`, Keyword, Push("not_matcher")},
|
||||||
|
// Directives & matcher definitions
|
||||||
|
Include("site_body"),
|
||||||
|
// Any other subdirective
|
||||||
|
{`[^\s#]+`, Keyword, Push("directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"not_matcher": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
{`[^\s#]+`, Keyword, Push("arguments")},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
},
|
||||||
|
"deep_not_matcher": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
{`[^\s#]+`, Keyword, Push("deep_subdirective")},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
},
|
||||||
|
"directive": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
{caddyfileMatcherTokenRegexp, NameDecorator, Push("arguments")},
|
||||||
|
{caddyfileCommentRegexp, CommentSingle, Pop(1)},
|
||||||
|
{`\s*\n`, Text, Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"nested_directive": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("nested_block")},
|
||||||
|
{caddyfileMatcherTokenRegexp, NameDecorator, Push("nested_arguments")},
|
||||||
|
{caddyfileCommentRegexp, CommentSingle, Pop(1)},
|
||||||
|
{`\s*\n`, Text, Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"subdirective": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
{caddyfileCommentRegexp, CommentSingle, Pop(1)},
|
||||||
|
{`\s*\n`, Text, Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"arguments": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
{caddyfileCommentRegexp, CommentSingle, Pop(2)},
|
||||||
|
{`\\\n`, Text, nil}, // Skip escaped newlines
|
||||||
|
{`\s*\n`, Text, Pop(2)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"nested_arguments": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("nested_block")},
|
||||||
|
{caddyfileCommentRegexp, CommentSingle, Pop(2)},
|
||||||
|
{`\\\n`, Text, nil}, // Skip escaped newlines
|
||||||
|
{`\s*\n`, Text, Pop(2)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"deep_subdirective": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
{caddyfileCommentRegexp, CommentSingle, Pop(3)},
|
||||||
|
{`\s*\n`, Text, Pop(3)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"uri_directive": {
|
||||||
|
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||||
|
{caddyfileMatcherTokenRegexp, NameDecorator, nil},
|
||||||
|
{`(strip_prefix|strip_suffix|replace|path_regexp)`, NameConstant, Push("arguments")},
|
||||||
|
{caddyfileCommentRegexp, CommentSingle, Pop(1)},
|
||||||
|
{`\s*\n`, Text, Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"double_quotes": {
|
||||||
|
Include("placeholder"),
|
||||||
|
{`\\"`, StringDouble, nil},
|
||||||
|
{`[^"]`, StringDouble, nil},
|
||||||
|
{`"`, StringDouble, Pop(1)},
|
||||||
|
},
|
||||||
|
"backticks": {
|
||||||
|
Include("placeholder"),
|
||||||
|
{"\\\\`", StringBacktick, nil},
|
||||||
|
{"[^`]", StringBacktick, nil},
|
||||||
|
{"`", StringBacktick, Pop(1)},
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
// Docs syntax for showing optional parts with [ ]
|
||||||
|
{`\[`, Punctuation, Push("optional")},
|
||||||
|
Include("name_constants"),
|
||||||
|
{`\|`, Punctuation, nil},
|
||||||
|
{`[^\[\]\|]+`, String, nil},
|
||||||
|
{`\]`, Punctuation, Pop(1)},
|
||||||
|
},
|
||||||
|
"heredoc": {
|
||||||
|
{`(<<([a-zA-Z0-9_-]+))(\n(.*|\n)*)(\s*)(\2)`, ByGroups(StringHeredoc, nil, String, String, String, StringHeredoc), nil},
|
||||||
|
},
|
||||||
|
"name_constants": {
|
||||||
|
{`\b(most_recently_modified|largest_size|smallest_size|first_exist|internal|disable_redirects|ignore_loaded_certs|disable_certs|private_ranges|first|last|before|after|on|off)\b(\||(?=\]|\s|$))`, ByGroups(NameConstant, Punctuation), nil},
|
||||||
|
},
|
||||||
|
"placeholder": {
|
||||||
|
// Placeholder with dots, colon for default value, brackets for args[0:]
|
||||||
|
{`\{[\w+.\[\]\:\$-]+\}`, StringEscape, nil},
|
||||||
|
// Handle opening brackets with no matching closing one
|
||||||
|
{`\{[^\}\s]*\b`, String, nil},
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
{caddyfileCommentRegexp, CommentSingle, nil},
|
||||||
|
{`\[\<matcher\>\]`, NameDecorator, nil},
|
||||||
|
Include("name_constants"),
|
||||||
|
Include("heredoc"),
|
||||||
|
{`(https?://)?([a-z0-9.-]+)(:)([0-9]+)([^\s]*)`, ByGroups(Name, Name, Punctuation, NumberInteger, Name), nil},
|
||||||
|
{`\[`, Punctuation, Push("optional")},
|
||||||
|
{"`", StringBacktick, Push("backticks")},
|
||||||
|
{`"`, StringDouble, Push("double_quotes")},
|
||||||
|
Include("placeholder"),
|
||||||
|
{`[a-z-]+/[a-z-+]+`, String, nil},
|
||||||
|
{`[0-9]+([smhdk]|ns|us|µs|ms)?\b`, NumberInteger, nil},
|
||||||
|
{`[^\s\n#\{]+`, String, nil},
|
||||||
|
{`/[^\s#]*`, Name, nil},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caddyfile lexer.
|
||||||
|
var Caddyfile = Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Caddyfile",
|
||||||
|
Aliases: []string{"caddyfile", "caddy"},
|
||||||
|
Filenames: []string{"Caddyfile*"},
|
||||||
|
MimeTypes: []string{},
|
||||||
|
},
|
||||||
|
caddyfileRules,
|
||||||
|
))
|
||||||
|
|
||||||
|
func caddyfileRules() Rules {
|
||||||
|
return Rules{
|
||||||
|
"root": {
|
||||||
|
{caddyfileCommentRegexp, CommentSingle, nil},
|
||||||
|
// Global options block
|
||||||
|
{`^\s*(\{)\s*$`, ByGroups(Punctuation), Push("globals")},
|
||||||
|
// Top level import
|
||||||
|
{`(import)(\s+)([^\s]+)`, ByGroups(Keyword, Text, NameVariableMagic), nil},
|
||||||
|
// Snippets
|
||||||
|
{`(&?\([^\s#]+\))(\s*)(\{)`, ByGroups(NameVariableAnonymous, Text, Punctuation), Push("snippet")},
|
||||||
|
// Site label
|
||||||
|
{`[^#{(\s,]+`, GenericHeading, Push("label")},
|
||||||
|
// Site label with placeholder
|
||||||
|
{`\{[\w+.\[\]\:\$-]+\}`, StringEscape, Push("label")},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
{`\}`, Punctuation, Pop(1)},
|
||||||
|
// Global options are parsed as subdirectives (no matcher)
|
||||||
|
{`[^\s#]+`, Keyword, Push("subdirective")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"snippet": {
|
||||||
|
{`\}`, Punctuation, Pop(1)},
|
||||||
|
Include("site_body"),
|
||||||
|
// Any other directive
|
||||||
|
{`[^\s#]+`, Keyword, Push("directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
// Allow multiple labels, comma separated, newlines after
|
||||||
|
// a comma means another label is coming
|
||||||
|
{`,\s*\n?`, Text, nil},
|
||||||
|
{` `, Text, nil},
|
||||||
|
// Site label with placeholder
|
||||||
|
Include("placeholder"),
|
||||||
|
// Site label
|
||||||
|
{`[^#{(\s,]+`, GenericHeading, nil},
|
||||||
|
// Comment after non-block label (hack because comments end in \n)
|
||||||
|
{`#.*\n`, CommentSingle, Push("site_block")},
|
||||||
|
// Note: if \n, we'll never pop out of the site_block, it's valid
|
||||||
|
{`\{(?=\s)|\n`, Punctuation, Push("site_block")},
|
||||||
|
},
|
||||||
|
"site_block": {
|
||||||
|
{`\}`, Punctuation, Pop(2)},
|
||||||
|
Include("site_block_common"),
|
||||||
|
},
|
||||||
|
}.Merge(caddyfileCommonRules())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caddyfile directive-only lexer.
|
||||||
|
var CaddyfileDirectives = Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Caddyfile Directives",
|
||||||
|
Aliases: []string{"caddyfile-directives", "caddyfile-d", "caddy-d"},
|
||||||
|
Filenames: []string{},
|
||||||
|
MimeTypes: []string{},
|
||||||
|
},
|
||||||
|
caddyfileDirectivesRules,
|
||||||
|
))
|
||||||
|
|
||||||
|
func caddyfileDirectivesRules() Rules {
|
||||||
|
return Rules{
|
||||||
|
// Same as "site_block" in Caddyfile
|
||||||
|
"root": {
|
||||||
|
Include("site_block_common"),
|
||||||
|
},
|
||||||
|
}.Merge(caddyfileCommonRules())
|
||||||
|
}
|
||||||
243
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/cl.go
generated
vendored
Normal file
243
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/cl.go
generated
vendored
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
package lexers
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma/v2" // nolint
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clBuiltinFunctions = []string{
|
||||||
|
"<", "<=", "=", ">", ">=", "-", "/", "/=", "*", "+", "1-", "1+",
|
||||||
|
"abort", "abs", "acons", "acos", "acosh", "add-method", "adjoin",
|
||||||
|
"adjustable-array-p", "adjust-array", "allocate-instance",
|
||||||
|
"alpha-char-p", "alphanumericp", "append", "apply", "apropos",
|
||||||
|
"apropos-list", "aref", "arithmetic-error-operands",
|
||||||
|
"arithmetic-error-operation", "array-dimension", "array-dimensions",
|
||||||
|
"array-displacement", "array-element-type", "array-has-fill-pointer-p",
|
||||||
|
"array-in-bounds-p", "arrayp", "array-rank", "array-row-major-index",
|
||||||
|
"array-total-size", "ash", "asin", "asinh", "assoc", "assoc-if",
|
||||||
|
"assoc-if-not", "atan", "atanh", "atom", "bit", "bit-and", "bit-andc1",
|
||||||
|
"bit-andc2", "bit-eqv", "bit-ior", "bit-nand", "bit-nor", "bit-not",
|
||||||
|
"bit-orc1", "bit-orc2", "bit-vector-p", "bit-xor", "boole",
|
||||||
|
"both-case-p", "boundp", "break", "broadcast-stream-streams",
|
||||||
|
"butlast", "byte", "byte-position", "byte-size", "caaaar", "caaadr",
|
||||||
|
"caaar", "caadar", "caaddr", "caadr", "caar", "cadaar", "cadadr",
|
||||||
|
"cadar", "caddar", "cadddr", "caddr", "cadr", "call-next-method", "car",
|
||||||
|
"cdaaar", "cdaadr", "cdaar", "cdadar", "cdaddr", "cdadr", "cdar",
|
||||||
|
"cddaar", "cddadr", "cddar", "cdddar", "cddddr", "cdddr", "cddr", "cdr",
|
||||||
|
"ceiling", "cell-error-name", "cerror", "change-class", "char", "char<",
|
||||||
|
"char<=", "char=", "char>", "char>=", "char/=", "character",
|
||||||
|
"characterp", "char-code", "char-downcase", "char-equal",
|
||||||
|
"char-greaterp", "char-int", "char-lessp", "char-name",
|
||||||
|
"char-not-equal", "char-not-greaterp", "char-not-lessp", "char-upcase",
|
||||||
|
"cis", "class-name", "class-of", "clear-input", "clear-output",
|
||||||
|
"close", "clrhash", "code-char", "coerce", "compile",
|
||||||
|
"compiled-function-p", "compile-file", "compile-file-pathname",
|
||||||
|
"compiler-macro-function", "complement", "complex", "complexp",
|
||||||
|
"compute-applicable-methods", "compute-restarts", "concatenate",
|
||||||
|
"concatenated-stream-streams", "conjugate", "cons", "consp",
|
||||||
|
"constantly", "constantp", "continue", "copy-alist", "copy-list",
|
||||||
|
"copy-pprint-dispatch", "copy-readtable", "copy-seq", "copy-structure",
|
||||||
|
"copy-symbol", "copy-tree", "cos", "cosh", "count", "count-if",
|
||||||
|
"count-if-not", "decode-float", "decode-universal-time", "delete",
|
||||||
|
"delete-duplicates", "delete-file", "delete-if", "delete-if-not",
|
||||||
|
"delete-package", "denominator", "deposit-field", "describe",
|
||||||
|
"describe-object", "digit-char", "digit-char-p", "directory",
|
||||||
|
"directory-namestring", "disassemble", "documentation", "dpb",
|
||||||
|
"dribble", "echo-stream-input-stream", "echo-stream-output-stream",
|
||||||
|
"ed", "eighth", "elt", "encode-universal-time", "endp",
|
||||||
|
"enough-namestring", "ensure-directories-exist",
|
||||||
|
"ensure-generic-function", "eq", "eql", "equal", "equalp", "error",
|
||||||
|
"eval", "evenp", "every", "exp", "export", "expt", "fboundp",
|
||||||
|
"fceiling", "fdefinition", "ffloor", "fifth", "file-author",
|
||||||
|
"file-error-pathname", "file-length", "file-namestring",
|
||||||
|
"file-position", "file-string-length", "file-write-date",
|
||||||
|
"fill", "fill-pointer", "find", "find-all-symbols", "find-class",
|
||||||
|
"find-if", "find-if-not", "find-method", "find-package", "find-restart",
|
||||||
|
"find-symbol", "finish-output", "first", "float", "float-digits",
|
||||||
|
"floatp", "float-precision", "float-radix", "float-sign", "floor",
|
||||||
|
"fmakunbound", "force-output", "format", "fourth", "fresh-line",
|
||||||
|
"fround", "ftruncate", "funcall", "function-keywords",
|
||||||
|
"function-lambda-expression", "functionp", "gcd", "gensym", "gentemp",
|
||||||
|
"get", "get-decoded-time", "get-dispatch-macro-character", "getf",
|
||||||
|
"gethash", "get-internal-real-time", "get-internal-run-time",
|
||||||
|
"get-macro-character", "get-output-stream-string", "get-properties",
|
||||||
|
"get-setf-expansion", "get-universal-time", "graphic-char-p",
|
||||||
|
"hash-table-count", "hash-table-p", "hash-table-rehash-size",
|
||||||
|
"hash-table-rehash-threshold", "hash-table-size", "hash-table-test",
|
||||||
|
"host-namestring", "identity", "imagpart", "import",
|
||||||
|
"initialize-instance", "input-stream-p", "inspect",
|
||||||
|
"integer-decode-float", "integer-length", "integerp",
|
||||||
|
"interactive-stream-p", "intern", "intersection",
|
||||||
|
"invalid-method-error", "invoke-debugger", "invoke-restart",
|
||||||
|
"invoke-restart-interactively", "isqrt", "keywordp", "last", "lcm",
|
||||||
|
"ldb", "ldb-test", "ldiff", "length", "lisp-implementation-type",
|
||||||
|
"lisp-implementation-version", "list", "list*", "list-all-packages",
|
||||||
|
"listen", "list-length", "listp", "load",
|
||||||
|
"load-logical-pathname-translations", "log", "logand", "logandc1",
|
||||||
|
"logandc2", "logbitp", "logcount", "logeqv", "logical-pathname",
|
||||||
|
"logical-pathname-translations", "logior", "lognand", "lognor",
|
||||||
|
"lognot", "logorc1", "logorc2", "logtest", "logxor", "long-site-name",
|
||||||
|
"lower-case-p", "machine-instance", "machine-type", "machine-version",
|
||||||
|
"macroexpand", "macroexpand-1", "macro-function", "make-array",
|
||||||
|
"make-broadcast-stream", "make-concatenated-stream", "make-condition",
|
||||||
|
"make-dispatch-macro-character", "make-echo-stream", "make-hash-table",
|
||||||
|
"make-instance", "make-instances-obsolete", "make-list",
|
||||||
|
"make-load-form", "make-load-form-saving-slots", "make-package",
|
||||||
|
"make-pathname", "make-random-state", "make-sequence", "make-string",
|
||||||
|
"make-string-input-stream", "make-string-output-stream", "make-symbol",
|
||||||
|
"make-synonym-stream", "make-two-way-stream", "makunbound", "map",
|
||||||
|
"mapc", "mapcan", "mapcar", "mapcon", "maphash", "map-into", "mapl",
|
||||||
|
"maplist", "mask-field", "max", "member", "member-if", "member-if-not",
|
||||||
|
"merge", "merge-pathnames", "method-combination-error",
|
||||||
|
"method-qualifiers", "min", "minusp", "mismatch", "mod",
|
||||||
|
"muffle-warning", "name-char", "namestring", "nbutlast", "nconc",
|
||||||
|
"next-method-p", "nintersection", "ninth", "no-applicable-method",
|
||||||
|
"no-next-method", "not", "notany", "notevery", "nreconc", "nreverse",
|
||||||
|
"nset-difference", "nset-exclusive-or", "nstring-capitalize",
|
||||||
|
"nstring-downcase", "nstring-upcase", "nsublis", "nsubst", "nsubst-if",
|
||||||
|
"nsubst-if-not", "nsubstitute", "nsubstitute-if", "nsubstitute-if-not",
|
||||||
|
"nth", "nthcdr", "null", "numberp", "numerator", "nunion", "oddp",
|
||||||
|
"open", "open-stream-p", "output-stream-p", "package-error-package",
|
||||||
|
"package-name", "package-nicknames", "packagep",
|
||||||
|
"package-shadowing-symbols", "package-used-by-list", "package-use-list",
|
||||||
|
"pairlis", "parse-integer", "parse-namestring", "pathname",
|
||||||
|
"pathname-device", "pathname-directory", "pathname-host",
|
||||||
|
"pathname-match-p", "pathname-name", "pathnamep", "pathname-type",
|
||||||
|
"pathname-version", "peek-char", "phase", "plusp", "position",
|
||||||
|
"position-if", "position-if-not", "pprint", "pprint-dispatch",
|
||||||
|
"pprint-fill", "pprint-indent", "pprint-linear", "pprint-newline",
|
||||||
|
"pprint-tab", "pprint-tabular", "prin1", "prin1-to-string", "princ",
|
||||||
|
"princ-to-string", "print", "print-object", "probe-file", "proclaim",
|
||||||
|
"provide", "random", "random-state-p", "rassoc", "rassoc-if",
|
||||||
|
"rassoc-if-not", "rational", "rationalize", "rationalp", "read",
|
||||||
|
"read-byte", "read-char", "read-char-no-hang", "read-delimited-list",
|
||||||
|
"read-from-string", "read-line", "read-preserving-whitespace",
|
||||||
|
"read-sequence", "readtable-case", "readtablep", "realp", "realpart",
|
||||||
|
"reduce", "reinitialize-instance", "rem", "remhash", "remove",
|
||||||
|
"remove-duplicates", "remove-if", "remove-if-not", "remove-method",
|
||||||
|
"remprop", "rename-file", "rename-package", "replace", "require",
|
||||||
|
"rest", "restart-name", "revappend", "reverse", "room", "round",
|
||||||
|
"row-major-aref", "rplaca", "rplacd", "sbit", "scale-float", "schar",
|
||||||
|
"search", "second", "set", "set-difference",
|
||||||
|
"set-dispatch-macro-character", "set-exclusive-or",
|
||||||
|
"set-macro-character", "set-pprint-dispatch", "set-syntax-from-char",
|
||||||
|
"seventh", "shadow", "shadowing-import", "shared-initialize",
|
||||||
|
"short-site-name", "signal", "signum", "simple-bit-vector-p",
|
||||||
|
"simple-condition-format-arguments", "simple-condition-format-control",
|
||||||
|
"simple-string-p", "simple-vector-p", "sin", "sinh", "sixth", "sleep",
|
||||||
|
"slot-boundp", "slot-exists-p", "slot-makunbound", "slot-missing",
|
||||||
|
"slot-unbound", "slot-value", "software-type", "software-version",
|
||||||
|
"some", "sort", "special-operator-p", "sqrt", "stable-sort",
|
||||||
|
"standard-char-p", "store-value", "stream-element-type",
|
||||||
|
"stream-error-stream", "stream-external-format", "streamp", "string",
|
||||||
|
"string<", "string<=", "string=", "string>", "string>=", "string/=",
|
||||||
|
"string-capitalize", "string-downcase", "string-equal",
|
||||||
|
"string-greaterp", "string-left-trim", "string-lessp",
|
||||||
|
"string-not-equal", "string-not-greaterp", "string-not-lessp",
|
||||||
|
"stringp", "string-right-trim", "string-trim", "string-upcase",
|
||||||
|
"sublis", "subseq", "subsetp", "subst", "subst-if", "subst-if-not",
|
||||||
|
"substitute", "substitute-if", "substitute-if-not", "subtypep", "svref",
|
||||||
|
"sxhash", "symbol-function", "symbol-name", "symbolp", "symbol-package",
|
||||||
|
"symbol-plist", "symbol-value", "synonym-stream-symbol", "syntax:",
|
||||||
|
"tailp", "tan", "tanh", "tenth", "terpri", "third",
|
||||||
|
"translate-logical-pathname", "translate-pathname", "tree-equal",
|
||||||
|
"truename", "truncate", "two-way-stream-input-stream",
|
||||||
|
"two-way-stream-output-stream", "type-error-datum",
|
||||||
|
"type-error-expected-type", "type-of", "typep", "unbound-slot-instance",
|
||||||
|
"unexport", "unintern", "union", "unread-char", "unuse-package",
|
||||||
|
"update-instance-for-different-class",
|
||||||
|
"update-instance-for-redefined-class", "upgraded-array-element-type",
|
||||||
|
"upgraded-complex-part-type", "upper-case-p", "use-package",
|
||||||
|
"user-homedir-pathname", "use-value", "values", "values-list", "vector",
|
||||||
|
"vectorp", "vector-pop", "vector-push", "vector-push-extend", "warn",
|
||||||
|
"wild-pathname-p", "write", "write-byte", "write-char", "write-line",
|
||||||
|
"write-sequence", "write-string", "write-to-string", "yes-or-no-p",
|
||||||
|
"y-or-n-p", "zerop",
|
||||||
|
}
|
||||||
|
|
||||||
|
clSpecialForms = []string{
|
||||||
|
"block", "catch", "declare", "eval-when", "flet", "function", "go", "if",
|
||||||
|
"labels", "lambda", "let", "let*", "load-time-value", "locally", "macrolet",
|
||||||
|
"multiple-value-call", "multiple-value-prog1", "progn", "progv", "quote",
|
||||||
|
"return-from", "setq", "symbol-macrolet", "tagbody", "the", "throw",
|
||||||
|
"unwind-protect",
|
||||||
|
}
|
||||||
|
|
||||||
|
clMacros = []string{
|
||||||
|
"and", "assert", "call-method", "case", "ccase", "check-type", "cond",
|
||||||
|
"ctypecase", "decf", "declaim", "defclass", "defconstant", "defgeneric",
|
||||||
|
"define-compiler-macro", "define-condition", "define-method-combination",
|
||||||
|
"define-modify-macro", "define-setf-expander", "define-symbol-macro",
|
||||||
|
"defmacro", "defmethod", "defpackage", "defparameter", "defsetf",
|
||||||
|
"defstruct", "deftype", "defun", "defvar", "destructuring-bind", "do",
|
||||||
|
"do*", "do-all-symbols", "do-external-symbols", "dolist", "do-symbols",
|
||||||
|
"dotimes", "ecase", "etypecase", "formatter", "handler-bind",
|
||||||
|
"handler-case", "ignore-errors", "incf", "in-package", "lambda", "loop",
|
||||||
|
"loop-finish", "make-method", "multiple-value-bind", "multiple-value-list",
|
||||||
|
"multiple-value-setq", "nth-value", "or", "pop",
|
||||||
|
"pprint-exit-if-list-exhausted", "pprint-logical-block", "pprint-pop",
|
||||||
|
"print-unreadable-object", "prog", "prog*", "prog1", "prog2", "psetf",
|
||||||
|
"psetq", "push", "pushnew", "remf", "restart-bind", "restart-case",
|
||||||
|
"return", "rotatef", "setf", "shiftf", "step", "time", "trace", "typecase",
|
||||||
|
"unless", "untrace", "when", "with-accessors", "with-compilation-unit",
|
||||||
|
"with-condition-restarts", "with-hash-table-iterator",
|
||||||
|
"with-input-from-string", "with-open-file", "with-open-stream",
|
||||||
|
"with-output-to-string", "with-package-iterator", "with-simple-restart",
|
||||||
|
"with-slots", "with-standard-io-syntax",
|
||||||
|
}
|
||||||
|
|
||||||
|
clLambdaListKeywords = []string{
|
||||||
|
"&allow-other-keys", "&aux", "&body", "&environment", "&key", "&optional",
|
||||||
|
"&rest", "&whole",
|
||||||
|
}
|
||||||
|
|
||||||
|
clDeclarations = []string{
|
||||||
|
"dynamic-extent", "ignore", "optimize", "ftype", "inline", "special",
|
||||||
|
"ignorable", "notinline", "type",
|
||||||
|
}
|
||||||
|
|
||||||
|
clBuiltinTypes = []string{
|
||||||
|
"atom", "boolean", "base-char", "base-string", "bignum", "bit",
|
||||||
|
"compiled-function", "extended-char", "fixnum", "keyword", "nil",
|
||||||
|
"signed-byte", "short-float", "single-float", "double-float", "long-float",
|
||||||
|
"simple-array", "simple-base-string", "simple-bit-vector", "simple-string",
|
||||||
|
"simple-vector", "standard-char", "unsigned-byte",
|
||||||
|
|
||||||
|
// Condition Types
|
||||||
|
"arithmetic-error", "cell-error", "condition", "control-error",
|
||||||
|
"division-by-zero", "end-of-file", "error", "file-error",
|
||||||
|
"floating-point-inexact", "floating-point-overflow",
|
||||||
|
"floating-point-underflow", "floating-point-invalid-operation",
|
||||||
|
"parse-error", "package-error", "print-not-readable", "program-error",
|
||||||
|
"reader-error", "serious-condition", "simple-condition", "simple-error",
|
||||||
|
"simple-type-error", "simple-warning", "stream-error", "storage-condition",
|
||||||
|
"style-warning", "type-error", "unbound-variable", "unbound-slot",
|
||||||
|
"undefined-function", "warning",
|
||||||
|
}
|
||||||
|
|
||||||
|
clBuiltinClasses = []string{
|
||||||
|
"array", "broadcast-stream", "bit-vector", "built-in-class", "character",
|
||||||
|
"class", "complex", "concatenated-stream", "cons", "echo-stream",
|
||||||
|
"file-stream", "float", "function", "generic-function", "hash-table",
|
||||||
|
"integer", "list", "logical-pathname", "method-combination", "method",
|
||||||
|
"null", "number", "package", "pathname", "ratio", "rational", "readtable",
|
||||||
|
"real", "random-state", "restart", "sequence", "standard-class",
|
||||||
|
"standard-generic-function", "standard-method", "standard-object",
|
||||||
|
"string-stream", "stream", "string", "structure-class", "structure-object",
|
||||||
|
"symbol", "synonym-stream", "t", "two-way-stream", "vector",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common Lisp lexer.
|
||||||
|
var CommonLisp = Register(TypeRemappingLexer(MustNewXMLLexer(
|
||||||
|
embedded,
|
||||||
|
"embedded/common_lisp.xml",
|
||||||
|
), TypeMapping{
|
||||||
|
{NameVariable, NameFunction, clBuiltinFunctions},
|
||||||
|
{NameVariable, Keyword, clSpecialForms},
|
||||||
|
{NameVariable, NameBuiltin, clMacros},
|
||||||
|
{NameVariable, Keyword, clLambdaListKeywords},
|
||||||
|
{NameVariable, Keyword, clDeclarations},
|
||||||
|
{NameVariable, KeywordType, clBuiltinTypes},
|
||||||
|
{NameVariable, NameClass, clBuiltinClasses},
|
||||||
|
}))
|
||||||
17
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/dns.go
generated
vendored
Normal file
17
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/dns.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package lexers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(moorereason): can this be factored away?
|
||||||
|
var zoneAnalyserRe = regexp.MustCompile(`(?m)^@\s+IN\s+SOA\s+`)
|
||||||
|
|
||||||
|
func init() { // nolint: gochecknoinits
|
||||||
|
Get("dns").SetAnalyser(func(text string) float32 {
|
||||||
|
if zoneAnalyserRe.FindString(text) != "" {
|
||||||
|
return 1.0
|
||||||
|
}
|
||||||
|
return 0.0
|
||||||
|
})
|
||||||
|
}
|
||||||
533
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/emacs.go
generated
vendored
Normal file
533
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/emacs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,533 @@
|
||||||
|
package lexers
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma/v2" // nolint
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
emacsMacros = []string{
|
||||||
|
"atomic-change-group", "case", "block", "cl-block", "cl-callf", "cl-callf2",
|
||||||
|
"cl-case", "cl-decf", "cl-declaim", "cl-declare",
|
||||||
|
"cl-define-compiler-macro", "cl-defmacro", "cl-defstruct",
|
||||||
|
"cl-defsubst", "cl-deftype", "cl-defun", "cl-destructuring-bind",
|
||||||
|
"cl-do", "cl-do*", "cl-do-all-symbols", "cl-do-symbols", "cl-dolist",
|
||||||
|
"cl-dotimes", "cl-ecase", "cl-etypecase", "eval-when", "cl-eval-when", "cl-flet",
|
||||||
|
"cl-flet*", "cl-function", "cl-incf", "cl-labels", "cl-letf",
|
||||||
|
"cl-letf*", "cl-load-time-value", "cl-locally", "cl-loop",
|
||||||
|
"cl-macrolet", "cl-multiple-value-bind", "cl-multiple-value-setq",
|
||||||
|
"cl-progv", "cl-psetf", "cl-psetq", "cl-pushnew", "cl-remf",
|
||||||
|
"cl-return", "cl-return-from", "cl-rotatef", "cl-shiftf",
|
||||||
|
"cl-symbol-macrolet", "cl-tagbody", "cl-the", "cl-typecase",
|
||||||
|
"combine-after-change-calls", "condition-case-unless-debug", "decf",
|
||||||
|
"declaim", "declare", "declare-function", "def-edebug-spec",
|
||||||
|
"defadvice", "defclass", "defcustom", "defface", "defgeneric",
|
||||||
|
"defgroup", "define-advice", "define-alternatives",
|
||||||
|
"define-compiler-macro", "define-derived-mode", "define-generic-mode",
|
||||||
|
"define-global-minor-mode", "define-globalized-minor-mode",
|
||||||
|
"define-minor-mode", "define-modify-macro",
|
||||||
|
"define-obsolete-face-alias", "define-obsolete-function-alias",
|
||||||
|
"define-obsolete-variable-alias", "define-setf-expander",
|
||||||
|
"define-skeleton", "defmacro", "defmethod", "defsetf", "defstruct",
|
||||||
|
"defsubst", "deftheme", "deftype", "defun", "defvar-local",
|
||||||
|
"delay-mode-hooks", "destructuring-bind", "do", "do*",
|
||||||
|
"do-all-symbols", "do-symbols", "dolist", "dont-compile", "dotimes",
|
||||||
|
"dotimes-with-progress-reporter", "ecase", "ert-deftest", "etypecase",
|
||||||
|
"eval-and-compile", "eval-when-compile", "flet", "ignore-errors",
|
||||||
|
"incf", "labels", "lambda", "letrec", "lexical-let", "lexical-let*",
|
||||||
|
"loop", "multiple-value-bind", "multiple-value-setq", "noreturn",
|
||||||
|
"oref", "oref-default", "oset", "oset-default", "pcase",
|
||||||
|
"pcase-defmacro", "pcase-dolist", "pcase-exhaustive", "pcase-let",
|
||||||
|
"pcase-let*", "pop", "psetf", "psetq", "push", "pushnew", "remf",
|
||||||
|
"return", "rotatef", "rx", "save-match-data", "save-selected-window",
|
||||||
|
"save-window-excursion", "setf", "setq-local", "shiftf",
|
||||||
|
"track-mouse", "typecase", "unless", "use-package", "when",
|
||||||
|
"while-no-input", "with-case-table", "with-category-table",
|
||||||
|
"with-coding-priority", "with-current-buffer", "with-demoted-errors",
|
||||||
|
"with-eval-after-load", "with-file-modes", "with-local-quit",
|
||||||
|
"with-output-to-string", "with-output-to-temp-buffer",
|
||||||
|
"with-parsed-tramp-file-name", "with-selected-frame",
|
||||||
|
"with-selected-window", "with-silent-modifications", "with-slots",
|
||||||
|
"with-syntax-table", "with-temp-buffer", "with-temp-file",
|
||||||
|
"with-temp-message", "with-timeout", "with-tramp-connection-property",
|
||||||
|
"with-tramp-file-property", "with-tramp-progress-reporter",
|
||||||
|
"with-wrapper-hook", "load-time-value", "locally", "macrolet", "progv",
|
||||||
|
"return-from",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsSpecialForms = []string{
|
||||||
|
"and", "catch", "cond", "condition-case", "defconst", "defvar",
|
||||||
|
"function", "if", "interactive", "let", "let*", "or", "prog1",
|
||||||
|
"prog2", "progn", "quote", "save-current-buffer", "save-excursion",
|
||||||
|
"save-restriction", "setq", "setq-default", "subr-arity",
|
||||||
|
"unwind-protect", "while",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsBuiltinFunction = []string{
|
||||||
|
"%", "*", "+", "-", "/", "/=", "1+", "1-", "<", "<=", "=", ">", ">=",
|
||||||
|
"Snarf-documentation", "abort-recursive-edit", "abs",
|
||||||
|
"accept-process-output", "access-file", "accessible-keymaps", "acos",
|
||||||
|
"active-minibuffer-window", "add-face-text-property",
|
||||||
|
"add-name-to-file", "add-text-properties", "all-completions",
|
||||||
|
"append", "apply", "apropos-internal", "aref", "arrayp", "aset",
|
||||||
|
"ash", "asin", "assoc", "assoc-string", "assq", "atan", "atom",
|
||||||
|
"autoload", "autoload-do-load", "backtrace", "backtrace--locals",
|
||||||
|
"backtrace-debug", "backtrace-eval", "backtrace-frame",
|
||||||
|
"backward-char", "backward-prefix-chars", "barf-if-buffer-read-only",
|
||||||
|
"base64-decode-region", "base64-decode-string",
|
||||||
|
"base64-encode-region", "base64-encode-string", "beginning-of-line",
|
||||||
|
"bidi-find-overridden-directionality", "bidi-resolved-levels",
|
||||||
|
"bitmap-spec-p", "bobp", "bolp", "bool-vector",
|
||||||
|
"bool-vector-count-consecutive", "bool-vector-count-population",
|
||||||
|
"bool-vector-exclusive-or", "bool-vector-intersection",
|
||||||
|
"bool-vector-not", "bool-vector-p", "bool-vector-set-difference",
|
||||||
|
"bool-vector-subsetp", "bool-vector-union", "boundp",
|
||||||
|
"buffer-base-buffer", "buffer-chars-modified-tick",
|
||||||
|
"buffer-enable-undo", "buffer-file-name", "buffer-has-markers-at",
|
||||||
|
"buffer-list", "buffer-live-p", "buffer-local-value",
|
||||||
|
"buffer-local-variables", "buffer-modified-p", "buffer-modified-tick",
|
||||||
|
"buffer-name", "buffer-size", "buffer-string", "buffer-substring",
|
||||||
|
"buffer-substring-no-properties", "buffer-swap-text", "bufferp",
|
||||||
|
"bury-buffer-internal", "byte-code", "byte-code-function-p",
|
||||||
|
"byte-to-position", "byte-to-string", "byteorder",
|
||||||
|
"call-interactively", "call-last-kbd-macro", "call-process",
|
||||||
|
"call-process-region", "cancel-kbd-macro-events", "capitalize",
|
||||||
|
"capitalize-region", "capitalize-word", "car", "car-less-than-car",
|
||||||
|
"car-safe", "case-table-p", "category-docstring",
|
||||||
|
"category-set-mnemonics", "category-table", "category-table-p",
|
||||||
|
"ccl-execute", "ccl-execute-on-string", "ccl-program-p", "cdr",
|
||||||
|
"cdr-safe", "ceiling", "char-after", "char-before",
|
||||||
|
"char-category-set", "char-charset", "char-equal", "char-or-string-p",
|
||||||
|
"char-resolve-modifiers", "char-syntax", "char-table-extra-slot",
|
||||||
|
"char-table-p", "char-table-parent", "char-table-range",
|
||||||
|
"char-table-subtype", "char-to-string", "char-width", "characterp",
|
||||||
|
"charset-after", "charset-id-internal", "charset-plist",
|
||||||
|
"charset-priority-list", "charsetp", "check-coding-system",
|
||||||
|
"check-coding-systems-region", "clear-buffer-auto-save-failure",
|
||||||
|
"clear-charset-maps", "clear-face-cache", "clear-font-cache",
|
||||||
|
"clear-image-cache", "clear-string", "clear-this-command-keys",
|
||||||
|
"close-font", "clrhash", "coding-system-aliases",
|
||||||
|
"coding-system-base", "coding-system-eol-type", "coding-system-p",
|
||||||
|
"coding-system-plist", "coding-system-priority-list",
|
||||||
|
"coding-system-put", "color-distance", "color-gray-p",
|
||||||
|
"color-supported-p", "combine-after-change-execute",
|
||||||
|
"command-error-default-function", "command-remapping", "commandp",
|
||||||
|
"compare-buffer-substrings", "compare-strings",
|
||||||
|
"compare-window-configurations", "completing-read",
|
||||||
|
"compose-region-internal", "compose-string-internal",
|
||||||
|
"composition-get-gstring", "compute-motion", "concat", "cons",
|
||||||
|
"consp", "constrain-to-field", "continue-process",
|
||||||
|
"controlling-tty-p", "coordinates-in-window-p", "copy-alist",
|
||||||
|
"copy-category-table", "copy-file", "copy-hash-table", "copy-keymap",
|
||||||
|
"copy-marker", "copy-sequence", "copy-syntax-table", "copysign",
|
||||||
|
"cos", "current-active-maps", "current-bidi-paragraph-direction",
|
||||||
|
"current-buffer", "current-case-table", "current-column",
|
||||||
|
"current-global-map", "current-idle-time", "current-indentation",
|
||||||
|
"current-input-mode", "current-local-map", "current-message",
|
||||||
|
"current-minor-mode-maps", "current-time", "current-time-string",
|
||||||
|
"current-time-zone", "current-window-configuration",
|
||||||
|
"cygwin-convert-file-name-from-windows",
|
||||||
|
"cygwin-convert-file-name-to-windows", "daemon-initialized",
|
||||||
|
"daemonp", "dbus--init-bus", "dbus-get-unique-name",
|
||||||
|
"dbus-message-internal", "debug-timer-check", "declare-equiv-charset",
|
||||||
|
"decode-big5-char", "decode-char", "decode-coding-region",
|
||||||
|
"decode-coding-string", "decode-sjis-char", "decode-time",
|
||||||
|
"default-boundp", "default-file-modes", "default-printer-name",
|
||||||
|
"default-toplevel-value", "default-value", "define-category",
|
||||||
|
"define-charset-alias", "define-charset-internal",
|
||||||
|
"define-coding-system-alias", "define-coding-system-internal",
|
||||||
|
"define-fringe-bitmap", "define-hash-table-test", "define-key",
|
||||||
|
"define-prefix-command", "delete",
|
||||||
|
"delete-all-overlays", "delete-and-extract-region", "delete-char",
|
||||||
|
"delete-directory-internal", "delete-field", "delete-file",
|
||||||
|
"delete-frame", "delete-other-windows-internal", "delete-overlay",
|
||||||
|
"delete-process", "delete-region", "delete-terminal",
|
||||||
|
"delete-window-internal", "delq", "describe-buffer-bindings",
|
||||||
|
"describe-vector", "destroy-fringe-bitmap", "detect-coding-region",
|
||||||
|
"detect-coding-string", "ding", "directory-file-name",
|
||||||
|
"directory-files", "directory-files-and-attributes", "discard-input",
|
||||||
|
"display-supports-face-attributes-p", "do-auto-save", "documentation",
|
||||||
|
"documentation-property", "downcase", "downcase-region",
|
||||||
|
"downcase-word", "draw-string", "dump-colors", "dump-emacs",
|
||||||
|
"dump-face", "dump-frame-glyph-matrix", "dump-glyph-matrix",
|
||||||
|
"dump-glyph-row", "dump-redisplay-history", "dump-tool-bar-row",
|
||||||
|
"elt", "emacs-pid", "encode-big5-char", "encode-char",
|
||||||
|
"encode-coding-region", "encode-coding-string", "encode-sjis-char",
|
||||||
|
"encode-time", "end-kbd-macro", "end-of-line", "eobp", "eolp", "eq",
|
||||||
|
"eql", "equal", "equal-including-properties", "erase-buffer",
|
||||||
|
"error-message-string", "eval", "eval-buffer", "eval-region",
|
||||||
|
"event-convert-list", "execute-kbd-macro", "exit-recursive-edit",
|
||||||
|
"exp", "expand-file-name", "expt", "external-debugging-output",
|
||||||
|
"face-attribute-relative-p", "face-attributes-as-vector", "face-font",
|
||||||
|
"fboundp", "fceiling", "fetch-bytecode", "ffloor",
|
||||||
|
"field-beginning", "field-end", "field-string",
|
||||||
|
"field-string-no-properties", "file-accessible-directory-p",
|
||||||
|
"file-acl", "file-attributes", "file-attributes-lessp",
|
||||||
|
"file-directory-p", "file-executable-p", "file-exists-p",
|
||||||
|
"file-locked-p", "file-modes", "file-name-absolute-p",
|
||||||
|
"file-name-all-completions", "file-name-as-directory",
|
||||||
|
"file-name-completion", "file-name-directory",
|
||||||
|
"file-name-nondirectory", "file-newer-than-file-p", "file-readable-p",
|
||||||
|
"file-regular-p", "file-selinux-context", "file-symlink-p",
|
||||||
|
"file-system-info", "file-system-info", "file-writable-p",
|
||||||
|
"fillarray", "find-charset-region", "find-charset-string",
|
||||||
|
"find-coding-systems-region-internal", "find-composition-internal",
|
||||||
|
"find-file-name-handler", "find-font", "find-operation-coding-system",
|
||||||
|
"float", "float-time", "floatp", "floor", "fmakunbound",
|
||||||
|
"following-char", "font-at", "font-drive-otf", "font-face-attributes",
|
||||||
|
"font-family-list", "font-get", "font-get-glyphs",
|
||||||
|
"font-get-system-font", "font-get-system-normal-font", "font-info",
|
||||||
|
"font-match-p", "font-otf-alternates", "font-put",
|
||||||
|
"font-shape-gstring", "font-spec", "font-variation-glyphs",
|
||||||
|
"font-xlfd-name", "fontp", "fontset-font", "fontset-info",
|
||||||
|
"fontset-list", "fontset-list-all", "force-mode-line-update",
|
||||||
|
"force-window-update", "format", "format-mode-line",
|
||||||
|
"format-network-address", "format-time-string", "forward-char",
|
||||||
|
"forward-comment", "forward-line", "forward-word",
|
||||||
|
"frame-border-width", "frame-bottom-divider-width",
|
||||||
|
"frame-can-run-window-configuration-change-hook", "frame-char-height",
|
||||||
|
"frame-char-width", "frame-face-alist", "frame-first-window",
|
||||||
|
"frame-focus", "frame-font-cache", "frame-fringe-width", "frame-list",
|
||||||
|
"frame-live-p", "frame-or-buffer-changed-p", "frame-parameter",
|
||||||
|
"frame-parameters", "frame-pixel-height", "frame-pixel-width",
|
||||||
|
"frame-pointer-visible-p", "frame-right-divider-width",
|
||||||
|
"frame-root-window", "frame-scroll-bar-height",
|
||||||
|
"frame-scroll-bar-width", "frame-selected-window", "frame-terminal",
|
||||||
|
"frame-text-cols", "frame-text-height", "frame-text-lines",
|
||||||
|
"frame-text-width", "frame-total-cols", "frame-total-lines",
|
||||||
|
"frame-visible-p", "framep", "frexp", "fringe-bitmaps-at-pos",
|
||||||
|
"fround", "fset", "ftruncate", "funcall", "funcall-interactively",
|
||||||
|
"function-equal", "functionp", "gap-position", "gap-size",
|
||||||
|
"garbage-collect", "gc-status", "generate-new-buffer-name", "get",
|
||||||
|
"get-buffer", "get-buffer-create", "get-buffer-process",
|
||||||
|
"get-buffer-window", "get-byte", "get-char-property",
|
||||||
|
"get-char-property-and-overlay", "get-file-buffer", "get-file-char",
|
||||||
|
"get-internal-run-time", "get-load-suffixes", "get-pos-property",
|
||||||
|
"get-process", "get-screen-color", "get-text-property",
|
||||||
|
"get-unicode-property-internal", "get-unused-category",
|
||||||
|
"get-unused-iso-final-char", "getenv-internal", "gethash",
|
||||||
|
"gfile-add-watch", "gfile-rm-watch", "global-key-binding",
|
||||||
|
"gnutls-available-p", "gnutls-boot", "gnutls-bye", "gnutls-deinit",
|
||||||
|
"gnutls-error-fatalp", "gnutls-error-string", "gnutls-errorp",
|
||||||
|
"gnutls-get-initstage", "gnutls-peer-status",
|
||||||
|
"gnutls-peer-status-warning-describe", "goto-char", "gpm-mouse-start",
|
||||||
|
"gpm-mouse-stop", "group-gid", "group-real-gid",
|
||||||
|
"handle-save-session", "handle-switch-frame", "hash-table-count",
|
||||||
|
"hash-table-p", "hash-table-rehash-size",
|
||||||
|
"hash-table-rehash-threshold", "hash-table-size", "hash-table-test",
|
||||||
|
"hash-table-weakness", "iconify-frame", "identity", "image-flush",
|
||||||
|
"image-mask-p", "image-metadata", "image-size", "imagemagick-types",
|
||||||
|
"imagep", "indent-to", "indirect-function", "indirect-variable",
|
||||||
|
"init-image-library", "inotify-add-watch", "inotify-rm-watch",
|
||||||
|
"input-pending-p", "insert", "insert-and-inherit",
|
||||||
|
"insert-before-markers", "insert-before-markers-and-inherit",
|
||||||
|
"insert-buffer-substring", "insert-byte", "insert-char",
|
||||||
|
"insert-file-contents", "insert-startup-screen", "int86",
|
||||||
|
"integer-or-marker-p", "integerp", "interactive-form", "intern",
|
||||||
|
"intern-soft", "internal--track-mouse", "internal-char-font",
|
||||||
|
"internal-complete-buffer", "internal-copy-lisp-face",
|
||||||
|
"internal-default-process-filter",
|
||||||
|
"internal-default-process-sentinel", "internal-describe-syntax-value",
|
||||||
|
"internal-event-symbol-parse-modifiers",
|
||||||
|
"internal-face-x-get-resource", "internal-get-lisp-face-attribute",
|
||||||
|
"internal-lisp-face-attribute-values", "internal-lisp-face-empty-p",
|
||||||
|
"internal-lisp-face-equal-p", "internal-lisp-face-p",
|
||||||
|
"internal-make-lisp-face", "internal-make-var-non-special",
|
||||||
|
"internal-merge-in-global-face",
|
||||||
|
"internal-set-alternative-font-family-alist",
|
||||||
|
"internal-set-alternative-font-registry-alist",
|
||||||
|
"internal-set-font-selection-order",
|
||||||
|
"internal-set-lisp-face-attribute",
|
||||||
|
"internal-set-lisp-face-attribute-from-resource",
|
||||||
|
"internal-show-cursor", "internal-show-cursor-p", "interrupt-process",
|
||||||
|
"invisible-p", "invocation-directory", "invocation-name", "isnan",
|
||||||
|
"iso-charset", "key-binding", "key-description",
|
||||||
|
"keyboard-coding-system", "keymap-parent", "keymap-prompt", "keymapp",
|
||||||
|
"keywordp", "kill-all-local-variables", "kill-buffer", "kill-emacs",
|
||||||
|
"kill-local-variable", "kill-process", "last-nonminibuffer-frame",
|
||||||
|
"lax-plist-get", "lax-plist-put", "ldexp", "length",
|
||||||
|
"libxml-parse-html-region", "libxml-parse-xml-region",
|
||||||
|
"line-beginning-position", "line-end-position", "line-pixel-height",
|
||||||
|
"list", "list-fonts", "list-system-processes", "listp", "load",
|
||||||
|
"load-average", "local-key-binding", "local-variable-if-set-p",
|
||||||
|
"local-variable-p", "locale-info", "locate-file-internal",
|
||||||
|
"lock-buffer", "log", "logand", "logb", "logior", "lognot", "logxor",
|
||||||
|
"looking-at", "lookup-image", "lookup-image-map", "lookup-key",
|
||||||
|
"lower-frame", "lsh", "macroexpand", "make-bool-vector",
|
||||||
|
"make-byte-code", "make-category-set", "make-category-table",
|
||||||
|
"make-char", "make-char-table", "make-directory-internal",
|
||||||
|
"make-frame-invisible", "make-frame-visible", "make-hash-table",
|
||||||
|
"make-indirect-buffer", "make-keymap", "make-list",
|
||||||
|
"make-local-variable", "make-marker", "make-network-process",
|
||||||
|
"make-overlay", "make-serial-process", "make-sparse-keymap",
|
||||||
|
"make-string", "make-symbol", "make-symbolic-link", "make-temp-name",
|
||||||
|
"make-terminal-frame", "make-variable-buffer-local",
|
||||||
|
"make-variable-frame-local", "make-vector", "makunbound",
|
||||||
|
"map-char-table", "map-charset-chars", "map-keymap",
|
||||||
|
"map-keymap-internal", "mapatoms", "mapc", "mapcar", "mapconcat",
|
||||||
|
"maphash", "mark-marker", "marker-buffer", "marker-insertion-type",
|
||||||
|
"marker-position", "markerp", "match-beginning", "match-data",
|
||||||
|
"match-end", "matching-paren", "max", "max-char", "md5", "member",
|
||||||
|
"memory-info", "memory-limit", "memory-use-counts", "memq", "memql",
|
||||||
|
"menu-bar-menu-at-x-y", "menu-or-popup-active-p",
|
||||||
|
"menu-or-popup-active-p", "merge-face-attribute", "message",
|
||||||
|
"message-box", "message-or-box", "min",
|
||||||
|
"minibuffer-completion-contents", "minibuffer-contents",
|
||||||
|
"minibuffer-contents-no-properties", "minibuffer-depth",
|
||||||
|
"minibuffer-prompt", "minibuffer-prompt-end",
|
||||||
|
"minibuffer-selected-window", "minibuffer-window", "minibufferp",
|
||||||
|
"minor-mode-key-binding", "mod", "modify-category-entry",
|
||||||
|
"modify-frame-parameters", "modify-syntax-entry",
|
||||||
|
"mouse-pixel-position", "mouse-position", "move-overlay",
|
||||||
|
"move-point-visually", "move-to-column", "move-to-window-line",
|
||||||
|
"msdos-downcase-filename", "msdos-long-file-names", "msdos-memget",
|
||||||
|
"msdos-memput", "msdos-mouse-disable", "msdos-mouse-enable",
|
||||||
|
"msdos-mouse-init", "msdos-mouse-p", "msdos-remember-default-colors",
|
||||||
|
"msdos-set-keyboard", "msdos-set-mouse-buttons",
|
||||||
|
"multibyte-char-to-unibyte", "multibyte-string-p", "narrow-to-region",
|
||||||
|
"natnump", "nconc", "network-interface-info",
|
||||||
|
"network-interface-list", "new-fontset", "newline-cache-check",
|
||||||
|
"next-char-property-change", "next-frame", "next-overlay-change",
|
||||||
|
"next-property-change", "next-read-file-uses-dialog-p",
|
||||||
|
"next-single-char-property-change", "next-single-property-change",
|
||||||
|
"next-window", "nlistp", "nreverse", "nth", "nthcdr", "null",
|
||||||
|
"number-or-marker-p", "number-to-string", "numberp",
|
||||||
|
"open-dribble-file", "open-font", "open-termscript",
|
||||||
|
"optimize-char-table", "other-buffer", "other-window-for-scrolling",
|
||||||
|
"overlay-buffer", "overlay-end", "overlay-get", "overlay-lists",
|
||||||
|
"overlay-properties", "overlay-put", "overlay-recenter",
|
||||||
|
"overlay-start", "overlayp", "overlays-at", "overlays-in",
|
||||||
|
"parse-partial-sexp", "play-sound-internal", "plist-get",
|
||||||
|
"plist-member", "plist-put", "point", "point-marker", "point-max",
|
||||||
|
"point-max-marker", "point-min", "point-min-marker",
|
||||||
|
"pos-visible-in-window-p", "position-bytes", "posix-looking-at",
|
||||||
|
"posix-search-backward", "posix-search-forward", "posix-string-match",
|
||||||
|
"posn-at-point", "posn-at-x-y", "preceding-char",
|
||||||
|
"prefix-numeric-value", "previous-char-property-change",
|
||||||
|
"previous-frame", "previous-overlay-change",
|
||||||
|
"previous-property-change", "previous-single-char-property-change",
|
||||||
|
"previous-single-property-change", "previous-window", "prin1",
|
||||||
|
"prin1-to-string", "princ", "print", "process-attributes",
|
||||||
|
"process-buffer", "process-coding-system", "process-command",
|
||||||
|
"process-connection", "process-contact", "process-datagram-address",
|
||||||
|
"process-exit-status", "process-filter", "process-filter-multibyte-p",
|
||||||
|
"process-id", "process-inherit-coding-system-flag", "process-list",
|
||||||
|
"process-mark", "process-name", "process-plist",
|
||||||
|
"process-query-on-exit-flag", "process-running-child-p",
|
||||||
|
"process-send-eof", "process-send-region", "process-send-string",
|
||||||
|
"process-sentinel", "process-status", "process-tty-name",
|
||||||
|
"process-type", "processp", "profiler-cpu-log",
|
||||||
|
"profiler-cpu-running-p", "profiler-cpu-start", "profiler-cpu-stop",
|
||||||
|
"profiler-memory-log", "profiler-memory-running-p",
|
||||||
|
"profiler-memory-start", "profiler-memory-stop", "propertize",
|
||||||
|
"purecopy", "put", "put-text-property",
|
||||||
|
"put-unicode-property-internal", "puthash", "query-font",
|
||||||
|
"query-fontset", "quit-process", "raise-frame", "random", "rassoc",
|
||||||
|
"rassq", "re-search-backward", "re-search-forward", "read",
|
||||||
|
"read-buffer", "read-char", "read-char-exclusive",
|
||||||
|
"read-coding-system", "read-command", "read-event",
|
||||||
|
"read-from-minibuffer", "read-from-string", "read-function",
|
||||||
|
"read-key-sequence", "read-key-sequence-vector",
|
||||||
|
"read-no-blanks-input", "read-non-nil-coding-system", "read-string",
|
||||||
|
"read-variable", "recent-auto-save-p", "recent-doskeys",
|
||||||
|
"recent-keys", "recenter", "recursion-depth", "recursive-edit",
|
||||||
|
"redirect-debugging-output", "redirect-frame-focus", "redisplay",
|
||||||
|
"redraw-display", "redraw-frame", "regexp-quote", "region-beginning",
|
||||||
|
"region-end", "register-ccl-program", "register-code-conversion-map",
|
||||||
|
"remhash", "remove-list-of-text-properties", "remove-text-properties",
|
||||||
|
"rename-buffer", "rename-file", "replace-match",
|
||||||
|
"reset-this-command-lengths", "resize-mini-window-internal",
|
||||||
|
"restore-buffer-modified-p", "resume-tty", "reverse", "round",
|
||||||
|
"run-hook-with-args", "run-hook-with-args-until-failure",
|
||||||
|
"run-hook-with-args-until-success", "run-hook-wrapped", "run-hooks",
|
||||||
|
"run-window-configuration-change-hook", "run-window-scroll-functions",
|
||||||
|
"safe-length", "scan-lists", "scan-sexps", "scroll-down",
|
||||||
|
"scroll-left", "scroll-other-window", "scroll-right", "scroll-up",
|
||||||
|
"search-backward", "search-forward", "secure-hash", "select-frame",
|
||||||
|
"select-window", "selected-frame", "selected-window",
|
||||||
|
"self-insert-command", "send-string-to-terminal", "sequencep",
|
||||||
|
"serial-process-configure", "set", "set-buffer",
|
||||||
|
"set-buffer-auto-saved", "set-buffer-major-mode",
|
||||||
|
"set-buffer-modified-p", "set-buffer-multibyte", "set-case-table",
|
||||||
|
"set-category-table", "set-char-table-extra-slot",
|
||||||
|
"set-char-table-parent", "set-char-table-range", "set-charset-plist",
|
||||||
|
"set-charset-priority", "set-coding-system-priority",
|
||||||
|
"set-cursor-size", "set-default", "set-default-file-modes",
|
||||||
|
"set-default-toplevel-value", "set-file-acl", "set-file-modes",
|
||||||
|
"set-file-selinux-context", "set-file-times", "set-fontset-font",
|
||||||
|
"set-frame-height", "set-frame-position", "set-frame-selected-window",
|
||||||
|
"set-frame-size", "set-frame-width", "set-fringe-bitmap-face",
|
||||||
|
"set-input-interrupt-mode", "set-input-meta-mode", "set-input-mode",
|
||||||
|
"set-keyboard-coding-system-internal", "set-keymap-parent",
|
||||||
|
"set-marker", "set-marker-insertion-type", "set-match-data",
|
||||||
|
"set-message-beep", "set-minibuffer-window",
|
||||||
|
"set-mouse-pixel-position", "set-mouse-position",
|
||||||
|
"set-network-process-option", "set-output-flow-control",
|
||||||
|
"set-process-buffer", "set-process-coding-system",
|
||||||
|
"set-process-datagram-address", "set-process-filter",
|
||||||
|
"set-process-filter-multibyte",
|
||||||
|
"set-process-inherit-coding-system-flag", "set-process-plist",
|
||||||
|
"set-process-query-on-exit-flag", "set-process-sentinel",
|
||||||
|
"set-process-window-size", "set-quit-char",
|
||||||
|
"set-safe-terminal-coding-system-internal", "set-screen-color",
|
||||||
|
"set-standard-case-table", "set-syntax-table",
|
||||||
|
"set-terminal-coding-system-internal", "set-terminal-local-value",
|
||||||
|
"set-terminal-parameter", "set-text-properties", "set-time-zone-rule",
|
||||||
|
"set-visited-file-modtime", "set-window-buffer",
|
||||||
|
"set-window-combination-limit", "set-window-configuration",
|
||||||
|
"set-window-dedicated-p", "set-window-display-table",
|
||||||
|
"set-window-fringes", "set-window-hscroll", "set-window-margins",
|
||||||
|
"set-window-new-normal", "set-window-new-pixel",
|
||||||
|
"set-window-new-total", "set-window-next-buffers",
|
||||||
|
"set-window-parameter", "set-window-point", "set-window-prev-buffers",
|
||||||
|
"set-window-redisplay-end-trigger", "set-window-scroll-bars",
|
||||||
|
"set-window-start", "set-window-vscroll", "setcar", "setcdr",
|
||||||
|
"setplist", "show-face-resources", "signal", "signal-process", "sin",
|
||||||
|
"single-key-description", "skip-chars-backward", "skip-chars-forward",
|
||||||
|
"skip-syntax-backward", "skip-syntax-forward", "sleep-for", "sort",
|
||||||
|
"sort-charsets", "special-variable-p", "split-char",
|
||||||
|
"split-window-internal", "sqrt", "standard-case-table",
|
||||||
|
"standard-category-table", "standard-syntax-table", "start-kbd-macro",
|
||||||
|
"start-process", "stop-process", "store-kbd-macro-event", "string",
|
||||||
|
"string-as-multibyte", "string-as-unibyte", "string-bytes",
|
||||||
|
"string-collate-equalp", "string-collate-lessp", "string-equal",
|
||||||
|
"string-lessp", "string-make-multibyte", "string-make-unibyte",
|
||||||
|
"string-match", "string-to-char", "string-to-multibyte",
|
||||||
|
"string-to-number", "string-to-syntax", "string-to-unibyte",
|
||||||
|
"string-width", "stringp", "subr-name", "subrp",
|
||||||
|
"subst-char-in-region", "substitute-command-keys",
|
||||||
|
"substitute-in-file-name", "substring", "substring-no-properties",
|
||||||
|
"suspend-emacs", "suspend-tty", "suspicious-object", "sxhash",
|
||||||
|
"symbol-function", "symbol-name", "symbol-plist", "symbol-value",
|
||||||
|
"symbolp", "syntax-table", "syntax-table-p", "system-groups",
|
||||||
|
"system-move-file-to-trash", "system-name", "system-users", "tan",
|
||||||
|
"terminal-coding-system", "terminal-list", "terminal-live-p",
|
||||||
|
"terminal-local-value", "terminal-name", "terminal-parameter",
|
||||||
|
"terminal-parameters", "terpri", "test-completion",
|
||||||
|
"text-char-description", "text-properties-at", "text-property-any",
|
||||||
|
"text-property-not-all", "this-command-keys",
|
||||||
|
"this-command-keys-vector", "this-single-command-keys",
|
||||||
|
"this-single-command-raw-keys", "time-add", "time-less-p",
|
||||||
|
"time-subtract", "tool-bar-get-system-style", "tool-bar-height",
|
||||||
|
"tool-bar-pixel-width", "top-level", "trace-redisplay",
|
||||||
|
"trace-to-stderr", "translate-region-internal", "transpose-regions",
|
||||||
|
"truncate", "try-completion", "tty-display-color-cells",
|
||||||
|
"tty-display-color-p", "tty-no-underline",
|
||||||
|
"tty-suppress-bold-inverse-default-colors", "tty-top-frame",
|
||||||
|
"tty-type", "type-of", "undo-boundary", "unencodable-char-position",
|
||||||
|
"unhandled-file-name-directory", "unibyte-char-to-multibyte",
|
||||||
|
"unibyte-string", "unicode-property-table-internal", "unify-charset",
|
||||||
|
"unintern", "unix-sync", "unlock-buffer", "upcase", "upcase-initials",
|
||||||
|
"upcase-initials-region", "upcase-region", "upcase-word",
|
||||||
|
"use-global-map", "use-local-map", "user-full-name",
|
||||||
|
"user-login-name", "user-real-login-name", "user-real-uid",
|
||||||
|
"user-uid", "variable-binding-locus", "vconcat", "vector",
|
||||||
|
"vector-or-char-table-p", "vectorp", "verify-visited-file-modtime",
|
||||||
|
"vertical-motion", "visible-frame-list", "visited-file-modtime",
|
||||||
|
"w16-get-clipboard-data", "w16-selection-exists-p",
|
||||||
|
"w16-set-clipboard-data", "w32-battery-status",
|
||||||
|
"w32-default-color-map", "w32-define-rgb-color",
|
||||||
|
"w32-display-monitor-attributes-list", "w32-frame-menu-bar-size",
|
||||||
|
"w32-frame-rect", "w32-get-clipboard-data",
|
||||||
|
"w32-get-codepage-charset", "w32-get-console-codepage",
|
||||||
|
"w32-get-console-output-codepage", "w32-get-current-locale-id",
|
||||||
|
"w32-get-default-locale-id", "w32-get-keyboard-layout",
|
||||||
|
"w32-get-locale-info", "w32-get-valid-codepages",
|
||||||
|
"w32-get-valid-keyboard-layouts", "w32-get-valid-locale-ids",
|
||||||
|
"w32-has-winsock", "w32-long-file-name", "w32-reconstruct-hot-key",
|
||||||
|
"w32-register-hot-key", "w32-registered-hot-keys",
|
||||||
|
"w32-selection-exists-p", "w32-send-sys-command",
|
||||||
|
"w32-set-clipboard-data", "w32-set-console-codepage",
|
||||||
|
"w32-set-console-output-codepage", "w32-set-current-locale",
|
||||||
|
"w32-set-keyboard-layout", "w32-set-process-priority",
|
||||||
|
"w32-shell-execute", "w32-short-file-name", "w32-toggle-lock-key",
|
||||||
|
"w32-unload-winsock", "w32-unregister-hot-key", "w32-window-exists-p",
|
||||||
|
"w32notify-add-watch", "w32notify-rm-watch",
|
||||||
|
"waiting-for-user-input-p", "where-is-internal", "widen",
|
||||||
|
"widget-apply", "widget-get", "widget-put",
|
||||||
|
"window-absolute-pixel-edges", "window-at", "window-body-height",
|
||||||
|
"window-body-width", "window-bottom-divider-width", "window-buffer",
|
||||||
|
"window-combination-limit", "window-configuration-frame",
|
||||||
|
"window-configuration-p", "window-dedicated-p",
|
||||||
|
"window-display-table", "window-edges", "window-end", "window-frame",
|
||||||
|
"window-fringes", "window-header-line-height", "window-hscroll",
|
||||||
|
"window-inside-absolute-pixel-edges", "window-inside-edges",
|
||||||
|
"window-inside-pixel-edges", "window-left-child",
|
||||||
|
"window-left-column", "window-line-height", "window-list",
|
||||||
|
"window-list-1", "window-live-p", "window-margins",
|
||||||
|
"window-minibuffer-p", "window-mode-line-height", "window-new-normal",
|
||||||
|
"window-new-pixel", "window-new-total", "window-next-buffers",
|
||||||
|
"window-next-sibling", "window-normal-size", "window-old-point",
|
||||||
|
"window-parameter", "window-parameters", "window-parent",
|
||||||
|
"window-pixel-edges", "window-pixel-height", "window-pixel-left",
|
||||||
|
"window-pixel-top", "window-pixel-width", "window-point",
|
||||||
|
"window-prev-buffers", "window-prev-sibling",
|
||||||
|
"window-redisplay-end-trigger", "window-resize-apply",
|
||||||
|
"window-resize-apply-total", "window-right-divider-width",
|
||||||
|
"window-scroll-bar-height", "window-scroll-bar-width",
|
||||||
|
"window-scroll-bars", "window-start", "window-system",
|
||||||
|
"window-text-height", "window-text-pixel-size", "window-text-width",
|
||||||
|
"window-top-child", "window-top-line", "window-total-height",
|
||||||
|
"window-total-width", "window-use-time", "window-valid-p",
|
||||||
|
"window-vscroll", "windowp", "write-char", "write-region",
|
||||||
|
"x-backspace-delete-keys-p", "x-change-window-property",
|
||||||
|
"x-change-window-property", "x-close-connection",
|
||||||
|
"x-close-connection", "x-create-frame", "x-create-frame",
|
||||||
|
"x-delete-window-property", "x-delete-window-property",
|
||||||
|
"x-disown-selection-internal", "x-display-backing-store",
|
||||||
|
"x-display-backing-store", "x-display-color-cells",
|
||||||
|
"x-display-color-cells", "x-display-grayscale-p",
|
||||||
|
"x-display-grayscale-p", "x-display-list", "x-display-list",
|
||||||
|
"x-display-mm-height", "x-display-mm-height", "x-display-mm-width",
|
||||||
|
"x-display-mm-width", "x-display-monitor-attributes-list",
|
||||||
|
"x-display-pixel-height", "x-display-pixel-height",
|
||||||
|
"x-display-pixel-width", "x-display-pixel-width", "x-display-planes",
|
||||||
|
"x-display-planes", "x-display-save-under", "x-display-save-under",
|
||||||
|
"x-display-screens", "x-display-screens", "x-display-visual-class",
|
||||||
|
"x-display-visual-class", "x-family-fonts", "x-file-dialog",
|
||||||
|
"x-file-dialog", "x-file-dialog", "x-focus-frame", "x-frame-geometry",
|
||||||
|
"x-frame-geometry", "x-get-atom-name", "x-get-resource",
|
||||||
|
"x-get-selection-internal", "x-hide-tip", "x-hide-tip",
|
||||||
|
"x-list-fonts", "x-load-color-file", "x-menu-bar-open-internal",
|
||||||
|
"x-menu-bar-open-internal", "x-open-connection", "x-open-connection",
|
||||||
|
"x-own-selection-internal", "x-parse-geometry", "x-popup-dialog",
|
||||||
|
"x-popup-menu", "x-register-dnd-atom", "x-select-font",
|
||||||
|
"x-select-font", "x-selection-exists-p", "x-selection-owner-p",
|
||||||
|
"x-send-client-message", "x-server-max-request-size",
|
||||||
|
"x-server-max-request-size", "x-server-vendor", "x-server-vendor",
|
||||||
|
"x-server-version", "x-server-version", "x-show-tip", "x-show-tip",
|
||||||
|
"x-synchronize", "x-synchronize", "x-uses-old-gtk-dialog",
|
||||||
|
"x-window-property", "x-window-property", "x-wm-set-size-hint",
|
||||||
|
"xw-color-defined-p", "xw-color-defined-p", "xw-color-values",
|
||||||
|
"xw-color-values", "xw-display-color-p", "xw-display-color-p",
|
||||||
|
"yes-or-no-p", "zlib-available-p", "zlib-decompress-region",
|
||||||
|
"forward-point",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsBuiltinFunctionHighlighted = []string{
|
||||||
|
"defvaralias", "provide", "require",
|
||||||
|
"with-no-warnings", "define-widget", "with-electric-help",
|
||||||
|
"throw", "defalias", "featurep",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsLambdaListKeywords = []string{
|
||||||
|
"&allow-other-keys", "&aux", "&body", "&environment", "&key", "&optional",
|
||||||
|
"&rest", "&whole",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsErrorKeywords = []string{
|
||||||
|
"cl-assert", "cl-check-type", "error", "signal",
|
||||||
|
"user-error", "warn",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmacsLisp lexer.
|
||||||
|
var EmacsLisp = Register(TypeRemappingLexer(MustNewXMLLexer(
|
||||||
|
embedded,
|
||||||
|
"embedded/emacslisp.xml",
|
||||||
|
), TypeMapping{
|
||||||
|
{NameVariable, NameFunction, emacsBuiltinFunction},
|
||||||
|
{NameVariable, NameBuiltin, emacsSpecialForms},
|
||||||
|
{NameVariable, NameException, emacsErrorKeywords},
|
||||||
|
{NameVariable, NameBuiltin, append(emacsBuiltinFunctionHighlighted, emacsMacros...)},
|
||||||
|
{NameVariable, KeywordPseudo, emacsLambdaListKeywords},
|
||||||
|
}))
|
||||||
154
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abap.xml
generated
vendored
Normal file
154
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abap.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ABAP</name>
|
||||||
|
<alias>abap</alias>
|
||||||
|
<filename>*.abap</filename>
|
||||||
|
<filename>*.ABAP</filename>
|
||||||
|
<mime_type>text/x-abap</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="common">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="^\*.*$">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\".*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="##\w+">
|
||||||
|
<token type="CommentSpecial"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="variable-names">
|
||||||
|
<rule pattern="<\S+>">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\w[\w~]*(?:(\[\])|->\*)?">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule>
|
||||||
|
<include state="common"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="CALL\s+(?:BADI|CUSTOMER-FUNCTION|FUNCTION)">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(CALL\s+(?:DIALOG|SCREEN|SUBSCREEN|SELECTION-SCREEN|TRANSACTION|TRANSFORMATION))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(FORM|PERFORM)(\s+)(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(PERFORM)(\s+)(\()(\w+)(\))">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(MODULE)(\s+)(\S+)(\s+)(INPUT|OUTPUT)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(METHOD)(\s+)([\w~]+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\s+)([\w\-]+)([=\-]>)([\w\-~]+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?<=(=|-)>)([\w\-~]+)(?=\()">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(TEXT)(-)(\d{3})">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(TEXT)(-)(\w{3})">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(ADD-CORRESPONDING|AUTHORITY-CHECK|CLASS-DATA|CLASS-EVENTS|CLASS-METHODS|CLASS-POOL|DELETE-ADJACENT|DIVIDE-CORRESPONDING|EDITOR-CALL|ENHANCEMENT-POINT|ENHANCEMENT-SECTION|EXIT-COMMAND|FIELD-GROUPS|FIELD-SYMBOLS|FUNCTION-POOL|INTERFACE-POOL|INVERTED-DATE|LOAD-OF-PROGRAM|LOG-POINT|MESSAGE-ID|MOVE-CORRESPONDING|MULTIPLY-CORRESPONDING|NEW-LINE|NEW-PAGE|NEW-SECTION|NO-EXTENSION|OUTPUT-LENGTH|PRINT-CONTROL|SELECT-OPTIONS|START-OF-SELECTION|SUBTRACT-CORRESPONDING|SYNTAX-CHECK|SYSTEM-EXCEPTIONS|TYPE-POOL|TYPE-POOLS|NO-DISPLAY)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?<![-\>])(CREATE\s+(PUBLIC|PRIVATE|DATA|OBJECT)|(PUBLIC|PRIVATE|PROTECTED)\s+SECTION|(TYPE|LIKE)\s+((LINE\s+OF|REF\s+TO|(SORTED|STANDARD|HASHED)\s+TABLE\s+OF))?|FROM\s+(DATABASE|MEMORY)|CALL\s+METHOD|(GROUP|ORDER) BY|HAVING|SEPARATED BY|GET\s+(BADI|BIT|CURSOR|DATASET|LOCALE|PARAMETER|PF-STATUS|(PROPERTY|REFERENCE)\s+OF|RUN\s+TIME|TIME\s+(STAMP)?)?|SET\s+(BIT|BLANK\s+LINES|COUNTRY|CURSOR|DATASET|EXTENDED\s+CHECK|HANDLER|HOLD\s+DATA|LANGUAGE|LEFT\s+SCROLL-BOUNDARY|LOCALE|MARGIN|PARAMETER|PF-STATUS|PROPERTY\s+OF|RUN\s+TIME\s+(ANALYZER|CLOCK\s+RESOLUTION)|SCREEN|TITLEBAR|UPADTE\s+TASK\s+LOCAL|USER-COMMAND)|CONVERT\s+((INVERTED-)?DATE|TIME|TIME\s+STAMP|TEXT)|(CLOSE|OPEN)\s+(DATASET|CURSOR)|(TO|FROM)\s+(DATA BUFFER|INTERNAL TABLE|MEMORY ID|DATABASE|SHARED\s+(MEMORY|BUFFER))|DESCRIBE\s+(DISTANCE\s+BETWEEN|FIELD|LIST|TABLE)|FREE\s(MEMORY|OBJECT)?|PROCESS\s+(BEFORE\s+OUTPUT|AFTER\s+INPUT|ON\s+(VALUE-REQUEST|HELP-REQUEST))|AT\s+(LINE-SELECTION|USER-COMMAND|END\s+OF|NEW)|AT\s+SELECTION-SCREEN(\s+(ON(\s+(BLOCK|(HELP|VALUE)-REQUEST\s+FOR|END\s+OF|RADIOBUTTON\s+GROUP))?|OUTPUT))?|SELECTION-SCREEN:?\s+((BEGIN|END)\s+OF\s+((TABBED\s+)?BLOCK|LINE|SCREEN)|COMMENT|FUNCTION\s+KEY|INCLUDE\s+BLOCKS|POSITION|PUSHBUTTON|SKIP|ULINE)|LEAVE\s+(LIST-PROCESSING|PROGRAM|SCREEN|TO LIST-PROCESSING|TO TRANSACTION)(ENDING|STARTING)\s+AT|FORMAT\s+(COLOR|INTENSIFIED|INVERSE|HOTSPOT|INPUT|FRAMES|RESET)|AS\s+(CHECKBOX|SUBSCREEN|WINDOW)|WITH\s+(((NON-)?UNIQUE)?\s+KEY|FRAME)|(BEGIN|END)\s+OF|DELETE(\s+ADJACENT\s+DUPLICATES\sFROM)?|COMPARING(\s+ALL\s+FIELDS)?|(INSERT|APPEND)(\s+INITIAL\s+LINE\s+(IN)?TO|\s+LINES\s+OF)?|IN\s+((BYTE|CHARACTER)\s+MODE|PROGRAM)|END-OF-(DEFINITION|PAGE|SELECTION)|WITH\s+FRAME(\s+TITLE)|(REPLACE|FIND)\s+((FIRST|ALL)\s+OCCURRENCES?\s+OF\s+)?(SUBSTRING|REGEX)?|MATCH\s+(LENGTH|COUNT|LINE|OFFSET)|(RESPECTING|IGNORING)\s+CASE|IN\s+UPDATE\s+TASK|(SOURCE|RESULT)\s+(XML)?|REFERENCE\s+INTO|AND\s+(MARK|RETURN)|CLIENT\s+SPECIFIED|CORRESPONDING\s+FIELDS\s+OF|IF\s+FOUND|FOR\s+EVENT|INHERITING\s+FROM|LEAVE\s+TO\s+SCREEN|LOOP\s+AT\s+(SCREEN)?|LOWER\s+CASE|MATCHCODE\s+OBJECT|MODIF\s+ID|MODIFY\s+SCREEN|NESTING\s+LEVEL|NO\s+INTERVALS|OF\s+STRUCTURE|RADIOBUTTON\s+GROUP|RANGE\s+OF|REF\s+TO|SUPPRESS DIALOG|TABLE\s+OF|UPPER\s+CASE|TRANSPORTING\s+NO\s+FIELDS|VALUE\s+CHECK|VISIBLE\s+LENGTH|HEADER\s+LINE|COMMON\s+PART)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(^|(?<=(\s|\.)))(ABBREVIATED|ABSTRACT|ADD|ALIASES|ALIGN|ALPHA|ASSERT|AS|ASSIGN(ING)?|AT(\s+FIRST)?|BACK|BLOCK|BREAK-POINT|CASE|CATCH|CHANGING|CHECK|CLASS|CLEAR|COLLECT|COLOR|COMMIT|CREATE|COMMUNICATION|COMPONENTS?|COMPUTE|CONCATENATE|CONDENSE|CONSTANTS|CONTEXTS|CONTINUE|CONTROLS|COUNTRY|CURRENCY|DATA|DATE|DECIMALS|DEFAULT|DEFINE|DEFINITION|DEFERRED|DEMAND|DETAIL|DIRECTORY|DIVIDE|DO|DUMMY|ELSE(IF)?|ENDAT|ENDCASE|ENDCATCH|ENDCLASS|ENDDO|ENDFORM|ENDFUNCTION|ENDIF|ENDINTERFACE|ENDLOOP|ENDMETHOD|ENDMODULE|ENDSELECT|ENDTRY|ENDWHILE|ENHANCEMENT|EVENTS|EXACT|EXCEPTIONS?|EXIT|EXPONENT|EXPORT|EXPORTING|EXTRACT|FETCH|FIELDS?|FOR|FORM|FORMAT|FREE|FROM|FUNCTION|HIDE|ID|IF|IMPORT|IMPLEMENTATION|IMPORTING|IN|INCLUDE|INCLUDING|INDEX|INFOTYPES|INITIALIZATION|INTERFACE|INTERFACES|INTO|LANGUAGE|LEAVE|LENGTH|LINES|LOAD|LOCAL|JOIN|KEY|NEXT|MAXIMUM|MESSAGE|METHOD[S]?|MINIMUM|MODULE|MODIFIER|MODIFY|MOVE|MULTIPLY|NODES|NUMBER|OBLIGATORY|OBJECT|OF|OFF|ON|OTHERS|OVERLAY|PACK|PAD|PARAMETERS|PERCENTAGE|POSITION|PROGRAM|PROVIDE|PUBLIC|PUT|PF\d\d|RAISE|RAISING|RANGES?|READ|RECEIVE|REDEFINITION|REFRESH|REJECT|REPORT|RESERVE|RESUME|RETRY|RETURN|RETURNING|RIGHT|ROLLBACK|REPLACE|SCROLL|SEARCH|SELECT|SHIFT|SIGN|SINGLE|SIZE|SKIP|SORT|SPLIT|STATICS|STOP|STYLE|SUBMATCHES|SUBMIT|SUBTRACT|SUM(?!\()|SUMMARY|SUMMING|SUPPLY|TABLE|TABLES|TIMESTAMP|TIMES?|TIMEZONE|TITLE|\??TO|TOP-OF-PAGE|TRANSFER|TRANSLATE|TRY|TYPES|ULINE|UNDER|UNPACK|UPDATE|USING|VALUE|VALUES|VIA|VARYING|VARY|WAIT|WHEN|WHERE|WIDTH|WHILE|WITH|WINDOW|WRITE|XSD|ZERO)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(abs|acos|asin|atan|boolc|boolx|bit_set|char_off|charlen|ceil|cmax|cmin|condense|contains|contains_any_of|contains_any_not_of|concat_lines_of|cos|cosh|count|count_any_of|count_any_not_of|dbmaxlen|distance|escape|exp|find|find_end|find_any_of|find_any_not_of|floor|frac|from_mixed|insert|lines|log|log10|match|matches|nmax|nmin|numofchar|repeat|replace|rescale|reverse|round|segment|shift_left|shift_right|sign|sin|sinh|sqrt|strlen|substring|substring_after|substring_from|substring_before|substring_to|tan|tanh|to_upper|to_lower|to_mixed|translate|trunc|xstrlen)(\()\b">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameBuiltin"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="&[0-9]">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9]+">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?<=(\s|.))(AND|OR|EQ|NE|GT|LT|GE|LE|CO|CN|CA|NA|CS|NOT|NS|CP|NP|BYTE-CO|BYTE-CN|BYTE-CA|BYTE-NA|BYTE-CS|BYTE-NS|IS\s+(NOT\s+)?(INITIAL|ASSIGNED|REQUESTED|BOUND))\b">
|
||||||
|
<token type="OperatorWord"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="variable-names"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[?*<>=\-+&]">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'(''|[^'])*'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="`([^`])*`">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([|}])([^{}|]*?)([|{])">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[/;:()\[\],.]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(!)(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="Name"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
66
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abnf.xml
generated
vendored
Normal file
66
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abnf.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ABNF</name>
|
||||||
|
<alias>abnf</alias>
|
||||||
|
<filename>*.abnf</filename>
|
||||||
|
<mime_type>text/x-abnf</mime_type>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern=";.*$">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(%[si])?"[^"]*"">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%b[01]+\-[01]+\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%b[01]+(\.[01]+)*\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%d[0-9]+\-[0-9]+\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%d[0-9]+(\.[0-9]+)*\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%x[0-9a-fA-F]+\-[0-9a-fA-F]+\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%x[0-9a-fA-F]+(\.[0-9a-fA-F]+)*\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b[0-9]+\*[0-9]+">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b[0-9]+\*">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b[0-9]+">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(HEXDIG|DQUOTE|DIGIT|VCHAR|OCTET|ALPHA|CHAR|CRLF|HTAB|LWSP|BIT|CTL|WSP|LF|SP|CR)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[a-zA-Z][a-zA-Z0-9-]+\b">
|
||||||
|
<token type="NameClass"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(=/|=|/)">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[\[\]()]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=".">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
68
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript.xml
generated
vendored
Normal file
68
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ActionScript</name>
|
||||||
|
<alias>as</alias>
|
||||||
|
<alias>actionscript</alias>
|
||||||
|
<filename>*.as</filename>
|
||||||
|
<mime_type>application/x-actionscript</mime_type>
|
||||||
|
<mime_type>text/x-actionscript</mime_type>
|
||||||
|
<mime_type>text/actionscript</mime_type>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
<not_multiline>true</not_multiline>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="//.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/\*.*?\*/">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/(\\\\|\\/|[^/\n])*/[gim]*">
|
||||||
|
<token type="LiteralStringRegex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[~^*!%&<>|+=:;,/?\\-]+">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[{}\[\]();.]+">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(instanceof|arguments|continue|default|typeof|switch|return|catch|break|while|throw|each|this|with|else|case|var|new|for|try|if|do|in)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(implements|protected|namespace|interface|intrinsic|override|function|internal|private|package|extends|dynamic|import|native|return|public|static|class|const|super|final|get|set)\b">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(true|false|null|NaN|Infinity|-Infinity|undefined|Void)\b">
|
||||||
|
<token type="KeywordConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(IDynamicPropertyOutputIDynamicPropertyWriter|DisplacmentMapFilterMode|AccessibilityProperties|ContextMenuBuiltInItems|SharedObjectFlushStatus|DisplayObjectContainer|IllegalOperationError|DisplacmentMapFilter|InterpolationMethod|URLLoaderDataFormat|PrintJobOrientation|ActionScriptVersion|BitmapFilterQuality|GradientBevelFilter|GradientGlowFilter|DeleteObjectSample|StackOverflowError|SoundLoaderContext|ScriptTimeoutError|SecurityErrorEvent|InteractiveObject|StageDisplayState|FileReferenceList|TextFieldAutoSize|ApplicationDomain|BitmapDataChannel|ColorMatrixFilter|ExternalInterface|IMEConversionMode|DropShadowFilter|URLRequestHeader|ContextMenuEvent|ConvultionFilter|URLRequestMethod|BitmapFilterType|IEventDispatcher|ContextMenuItem|LocalConnection|InvalidSWFError|AsyncErrorEvent|MovieClipLoader|IBitmapDrawable|PrintJobOptions|EventDispatcher|NewObjectSample|HTTPStatusEvent|TextFormatAlign|IExternalizable|FullScreenEvent|DefinitionError|TextLineMetrics|NetStatusEvent|ColorTransform|ObjectEncoding|SecurityDomain|StageScaleMode|FocusDirection|ReferenceError|SoundTransform|KeyboardEvent|DisplayObject|PixelSnapping|LoaderContext|NetConnection|SecurityPanel|SecurityError|FileReference|AsBroadcaster|LineScaleMode|AntiAliasType|Accessibility|TextFieldType|URLVariabeles|ActivityEvent|ProgressEvent|TextColorType|StageQuality|TextSnapshot|Capabilities|BitmapFilter|SpreadMethod|GradientType|TextRenderer|SoundChannel|SharedObject|IOErrorEvent|SimpleButton|ContextMenu|InvokeEvent|CSMSettings|SyntaxError|StatusEvent|KeyLocation|IDataOutput|VerifyError|XMLDocument|XMLNodeType|MemoryError|GridFitType|BevelFilter|ErrorEvent|FrameLabel|GlowFilter|LoaderInfo|Microphone|MorphShape|BlurFilter|MouseEvent|FocusEvent|SoundMixer|FileFilter|TimerEvent|JointStyle|EventPhase|StageAlign|Dictionary|URLRequest|StyleSheet|SWFVersion|IDataInput|StaticText|RangeError|BitmapData|TextFormat|StackFrame|Namespace|SyncEvent|Rectangle|URLLoader|TypeError|Responder|NetStream|BlendMode|CapsStyle|DataEvent|ByteArray|MovieClip|Transform|TextField|Selection|AVM1Movie|XMLSocket|URLStream|FontStyle|EvalError|FontType|LoadVars|Graphics|Security|IMEEvent|URIError|Keyboard|Function|EOFError|PrintJob|IOError|XMLList|Boolean|ID3Info|XMLNode|Bitmap|String|RegExp|Sample|Object|Sprite|System|Endian|Matrix|Camera|Locale|Number|Loader|Socket|QName|Class|Timer|Sound|Shape|XMLUI|Mouse|Scene|Stage|Color|Point|Video|Error|Event|Proxy|Array|Date|uint|Math|Font|int|Key|IME|XML)\b">
|
||||||
|
<token type="NameBuiltin"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(decodeURIComponent|updateAfterEvent|clearInterval|setInterval|getVersion|parseFloat|fscommand|isXMLName|encodeURI|decodeURI|getTimer|unescape|isFinite|parseInt|getURL|escape|trace|isNaN|eval)\b">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[$a-zA-Z_]\w*">
|
||||||
|
<token type="NameOther"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0x[0-9a-f]+">
|
||||||
|
<token type="LiteralNumberHex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9]+">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'(\\\\|\\'|[^'])*'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
163
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript_3.xml
generated
vendored
Normal file
163
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript_3.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ActionScript 3</name>
|
||||||
|
<alias>as3</alias>
|
||||||
|
<alias>actionscript3</alias>
|
||||||
|
<filename>*.as</filename>
|
||||||
|
<mime_type>application/x-actionscript3</mime_type>
|
||||||
|
<mime_type>text/x-actionscript3</mime_type>
|
||||||
|
<mime_type>text/actionscript3</mime_type>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="funcparams">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\s*)(\.\.\.)?([$a-zA-Z_]\w*)(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?|\*)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Name"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="defval"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\)">
|
||||||
|
<token type="Operator"/>
|
||||||
|
<push state="type"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="type">
|
||||||
|
<rule pattern="(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?|\*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</bygroups>
|
||||||
|
<pop depth="2"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
<pop depth="2"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<pop depth="2"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="defval">
|
||||||
|
<rule pattern="(=)(\s*)([^(),]+)(\s*)(,?)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<usingself state="root"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
</bygroups>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=",">
|
||||||
|
<token type="Operator"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(function\s+)([$a-zA-Z_]\w*)(\s*)(\()">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="funcparams"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(var|const)(\s+)([$a-zA-Z_]\w*)(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Name"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(import|package)(\s+)((?:[$a-zA-Z_]\w*|\.)+)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameNamespace"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(new)(\s+)([$a-zA-Z_]\w*(?:\.<\w+>)?)(\s*)(\()">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="//.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/\*.*?\*/">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/(\\\\|\\/|[^\n])*/[gisx]*">
|
||||||
|
<token type="LiteralStringRegex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\.)([$a-zA-Z_]\w*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="NameAttribute"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(case|default|for|each|in|while|do|break|return|continue|if|else|throw|try|catch|with|new|typeof|arguments|instanceof|this|switch|import|include|as|is)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(class|public|final|internal|native|override|private|protected|static|import|extends|implements|interface|intrinsic|return|super|dynamic|function|const|get|namespace|package|set)\b">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(true|false|null|NaN|Infinity|-Infinity|undefined|void)\b">
|
||||||
|
<token type="KeywordConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(decodeURI|decodeURIComponent|encodeURI|escape|eval|isFinite|isNaN|isXMLName|clearInterval|fscommand|getTimer|getURL|getVersion|isFinite|parseFloat|parseInt|setInterval|trace|updateAfterEvent|unescape)\b">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[$a-zA-Z_]\w*">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0x[0-9a-f]+">
|
||||||
|
<token type="LiteralNumberHex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9]+">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'(\\\\|\\'|[^'])*'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[~^*!%&<>|+=:;,/?\\{}\[\]().-]+">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
321
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ada.xml
generated
vendored
Normal file
321
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ada.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,321 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>Ada</name>
|
||||||
|
<alias>ada</alias>
|
||||||
|
<alias>ada95</alias>
|
||||||
|
<alias>ada2005</alias>
|
||||||
|
<filename>*.adb</filename>
|
||||||
|
<filename>*.ads</filename>
|
||||||
|
<filename>*.ada</filename>
|
||||||
|
<mime_type>text/x-ada</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="end">
|
||||||
|
<rule pattern="(if|case|record|loop|select)">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""[^"]+"|[\w.]+">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="array_def">
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\w+)(\s+)(range)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="package_instantiation">
|
||||||
|
<rule pattern="("[^"]+"|\w+)(\s+)(=>)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[\w.\'"]">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\)">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="subprogram">
|
||||||
|
<rule pattern="\(">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="#pop" state="formal_part"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="is\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""[^"]+"|\w+">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="type_def">
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\(">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="formal_part"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="with|and|use">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="array\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<push state="#pop" state="array_def"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="record\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<push state="record_def"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(null record)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="import">
|
||||||
|
<rule pattern="[\w.]+">
|
||||||
|
<token type="NameNamespace"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="formal_part">
|
||||||
|
<rule pattern="\)">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\w+">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=",|:[^=]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(in|not|null|out|access)\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="package">
|
||||||
|
<rule pattern="body">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="is\s+new|renames">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="is">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\(">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="package_instantiation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([\w.]+)">
|
||||||
|
<token type="NameClass"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="attribute">
|
||||||
|
<rule pattern="(')(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameAttribute"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="record_def">
|
||||||
|
<rule pattern="end record">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="[^\S\n]+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="--.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^\S\n]+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="function|procedure|entry">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<push state="subprogram"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(subtype|type)(\s+)(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="type_def"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="task|protected">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(subtype)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(end)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="end"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(pragma)(\s+)(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="CommentPreproc"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(true|false|null)\b">
|
||||||
|
<token type="KeywordConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(Short_Short_Integer|Short_Short_Float|Long_Long_Integer|Long_Long_Float|Wide_Character|Reference_Type|Short_Integer|Long_Integer|Wide_String|Short_Float|Controlled|Long_Float|Character|Generator|File_Type|File_Mode|Positive|Duration|Boolean|Natural|Integer|Address|Cursor|String|Count|Float|Byte)\b">
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(and(\s+then)?|in|mod|not|or(\s+else)|rem)\b">
|
||||||
|
<token type="OperatorWord"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="generic|private">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="package">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<push state="package"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="array\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<push state="array_def"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(with|use)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordNamespace"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="import"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\w+)(\s*)(:)(\s*)(constant)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameConstant"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="<<\w+>>">
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\w+)(\s*)(:)(\s*)(declare|begin|loop|for|while)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(synchronized|overriding|terminate|interface|exception|protected|separate|constant|abstract|renames|reverse|subtype|aliased|declare|requeue|limited|return|tagged|access|record|select|accept|digits|others|pragma|entry|elsif|delta|delay|array|until|range|raise|while|begin|abort|else|loop|when|type|null|then|body|task|goto|case|exit|end|for|abs|xor|all|new|out|is|of|if|or|do|at)\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""[^"]*"">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="attribute"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="numbers"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'[^']'">
|
||||||
|
<token type="LiteralStringChar"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\w+)(\s*|[(,])">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Name"/>
|
||||||
|
<usingself state="root"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(<>|=>|:=|[()|:;,.'])">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[*<>+=/&-]">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\n+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="numbers">
|
||||||
|
<rule pattern="[0-9_]+#[0-9a-f]+#">
|
||||||
|
<token type="LiteralNumberHex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9_]+\.[0-9_]*">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9_]+">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
66
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/agda.xml
generated
vendored
Normal file
66
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/agda.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>Agda</name>
|
||||||
|
<alias>agda</alias>
|
||||||
|
<filename>*.agda</filename>
|
||||||
|
<mime_type>text/x-agda</mime_type>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="^(\s*)([^\s(){}]+)(\s*)(:)(\s*)"><bygroups><token type="TextWhitespace"/><token type="NameFunction"/><token type="TextWhitespace"/><token type="OperatorWord"/><token type="TextWhitespace"/></bygroups></rule>
|
||||||
|
<rule pattern="--(?![!#$%&*+./<=>?@^|_~:\\]).*?$"><token type="CommentSingle"/></rule>
|
||||||
|
<rule pattern="\{-"><token type="CommentMultiline"/><push state="comment"/></rule>
|
||||||
|
<rule pattern="\{!"><token type="CommentMultiline"/><push state="hole"/></rule>
|
||||||
|
<rule pattern="\b(abstract|codata|coinductive|constructor|data|do|eta-equality|field|forall|hiding|in|inductive|infix|infixl|infixr|instance|interleaved|let|macro|mutual|no-eta-equality|open|overlap|pattern|postulate|primitive|private|quote|quoteTerm|record|renaming|rewrite|syntax|tactic|unquote|unquoteDecl|unquoteDef|using|variable|where|with)(?!\')\b"><token type="KeywordReserved"/></rule>
|
||||||
|
<rule pattern="(import|module)(\s+)"><bygroups><token type="KeywordReserved"/><token type="TextWhitespace"/></bygroups><push state="module"/></rule>
|
||||||
|
<rule pattern="\b(Set|Prop)[\u2080-\u2089]*\b"><token type="KeywordType"/></rule>
|
||||||
|
<rule pattern="(\(|\)|\{|\})"><token type="Operator"/></rule>
|
||||||
|
<rule pattern="(\.{1,3}|\||\u03BB|\u2200|\u2192|:|=|->)"><token type="OperatorWord"/></rule>
|
||||||
|
<rule pattern="\d+[eE][+-]?\d+"><token type="LiteralNumberFloat"/></rule>
|
||||||
|
<rule pattern="\d+\.\d+([eE][+-]?\d+)?"><token type="LiteralNumberFloat"/></rule>
|
||||||
|
<rule pattern="0[xX][\da-fA-F]+"><token type="LiteralNumberHex"/></rule>
|
||||||
|
<rule pattern="\d+"><token type="LiteralNumberInteger"/></rule>
|
||||||
|
<rule pattern="'"><token type="LiteralStringChar"/><push state="character"/></rule>
|
||||||
|
<rule pattern="""><token type="LiteralString"/><push state="string"/></rule>
|
||||||
|
<rule pattern="[^\s(){}]+"><token type="Text"/></rule>
|
||||||
|
<rule pattern="\s+?"><token type="TextWhitespace"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="hole">
|
||||||
|
<rule pattern="[^!{}]+"><token type="CommentMultiline"/></rule>
|
||||||
|
<rule pattern="\{!"><token type="CommentMultiline"/><push/></rule>
|
||||||
|
<rule pattern="!\}"><token type="CommentMultiline"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[!{}]"><token type="CommentMultiline"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="module">
|
||||||
|
<rule pattern="\{-"><token type="CommentMultiline"/><push state="comment"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z][\w.\']*"><token type="Name"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[\W0-9_]+"><token type="Text"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="comment">
|
||||||
|
<rule pattern="[^-{}]+"><token type="CommentMultiline"/></rule>
|
||||||
|
<rule pattern="\{-"><token type="CommentMultiline"/><push/></rule>
|
||||||
|
<rule pattern="-\}"><token type="CommentMultiline"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[-{}]"><token type="CommentMultiline"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="character">
|
||||||
|
<rule pattern="[^\\']'"><token type="LiteralStringChar"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="\\"><token type="LiteralStringEscape"/><push state="escape"/></rule>
|
||||||
|
<rule pattern="'"><token type="LiteralStringChar"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="string">
|
||||||
|
<rule pattern="[^\\"]+"><token type="LiteralString"/></rule>
|
||||||
|
<rule pattern="\\"><token type="LiteralStringEscape"/><push state="escape"/></rule>
|
||||||
|
<rule pattern="""><token type="LiteralString"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="escape">
|
||||||
|
<rule pattern="[abfnrtv"\'&\\]"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="\^[][A-ZÀ-ÖØ-ÞĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮİIJĴĶĹĻĽĿŁŃŅŇŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŸ-ŹŻŽƁ-ƂƄƆ-ƇƉ-ƋƎ-ƑƓ-ƔƖ-ƘƜ-ƝƟ-ƠƢƤƦ-ƧƩƬƮ-ƯƱ-ƳƵƷ-ƸƼDŽLJNJǍǏǑǓǕǗǙǛǞǠǢǤǦǨǪǬǮDZǴǶ-ǸǺǼǾȀȂȄȆȈȊȌȎȐȒȔȖȘȚȜȞȠȢȤȦȨȪȬȮȰȲȺ-ȻȽ-ȾɁɃ-ɆɈɊɌɎͰͲͶͿΆΈ-ΊΌΎ-ΏΑ-ΡΣ-ΫϏϒ-ϔϘϚϜϞϠϢϤϦϨϪϬϮϴϷϹ-ϺϽ-ЯѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎҐҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾӀ-ӁӃӅӇӉӋӍӐӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӶӸӺӼӾԀԂԄԆԈԊԌԎԐԒԔԖԘԚԜԞԠԢԤԦԨԪԬԮԱ-ՖႠ-ჅჇჍᎠ-ᏵᲐ-ᲺᲽ-ᲿḀḂḄḆḈḊḌḎḐḒḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸḺḼḾṀṂṄṆṈṊṌṎṐṒṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎẐẒẔẞẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎỐỒỔỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸỺỼỾἈ-ἏἘ-ἝἨ-ἯἸ-ἿὈ-ὍὙὛὝὟὨ-ὯᾸ-ΆῈ-ΉῘ-ΊῨ-ῬῸ-Ώℂℇℋ-ℍℐ-ℒℕℙ-ℝℤΩℨK-ℭℰ-ℳℾ-ℿⅅↃⰀ-ⰮⱠⱢ-ⱤⱧⱩⱫⱭ-ⱰⱲⱵⱾ-ⲀⲂⲄⲆⲈⲊⲌⲎⲐⲒⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⲲⲴⲶⲸⲺⲼⲾⳀⳂⳄⳆⳈⳊⳌⳎⳐⳒⳔⳖⳘⳚⳜⳞⳠⳢⳫⳭⳲꙀꙂꙄꙆꙈꙊꙌꙎꙐꙒꙔꙖꙘꙚꙜꙞꙠꙢꙤꙦꙨꙪꙬꚀꚂꚄꚆꚈꚊꚌꚎꚐꚒꚔꚖꚘꚚꜢꜤꜦꜨꜪꜬꜮꜲꜴꜶꜸꜺꜼꜾꝀꝂꝄꝆꝈꝊꝌꝎꝐꝒꝔꝖꝘꝚꝜꝞꝠꝢꝤꝦꝨꝪꝬꝮꝹꝻꝽ-ꝾꞀꞂꞄꞆꞋꞍꞐꞒꞖꞘꞚꞜꞞꞠꞢꞤꞦꞨꞪ-ꞮꞰ-ꞴꞶꞸA-Z𐐀-𐐧𐒰-𐓓𐲀-𐲲𑢠-𑢿𖹀-𖹟𝐀-𝐙𝐴-𝑍𝑨-𝒁𝒜𝒞-𝒟𝒢𝒥-𝒦𝒩-𝒬𝒮-𝒵𝓐-𝓩𝔄-𝔅𝔇-𝔊𝔍-𝔔𝔖-𝔜𝔸-𝔹𝔻-𝔾𝕀-𝕄𝕆𝕊-𝕐𝕬-𝖅𝖠-𝖹𝗔-𝗭𝘈-𝘡𝘼-𝙕𝙰-𝚉𝚨-𝛀𝛢-𝛺𝜜-𝜴𝝖-𝝮𝞐-𝞨𝟊𞤀-𞤡@^_]"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="NUL|SOH|[SE]TX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|S[OI]|DLE|DC[1-4]|NAK|SYN|ETB|CAN|EM|SUB|ESC|[FGRU]S|SP|DEL"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="o[0-7]+"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="x[\da-fA-F]+"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="\d+"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="(\s+)(\\)"><bygroups><token type="TextWhitespace"/><token type="LiteralStringEscape"/></bygroups><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
|
|
||||||
75
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/al.xml
generated
vendored
Normal file
75
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/al.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>AL</name>
|
||||||
|
<alias>al</alias>
|
||||||
|
<filename>*.al</filename>
|
||||||
|
<filename>*.dal</filename>
|
||||||
|
<mime_type>text/x-al</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?s)\/\*.*?\\*\*\/">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?s)//.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\"([^\"])*\"">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'([^'])*'">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(ARRAY|ASSERTERROR|BEGIN|BREAK|CASE|DO|DOWNTO|ELSE|END|EVENT|EXIT|FOR|FOREACH|FUNCTION|IF|IMPLEMENTS|IN|INDATASET|INTERFACE|INTERNAL|LOCAL|OF|PROCEDURE|PROGRAM|PROTECTED|REPEAT|RUNONCLIENT|SECURITYFILTERING|SUPPRESSDISPOSE|TEMPORARY|THEN|TO|TRIGGER|UNTIL|VAR|WHILE|WITH|WITHEVENTS))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(AND|DIV|MOD|NOT|OR|XOR))\b">
|
||||||
|
<token type="OperatorWord"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(AVERAGE|CONST|COUNT|EXIST|FIELD|FILTER|LOOKUP|MAX|MIN|ORDER|SORTING|SUM|TABLEDATA|UPPERLIMIT|WHERE|ASCENDING|DESCENDING))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(CODEUNIT|PAGE|PAGEEXTENSION|PAGECUSTOMIZATION|DOTNET|ENUM|ENUMEXTENSION|VALUE|QUERY|REPORT|TABLE|TABLEEXTENSION|XMLPORT|PROFILE|CONTROLADDIN|REPORTEXTENSION|INTERFACE|PERMISSIONSET|PERMISSIONSETEXTENSION|ENTITLEMENT))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(Action|Array|Automation|BigInteger|BigText|Blob|Boolean|Byte|Char|ClientType|Code|Codeunit|CompletionTriggerErrorLevel|ConnectionType|Database|DataClassification|DataScope|Date|DateFormula|DateTime|Decimal|DefaultLayout|Dialog|Dictionary|DotNet|DotNetAssembly|DotNetTypeDeclaration|Duration|Enum|ErrorInfo|ErrorType|ExecutionContext|ExecutionMode|FieldClass|FieldRef|FieldType|File|FilterPageBuilder|Guid|InStream|Integer|Joker|KeyRef|List|ModuleDependencyInfo|ModuleInfo|None|Notification|NotificationScope|ObjectType|Option|OutStream|Page|PageResult|Query|Record|RecordId|RecordRef|Report|ReportFormat|SecurityFilter|SecurityFiltering|Table|TableConnectionType|TableFilter|TestAction|TestField|TestFilterField|TestPage|TestPermissions|TestRequestPage|Text|TextBuilder|TextConst|TextEncoding|Time|TransactionModel|TransactionType|Variant|Verbosity|Version|XmlPort|HttpContent|HttpHeaders|HttpClient|HttpRequestMessage|HttpResponseMessage|JsonToken|JsonValue|JsonArray|JsonObject|View|Views|XmlAttribute|XmlAttributeCollection|XmlComment|XmlCData|XmlDeclaration|XmlDocument|XmlDocumentType|XmlElement|XmlNamespaceManager|XmlNameTable|XmlNode|XmlNodeList|XmlProcessingInstruction|XmlReadOptions|XmlText|XmlWriteOptions|WebServiceActionContext|WebServiceActionResultCode|SessionSettings))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b([<>]=|<>|<|>)\b?">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(\-|\+|\/|\*)\b">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s*(\:=|\+=|-=|\/=|\*=)\s*?">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(ADD|ADDFIRST|ADDLAST|ADDAFTER|ADDBEFORE|ACTION|ACTIONS|AREA|ASSEMBLY|CHARTPART|CUEGROUP|CUSTOMIZES|COLUMN|DATAITEM|DATASET|ELEMENTS|EXTENDS|FIELD|FIELDGROUP|FIELDATTRIBUTE|FIELDELEMENT|FIELDGROUPS|FIELDS|FILTER|FIXED|GRID|GROUP|MOVEAFTER|MOVEBEFORE|KEY|KEYS|LABEL|LABELS|LAYOUT|MODIFY|MOVEFIRST|MOVELAST|MOVEBEFORE|MOVEAFTER|PART|REPEATER|USERCONTROL|REQUESTPAGE|SCHEMA|SEPARATOR|SYSTEMPART|TABLEELEMENT|TEXTATTRIBUTE|TEXTELEMENT|TYPE))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s*[(\.\.)&\|]\s*">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\b">
|
||||||
|
<token type="LiteralNumber"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[;:,]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="#[ \t]*(if|else|elif|endif|define|undef|region|endregion|pragma)\b.*?\n">
|
||||||
|
<token type="CommentPreproc"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\w+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=".">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
58
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/alloy.xml
generated
vendored
Normal file
58
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/alloy.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>Alloy</name>
|
||||||
|
<alias>alloy</alias>
|
||||||
|
<filename>*.als</filename>
|
||||||
|
<mime_type>text/x-alloy</mime_type>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="sig">
|
||||||
|
<rule pattern="(extends)\b"><token type="Keyword"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z_][\w]*"*"><token type="Name"/></rule>
|
||||||
|
<rule pattern="[^\S\n]+"><token type="TextWhitespace"/></rule>
|
||||||
|
<rule pattern=","><token type="Punctuation"/></rule>
|
||||||
|
<rule pattern="\{"><token type="Operator"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="module">
|
||||||
|
<rule pattern="[^\S\n]+"><token type="TextWhitespace"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z_][\w]*"*"><token type="Name"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="fun">
|
||||||
|
<rule pattern="[^\S\n]+"><token type="TextWhitespace"/></rule>
|
||||||
|
<rule pattern="\{"><token type="Operator"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z_][\w]*"*"><token type="Name"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="fact">
|
||||||
|
<rule><include state="fun"/></rule>
|
||||||
|
<rule pattern=""\b(\\\\|\\[^\\]|[^"\\])*""><token type="LiteralString"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="--.*?$"><token type="CommentSingle"/></rule>
|
||||||
|
<rule pattern="//.*?$"><token type="CommentSingle"/></rule>
|
||||||
|
<rule pattern="/\*.*?\*/"><token type="CommentMultiline"/></rule>
|
||||||
|
<rule pattern="[^\S\n]+"><token type="TextWhitespace"/></rule>
|
||||||
|
<rule pattern="(module|open)(\s+)"><bygroups><token type="KeywordNamespace"/><token type="TextWhitespace"/></bygroups><push state="module"/></rule>
|
||||||
|
<rule pattern="(sig|enum)(\s+)"><bygroups><token type="KeywordDeclaration"/><token type="TextWhitespace"/></bygroups><push state="sig"/></rule>
|
||||||
|
<rule pattern="(iden|univ|none)\b"><token type="KeywordConstant"/></rule>
|
||||||
|
<rule pattern="(int|Int)\b"><token type="KeywordType"/></rule>
|
||||||
|
<rule pattern="(var|this|abstract|extends|set|seq|one|lone|let)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(all|some|no|sum|disj|when|else)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(run|check|for|but|exactly|expect|as|steps)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(always|after|eventually|until|release)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(historically|before|once|since|triggered)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(and|or|implies|iff|in)\b"><token type="OperatorWord"/></rule>
|
||||||
|
<rule pattern="(fun|pred|assert)(\s+)"><bygroups><token type="Keyword"/><token type="TextWhitespace"/></bygroups><push state="fun"/></rule>
|
||||||
|
<rule pattern="(fact)(\s+)"><bygroups><token type="Keyword"/><token type="TextWhitespace"/></bygroups><push state="fact"/></rule>
|
||||||
|
<rule pattern="!|#|&&|\+\+|<<|>>|>=|<=>|<=|\.\.|\.|->"><token type="Operator"/></rule>
|
||||||
|
<rule pattern="[-+/*%=<>&!^|~{}\[\]().\';]"><token type="Operator"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z_][\w]*"*"><token type="Name"/></rule>
|
||||||
|
<rule pattern="[:,]"><token type="Punctuation"/></rule>
|
||||||
|
<rule pattern="[0-9]+"><token type="LiteralNumberInteger"/></rule>
|
||||||
|
<rule pattern=""\b(\\\\|\\[^\\]|[^"\\])*""><token type="LiteralString"/></rule>
|
||||||
|
<rule pattern="\n"><token type="TextWhitespace"/></rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
|
|
||||||
109
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/angular2.xml
generated
vendored
Normal file
109
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/angular2.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>Angular2</name>
|
||||||
|
<alias>ng2</alias>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="attr">
|
||||||
|
<rule pattern="".*?"">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'.*?'">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^\s>]+">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="[^{([*#]+">
|
||||||
|
<token type="Other" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\{\{)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="ngExpression" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([([]+)([\w:.-]+)([\])]+)(\s*)(=)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="Operator" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="attr" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([([]+)([\w:.-]+)([\])]+)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="TextWhitespace" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([*#])([\w:.-]+)(\s*)(=)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="Operator" />
|
||||||
|
<token type="TextWhitespace" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="attr" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([*#])([\w:.-]+)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="ngExpression">
|
||||||
|
<rule pattern="\s+(\|\s+)?">
|
||||||
|
<token type="Text" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\}\}">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":?(true|false)">
|
||||||
|
<token type="LiteralStringBoolean" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":?"(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":?'(\\\\|\\'|[^'])*'">
|
||||||
|
<token type="LiteralStringSingle" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9](\.[0-9]*)?(eE[+-][0-9])?[flFLdD]?|0[xX][0-9a-fA-F]+[Ll]?">
|
||||||
|
<token type="LiteralNumber" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[a-zA-Z][\w-]*(\(.*\))?">
|
||||||
|
<token type="NameVariable" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\.[\w-]+(\(.*\))?">
|
||||||
|
<token type="NameVariable" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\?)(\s*)([^}\s]+)(\s*)(:)(\s*)([^}\s]+)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Operator" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="Operator" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
317
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/antlr.xml
generated
vendored
Normal file
317
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/antlr.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ANTLR</name>
|
||||||
|
<alias>antlr</alias>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="nested-arg-action">
|
||||||
|
<rule pattern="([^$\[\]\'"/]+|"(\\\\|\\"|[^"])*"|'(\\\\|\\'|[^'])*'|//.*$\n?|/\*(.|\n)*?\*/|/(?!\*)(\\\\|\\/|[^/])*/|/)+">
|
||||||
|
<token type="Other"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\[">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\$[a-zA-Z]+)(\.?)(text|value)?">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameProperty"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\\\\|\\\]|\\\[|[^\[\]])+">
|
||||||
|
<token type="Other"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="exception">
|
||||||
|
<rule pattern="\n">
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s">
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\[">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="nested-arg-action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="whitespace">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(lexer|parser|tree)?(\s*)(grammar\b)(\s*)([A-Za-z]\w*)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameClass"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="options\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="options"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="tokens\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="tokens"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(scope)(\s*)([A-Za-z]\w*)(\s*)(\{)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(catch|finally)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="exception"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(@[A-Za-z]\w*)(\s*)(::)?(\s*)([A-Za-z]\w*)(\s*)(\{)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="((?:protected|private|public|fragment)\b)?(\s*)([A-Za-z]\w*)(!)?">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="rule-alts" state="rule-prelims"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="tokens">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([A-Z]\w*)(\s*)(=)?(\s*)(\'(?:\\\\|\\\'|[^\']*)\')?(\s*)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\}">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="options">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([A-Za-z]\w*)(\s*)(=)(\s*)([A-Za-z]\w*|\'(?:\\\\|\\\'|[^\']*)\'|[0-9]+|\*)(\s*)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\}">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="rule-alts">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="options\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="options"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'(\\\\|\\'|[^'])*'">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="<<([^>]|>[^>])>>">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\$?[A-Z_]\w*">
|
||||||
|
<token type="NameConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\$?[a-z_]\w*">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\+|\||->|=>|=|\(|\)|\.\.|\.|\?|\*|\^|!|\#|~)">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=",">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\[">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="nested-arg-action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="rule-prelims">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="returns\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\[">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="nested-arg-action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(throws)(\s+)([A-Za-z]\w*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(,)(\s*)([A-Za-z]\w*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="options\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="options"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(scope)(\s+)(\{)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(scope)(\s+)([A-Za-z]\w*)(\s*)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(@[A-Za-z]\w*)(\s*)(\{)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="action">
|
||||||
|
<rule pattern="([^${}\'"/\\]+|"(\\\\|\\"|[^"])*"|'(\\\\|\\'|[^'])*'|//.*$\n?|/\*(.|\n)*?\*/|/(?!\*)(\\\\|\\/|[^/])*/|\\(?!%)|/)+">
|
||||||
|
<token type="Other"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\\)(%)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Other"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\$[a-zA-Z]+)(\.?)(text|value)?">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameProperty"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\}">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="comments">
|
||||||
|
<rule pattern="//.*$">
|
||||||
|
<token type="Comment"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/\*(.|\n)*?\*/">
|
||||||
|
<token type="Comment"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
74
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apacheconf.xml
generated
vendored
Normal file
74
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apacheconf.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ApacheConf</name>
|
||||||
|
<alias>apacheconf</alias>
|
||||||
|
<alias>aconf</alias>
|
||||||
|
<alias>apache</alias>
|
||||||
|
<filename>.htaccess</filename>
|
||||||
|
<filename>apache.conf</filename>
|
||||||
|
<filename>apache2.conf</filename>
|
||||||
|
<mime_type>text/x-apacheconf</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(#.*?)$">
|
||||||
|
<token type="Comment"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(<[^\s>]+)(?:(\s+)(.*?))?(>)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameTag"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
<token type="NameTag"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([a-z]\w*)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameBuiltin"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="value"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\.+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="value">
|
||||||
|
<rule pattern="\\\n">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="$">
|
||||||
|
<token type="Text"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\\">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^\S\n]+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\d+\.\d+\.\d+\.\d+(?:/\d+)?">
|
||||||
|
<token type="LiteralNumber"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\d+">
|
||||||
|
<token type="LiteralNumber"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/([a-z0-9][\w./-]+)">
|
||||||
|
<token type="LiteralStringOther"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(on|off|none|any|all|double|email|dns|min|minimal|os|productonly|full|emerg|alert|crit|error|warn|notice|info|debug|registry|script|inetd|standalone|user|group)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""([^"\\]*(?:\\.[^"\\]*)*)"">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^\s"\\]+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
59
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apl.xml
generated
vendored
Normal file
59
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apl.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>APL</name>
|
||||||
|
<alias>apl</alias>
|
||||||
|
<filename>*.apl</filename>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[⍝#].*$">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\'((\'\')|[^\'])*\'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(("")|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[⋄◇()]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[\[\];]">
|
||||||
|
<token type="LiteralStringRegex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="⎕[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[A-Za-zΔ∆⍙_][A-Za-zΔ∆⍙_¯0-9]*">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞)([Jj]¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞))?">
|
||||||
|
<token type="LiteralNumber"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[\.\\/⌿⍀¨⍣⍨⍠⍤∘⍥@⌺⌶⍢]">
|
||||||
|
<token type="NameAttribute"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[+\-×÷⌈⌊∣|⍳?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⌸⍯↗⊆⍸]">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="⍬">
|
||||||
|
<token type="NameConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[⎕⍞]">
|
||||||
|
<token type="NameVariableGlobal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[←→]">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[⍺⍵⍶⍹∇:]">
|
||||||
|
<token type="NameBuiltinPseudo"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[{}]">
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
151
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/applescript.xml
generated
vendored
Normal file
151
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/applescript.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>AppleScript</name>
|
||||||
|
<alias>applescript</alias>
|
||||||
|
<filename>*.applescript</filename>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="¬\n">
|
||||||
|
<token type="LiteralStringEscape" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'s\s+">
|
||||||
|
<token type="Text" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(--|#).*?$">
|
||||||
|
<token type="Comment" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\(\*">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
<push state="comment" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[(){}!,.:]">
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(«)([^»]+)(»)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b((?:considering|ignoring)\s*)(application responses|case|diacriticals|hyphens|numeric strings|punctuation|white space)"
|
||||||
|
>
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword" />
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(-|\*|\+|&|≠|>=?|<=?|=|≥|≤|/|÷|\^)">
|
||||||
|
<token type="Operator" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(and|or|is equal|equals|(is )?equal to|is not|isn't|isn't equal( to)?|is not equal( to)?|doesn't equal|does not equal|(is )?greater than|comes after|is not less than or equal( to)?|isn't less than or equal( to)?|(is )?less than|comes before|is not greater than or equal( to)?|isn't greater than or equal( to)?|(is )?greater than or equal( to)?|is not less than|isn't less than|does not come before|doesn't come before|(is )?less than or equal( to)?|is not greater than|isn't greater than|does not come after|doesn't come after|starts? with|begins? with|ends? with|contains?|does not contain|doesn't contain|is in|is contained by|is not in|is not contained by|isn't contained by|div|mod|not|(a )?(ref( to)?|reference to)|is|does)\b"
|
||||||
|
>
|
||||||
|
<token type="OperatorWord" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="^(\s*(?:on|end)\s+)(zoomed|write to file|will zoom|will show|will select tab view item|will resize( sub views)?|will resign active|will quit|will pop up|will open|will move|will miniaturize|will hide|will finish launching|will display outline cell|will display item cell|will display cell|will display browser cell|will dismiss|will close|will become active|was miniaturized|was hidden|update toolbar item|update parameters|update menu item|shown|should zoom|should selection change|should select tab view item|should select row|should select item|should select column|should quit( after last window closed)?|should open( untitled)?|should expand item|should end editing|should collapse item|should close|should begin editing|selection changing|selection changed|selected tab view item|scroll wheel|rows changed|right mouse up|right mouse dragged|right mouse down|resized( sub views)?|resigned main|resigned key|resigned active|read from file|prepare table drop|prepare table drag|prepare outline drop|prepare outline drag|prepare drop|plugin loaded|parameters updated|panel ended|opened|open untitled|number of rows|number of items|number of browser rows|moved|mouse up|mouse moved|mouse exited|mouse entered|mouse dragged|mouse down|miniaturized|load data representation|launched|keyboard up|keyboard down|items changed|item value changed|item value|item expandable|idle|exposed|end editing|drop|drag( (entered|exited|updated))?|double clicked|document nib name|dialog ended|deminiaturized|data representation|conclude drop|column resized|column moved|column clicked|closed|clicked toolbar item|clicked|choose menu item|child of item|changed|change item value|change cell value|cell value changed|cell value|bounds changed|begin editing|became main|became key|awake from nib|alert ended|activated|action|accept table drop|accept outline drop)"
|
||||||
|
>
|
||||||
|
<token type="Keyword" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="^(\s*)(in|on|script|to)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="Keyword" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(as )(alias |application |boolean |class |constant |date |file |integer |list |number |POSIX file |real |record |reference |RGB color |script |text |unit types|(?:Unicode )?text|string)\b"
|
||||||
|
>
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword" />
|
||||||
|
<token type="NameClass" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(AppleScript|current application|false|linefeed|missing value|pi|quote|result|return|space|tab|text item delimiters|true|version)\b"
|
||||||
|
>
|
||||||
|
<token type="NameConstant" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(ASCII (character|number)|activate|beep|choose URL|choose application|choose color|choose file( name)?|choose folder|choose from list|choose remote application|clipboard info|close( access)?|copy|count|current date|delay|delete|display (alert|dialog)|do shell script|duplicate|exists|get eof|get volume settings|info for|launch|list (disks|folder)|load script|log|make|mount volume|new|offset|open( (for access|location))?|path to|print|quit|random number|read|round|run( script)?|say|scripting components|set (eof|the clipboard to|volume)|store script|summarize|system attribute|system info|the clipboard|time to GMT|write|quoted form)\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(considering|else|error|exit|from|if|ignoring|in|repeat|tell|then|times|to|try|until|using terms from|while|with|with timeout( of)?|with transaction|by|continue|end|its?|me|my|return|of|as)\b"
|
||||||
|
>
|
||||||
|
<token type="Keyword" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(global|local|prop(erty)?|set|get)\b">
|
||||||
|
<token type="Keyword" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(but|put|returning|the)\b">
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(attachment|attribute run|character|day|month|paragraph|word|year)s?\b">
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(about|above|against|apart from|around|aside from|at|below|beneath|beside|between|for|given|instead of|on|onto|out of|over|since)\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(accepts arrow key|action method|active|alignment|allowed identifiers|allows branch selection|allows column reordering|allows column resizing|allows column selection|allows customization|allows editing text attributes|allows empty selection|allows mixed state|allows multiple selection|allows reordering|allows undo|alpha( value)?|alternate image|alternate increment value|alternate title|animation delay|associated file name|associated object|auto completes|auto display|auto enables items|auto repeat|auto resizes( outline column)?|auto save expanded items|auto save name|auto save table columns|auto saves configuration|auto scroll|auto sizes all columns to fit|auto sizes cells|background color|bezel state|bezel style|bezeled|border rect|border type|bordered|bounds( rotation)?|box type|button returned|button type|can choose directories|can choose files|can draw|can hide|cell( (background color|size|type))?|characters|class|click count|clicked( data)? column|clicked data item|clicked( data)? row|closeable|collating|color( (mode|panel))|command key down|configuration|content(s| (size|view( margins)?))?|context|continuous|control key down|control size|control tint|control view|controller visible|coordinate system|copies( on scroll)?|corner view|current cell|current column|current( field)? editor|current( menu)? item|current row|current tab view item|data source|default identifiers|delta (x|y|z)|destination window|directory|display mode|displayed cell|document( (edited|rect|view))?|double value|dragged column|dragged distance|dragged items|draws( cell)? background|draws grid|dynamically scrolls|echos bullets|edge|editable|edited( data)? column|edited data item|edited( data)? row|enabled|enclosing scroll view|ending page|error handling|event number|event type|excluded from windows menu|executable path|expanded|fax number|field editor|file kind|file name|file type|first responder|first visible column|flipped|floating|font( panel)?|formatter|frameworks path|frontmost|gave up|grid color|has data items|has horizontal ruler|has horizontal scroller|has parent data item|has resize indicator|has shadow|has sub menu|has vertical ruler|has vertical scroller|header cell|header view|hidden|hides when deactivated|highlights by|horizontal line scroll|horizontal page scroll|horizontal ruler view|horizontally resizable|icon image|id|identifier|ignores multiple clicks|image( (alignment|dims when disabled|frame style|scaling))?|imports graphics|increment value|indentation per level|indeterminate|index|integer value|intercell spacing|item height|key( (code|equivalent( modifier)?|window))?|knob thickness|label|last( visible)? column|leading offset|leaf|level|line scroll|loaded|localized sort|location|loop mode|main( (bunde|menu|window))?|marker follows cell|matrix mode|maximum( content)? size|maximum visible columns|menu( form representation)?|miniaturizable|miniaturized|minimized image|minimized title|minimum column width|minimum( content)? size|modal|modified|mouse down state|movie( (controller|file|rect))?|muted|name|needs display|next state|next text|number of tick marks|only tick mark values|opaque|open panel|option key down|outline table column|page scroll|pages across|pages down|palette label|pane splitter|parent data item|parent window|pasteboard|path( (names|separator))?|playing|plays every frame|plays selection only|position|preferred edge|preferred type|pressure|previous text|prompt|properties|prototype cell|pulls down|rate|released when closed|repeated|requested print time|required file type|resizable|resized column|resource path|returns records|reuses columns|rich text|roll over|row height|rulers visible|save panel|scripts path|scrollable|selectable( identifiers)?|selected cell|selected( data)? columns?|selected data items?|selected( data)? rows?|selected item identifier|selection by rect|send action on arrow key|sends action when done editing|separates columns|separator item|sequence number|services menu|shared frameworks path|shared support path|sheet|shift key down|shows alpha|shows state by|size( mode)?|smart insert delete enabled|sort case sensitivity|sort column|sort order|sort type|sorted( data rows)?|sound|source( mask)?|spell checking enabled|starting page|state|string value|sub menu|super menu|super view|tab key traverses cells|tab state|tab type|tab view|table view|tag|target( printer)?|text color|text container insert|text container origin|text returned|tick mark position|time stamp|title(d| (cell|font|height|position|rect))?|tool tip|toolbar|trailing offset|transparent|treat packages as directories|truncated labels|types|unmodified characters|update views|use sort indicator|user defaults|uses data source|uses ruler|uses threaded animation|uses title from previous column|value wraps|version|vertical( (line scroll|page scroll|ruler view))?|vertically resizable|view|visible( document rect)?|volume|width|window|windows menu|wraps|zoomable|zoomed)\b"
|
||||||
|
>
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(action cell|alert reply|application|box|browser( cell)?|bundle|button( cell)?|cell|clip view|color well|color-panel|combo box( item)?|control|data( (cell|column|item|row|source))?|default entry|dialog reply|document|drag info|drawer|event|font(-panel)?|formatter|image( (cell|view))?|matrix|menu( item)?|item|movie( view)?|open-panel|outline view|panel|pasteboard|plugin|popup button|progress indicator|responder|save-panel|scroll view|secure text field( cell)?|slider|sound|split view|stepper|tab view( item)?|table( (column|header cell|header view|view))|text( (field( cell)?|view))?|toolbar( item)?|user-defaults|view|window)s?\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(animate|append|call method|center|close drawer|close panel|display|display alert|display dialog|display panel|go|hide|highlight|increment|item for|load image|load movie|load nib|load panel|load sound|localized string|lock focus|log|open drawer|path for|pause|perform action|play|register|resume|scroll|select( all)?|show|size to fit|start|step back|step forward|stop|synchronize|unlock focus|update)\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b((in )?back of|(in )?front of|[0-9]+(st|nd|rd|th)|first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|after|back|before|behind|every|front|index|last|middle|some|that|through|thru|where|whose)\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b([a-zA-Z]\w*)\b">
|
||||||
|
<token type="NameVariable" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[-+]?(\d+\.\d*|\d*\.\d+)(E[-+][0-9]+)?">
|
||||||
|
<token type="LiteralNumberFloat" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[-+]?\d+">
|
||||||
|
<token type="LiteralNumberInteger" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="comment">
|
||||||
|
<rule pattern="\(\*">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
<push />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*\)">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^*(]+">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[*(]">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
174
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arangodb_aql.xml
generated
vendored
Normal file
174
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arangodb_aql.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ArangoDB AQL</name>
|
||||||
|
<alias>aql</alias>
|
||||||
|
<filename>*.aql</filename>
|
||||||
|
<mime_type>text/x-aql</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
<ensure_nl>true</ensure_nl>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="comments-and-whitespace">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="//.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/\*">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
<push state="multiline-comment"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="multiline-comment">
|
||||||
|
<rule pattern="[^*]+">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*/">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="double-quote">
|
||||||
|
<rule pattern="\\.">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^"\\]+">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="single-quote">
|
||||||
|
<rule pattern="\\.">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^'\\]+">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="backtick">
|
||||||
|
<rule pattern="\\.">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^`\\]+">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="`">
|
||||||
|
<token type="Name"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="forwardtick">
|
||||||
|
<rule pattern="\\.">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^´\\]+">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="´">
|
||||||
|
<token type="Name"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="identifier">
|
||||||
|
<rule pattern="(?:\$?|_+)[a-z]+[_a-z0-9]*">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="`">
|
||||||
|
<token type="Name"/>
|
||||||
|
<push state="backtick"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="´">
|
||||||
|
<token type="Name"/>
|
||||||
|
<push state="forwardtick"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule>
|
||||||
|
<include state="comments-and-whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0b[01]+">
|
||||||
|
<token type="LiteralNumberBin"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0x[0-9a-f]+">
|
||||||
|
<token type="LiteralNumberHex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:0|[1-9][0-9]*)(?![\.e])">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:(?:0|[1-9][0-9]*)(?:\.[0-9]+)?|\.[0-9]+)(?:e[\-\+]?[0-9]+)?">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="@@(?:_+[a-z0-9]+[a-z0-9_]*|[a-z0-9][a-z0-9_]*)">
|
||||||
|
<token type="NameVariableGlobal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="@(?:_+[a-z0-9]+[a-z0-9_]*|[a-z0-9][a-z0-9_]*)">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="=~|!~|[=!<>]=?|[%?:/*+-]|\.\.|&&|\|\|">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[.,(){}\[\]]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[a-zA-Z0-9][a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]+)+(?=\s*\()">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(WITH)(\s+)(COUNT)(\s+)(INTO)\b">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordPseudo"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:KEEP|PRUNE|SEARCH|TO)\b">
|
||||||
|
<token type="KeywordPseudo"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="OPTIONS(?=\s*\{)">
|
||||||
|
<token type="KeywordPseudo"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:AGGREGATE|ALL|ALL_SHORTEST_PATHS|AND|ANY|ASC|AT LEAST|COLLECT|DESC|DISTINCT|FILTER|FOR|GRAPH|IN|INBOUND|INSERT|INTO|K_PATHS|K_SHORTEST_PATHS|LIKE|LIMIT|NONE|NOT|OR|OUTBOUND|REMOVE|REPLACE|RETURN|SHORTEST_PATH|SORT|UPDATE|UPSERT|WITH|WINDOW)\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="LET\b">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:true|false|null)\b">
|
||||||
|
<token type="KeywordConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?-i)(?:CURRENT|NEW|OLD)\b">
|
||||||
|
<token type="NameBuiltinPseudo"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:to_bool|to_number|to_char|to_string|to_array|to_list|is_null|is_bool|is_number|is_string|is_array|is_list|is_object|is_document|is_datestring|typename|json_stringify|json_parse|concat|concat_separator|char_length|lower|upper|substring|substring_bytes|left|right|trim|reverse|repeat|contains|log|log2|log10|exp|exp2|sin|cos|tan|asin|acos|atan|atan2|radians|degrees|pi|regex_test|regex_replace|like|floor|ceil|round|abs|rand|random|sqrt|pow|length|count|min|max|average|avg|sum|product|median|variance_population|variance_sample|variance|percentile|bit_and|bit_or|bit_xor|bit_negate|bit_test|bit_popcount|bit_shift_left|bit_shift_right|bit_construct|bit_deconstruct|bit_to_string|bit_from_string|first|last|unique|outersection|interleave|in_range|jaccard|matches|merge|merge_recursive|has|attributes|keys|values|entries|unset|unset_recursive|keep|keep_recursive|near|within|within_rectangle|is_in_polygon|distance|fulltext|stddev_sample|stddev_population|stddev|slice|nth|position|contains_array|translate|zip|call|apply|push|append|pop|shift|unshift|remove_value|remove_values|remove_nth|replace_nth|date_now|date_timestamp|date_iso8601|date_dayofweek|date_year|date_month|date_day|date_hour|date_minute|date_second|date_millisecond|date_dayofyear|date_isoweek|date_isoweekyear|date_leapyear|date_quarter|date_days_in_month|date_trunc|date_round|date_add|date_subtract|date_diff|date_compare|date_format|date_utctolocal|date_localtoutc|date_timezone|date_timezones|fail|passthru|v8|sleep|schema_get|schema_validate|shard_id|version|noopt|noeval|not_null|first_list|first_document|parse_identifier|parse_collection|parse_key|current_user|current_database|collection_count|pregel_result|collections|document|decode_rev|range|union|union_distinct|minus|intersection|flatten|is_same_collection|check_document|ltrim|rtrim|find_first|find_last|split|substitute|ipv4_to_number|ipv4_from_number|is_ipv4|md5|sha1|sha256|sha512|crc32|fnv64|hash|random_token|to_base64|to_hex|encode_uri_component|soundex|assert|warn|is_key|sorted|sorted_unique|count_distinct|count_unique|levenshtein_distance|levenshtein_match|regex_matches|regex_split|ngram_match|ngram_similarity|ngram_positional_similarity|uuid|tokens|exists|starts_with|phrase|min_match|bm25|tfidf|boost|analyzer|offset_info|value|cosine_similarity|decay_exp|decay_gauss|decay_linear|l1_distance|l2_distance|minhash|minhash_count|minhash_error|minhash_match|geo_point|geo_multipoint|geo_polygon|geo_multipolygon|geo_linestring|geo_multilinestring|geo_contains|geo_intersects|geo_equals|geo_distance|geo_area|geo_in_range)(?=\s*\()">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
<push state="double-quote"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
<push state="single-quote"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="#\d+\b">
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="identifier"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
322
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arduino.xml
generated
vendored
Normal file
322
src/vcom-0.2.5/vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arduino.xml
generated
vendored
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>Arduino</name>
|
||||||
|
<alias>arduino</alias>
|
||||||
|
<filename>*.ino</filename>
|
||||||
|
<mime_type>text/x-arduino</mime_type>
|
||||||
|
<ensure_nl>true</ensure_nl>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="whitespace">
|
||||||
|
<rule pattern="^#if\s+0">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<push state="if0" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="^#">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<push state="macro" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="^(\s*(?:/[*].*?[*]/\s*)?)(#if\s+0)">
|
||||||
|
<bygroups>
|
||||||
|
<usingself state="root" />
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="if0" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="^(\s*(?:/[*].*?[*]/\s*)?)(#)">
|
||||||
|
<bygroups>
|
||||||
|
<usingself state="root" />
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="macro" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\n">
|
||||||
|
<token type="Text" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\\\n">
|
||||||
|
<token type="Text" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="//(\n|[\w\W]*?[^\\]\n)">
|
||||||
|
<token type="CommentSingle" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/(\\\n)?[*][\w\W]*?[*](\\\n)?/">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/(\\\n)?[*][\w\W]*">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="string">
|
||||||
|
<rule pattern=""">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\\([\\abfnrtv"\']|x[a-fA-F0-9]{2,4}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8}|[0-7]{1,3})">
|
||||||
|
<token type="LiteralStringEscape" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^\\"\n]+">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\\\n">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\\">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="macro">
|
||||||
|
<rule pattern="(include)(\s*(?:/[*].*?[*]/\s*)?)([^\n]+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="CommentPreprocFile" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^/\n]+">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/[*](.|\n)*?[*]/">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="//.*?\n">
|
||||||
|
<token type="CommentSingle" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?<=\\)\n">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\n">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="statements">
|
||||||
|
<rule
|
||||||
|
pattern="(reinterpret_cast|static_assert|dynamic_cast|thread_local|static_cast|const_cast|protected|constexpr|namespace|restrict|noexcept|override|operator|typename|template|explicit|decltype|nullptr|private|alignof|virtual|mutable|alignas|typeid|friend|throws|export|public|delete|final|using|throw|catch|this|try|new)\b"
|
||||||
|
>
|
||||||
|
<token type="Keyword" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="char(16_t|32_t)\b">
|
||||||
|
<token type="KeywordType" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(class)\b">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="classname" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(R)(")([^\\()\s]{,16})(\()((?:.|\n)*?)(\)\3)(")">
|
||||||
|
<bygroups>
|
||||||
|
<token type="LiteralStringAffix" />
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<token type="LiteralStringDelimiter" />
|
||||||
|
<token type="LiteralStringDelimiter" />
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<token type="LiteralStringDelimiter" />
|
||||||
|
<token type="LiteralString" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(u8|u|U)(")">
|
||||||
|
<bygroups>
|
||||||
|
<token type="LiteralStringAffix" />
|
||||||
|
<token type="LiteralString" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="string" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(L?)(")">
|
||||||
|
<bygroups>
|
||||||
|
<token type="LiteralStringAffix" />
|
||||||
|
<token type="LiteralString" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="string" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(L?)(')(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\\'\n])(')">
|
||||||
|
<bygroups>
|
||||||
|
<token type="LiteralStringAffix" />
|
||||||
|
<token type="LiteralStringChar" />
|
||||||
|
<token type="LiteralStringChar" />
|
||||||
|
<token type="LiteralStringChar" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+[LlUu]*">
|
||||||
|
<token type="LiteralNumberFloat" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\d+\.\d*|\.\d+|\d+[fF])[fF]?">
|
||||||
|
<token type="LiteralNumberFloat" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0x[0-9a-fA-F]+[LlUu]*">
|
||||||
|
<token type="LiteralNumberHex" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0[0-7]+[LlUu]*">
|
||||||
|
<token type="LiteralNumberOct" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\d+[LlUu]*">
|
||||||
|
<token type="LiteralNumberInteger" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*/">
|
||||||
|
<token type="Error" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[~!%^&*+=|?:<>/-]">
|
||||||
|
<token type="Operator" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[()\[\],.]">
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="(restricted|volatile|continue|register|default|typedef|struct|extern|switch|sizeof|static|return|union|while|const|break|goto|enum|else|case|auto|for|asm|if|do)\b"
|
||||||
|
>
|
||||||
|
<token type="Keyword" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="(_Bool|_Complex|_Imaginary|array|atomic_bool|atomic_char|atomic_int|atomic_llong|atomic_long|atomic_schar|atomic_short|atomic_uchar|atomic_uint|atomic_ullong|atomic_ulong|atomic_ushort|auto|bool|boolean|BooleanVariables|Byte|byte|Char|char|char16_t|char32_t|class|complex|Const|const|const_cast|delete|double|dynamic_cast|enum|explicit|extern|Float|float|friend|inline|Int|int|int16_t|int32_t|int64_t|int8_t|Long|long|new|NULL|null|operator|private|PROGMEM|protected|public|register|reinterpret_cast|short|signed|sizeof|Static|static|static_cast|String|struct|typedef|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|virtual|Void|void|Volatile|volatile|word)\b"
|
||||||
|
>
|
||||||
|
<token type="KeywordType" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(and|final|If|Loop|loop|not|or|override|setup|Setup|throw|try|xor)\b">
|
||||||
|
<token type="Keyword" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="(ANALOG_MESSAGE|BIN|CHANGE|DEC|DEFAULT|DIGITAL_MESSAGE|EXTERNAL|FALLING|FIRMATA_STRING|HALF_PI|HEX|HIGH|INPUT|INPUT_PULLUP|INTERNAL|INTERNAL1V1|INTERNAL1V1|INTERNAL2V56|INTERNAL2V56|LED_BUILTIN|LED_BUILTIN_RX|LED_BUILTIN_TX|LOW|LSBFIRST|MSBFIRST|OCT|OUTPUT|PI|REPORT_ANALOG|REPORT_DIGITAL|RISING|SET_PIN_MODE|SYSEX_START|SYSTEM_RESET|TWO_PI)\b"
|
||||||
|
>
|
||||||
|
<token type="KeywordConstant" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(boolean|const|byte|word|string|String|array)\b">
|
||||||
|
<token type="NameVariable" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="(Keyboard|KeyboardController|MouseController|SoftwareSerial|EthernetServer|EthernetClient|LiquidCrystal|RobotControl|GSMVoiceCall|EthernetUDP|EsploraTFT|HttpClient|RobotMotor|WiFiClient|GSMScanner|FileSystem|Scheduler|GSMServer|YunClient|YunServer|IPAddress|GSMClient|GSMModem|Keyboard|Ethernet|Console|GSMBand|Esplora|Stepper|Process|WiFiUDP|GSM_SMS|Mailbox|USBHost|Firmata|PImage|Client|Server|GSMPIN|FileIO|Bridge|Serial|EEPROM|Stream|Mouse|Audio|Servo|File|Task|GPRS|WiFi|Wire|TFT|GSM|SPI|SD)\b"
|
||||||
|
>
|
||||||
|
<token type="NameClass" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="(abs|Abs|accept|ACos|acos|acosf|addParameter|analogRead|AnalogRead|analogReadResolution|AnalogReadResolution|analogReference|AnalogReference|analogWrite|AnalogWrite|analogWriteResolution|AnalogWriteResolution|answerCall|asin|ASin|asinf|atan|ATan|atan2|ATan2|atan2f|atanf|attach|attached|attachGPRS|attachInterrupt|AttachInterrupt|autoscroll|available|availableForWrite|background|beep|begin|beginPacket|beginSD|beginSMS|beginSpeaker|beginTFT|beginTransmission|beginWrite|bit|Bit|BitClear|bitClear|bitRead|BitRead|bitSet|BitSet|BitWrite|bitWrite|blink|blinkVersion|BSSID|buffer|byte|cbrt|cbrtf|Ceil|ceil|ceilf|changePIN|char|charAt|checkPIN|checkPUK|checkReg|circle|cityNameRead|cityNameWrite|clear|clearScreen|click|close|compareTo|compassRead|concat|config|connect|connected|constrain|Constrain|copysign|copysignf|cos|Cos|cosf|cosh|coshf|countryNameRead|countryNameWrite|createChar|cursor|debugPrint|degrees|Delay|delay|DelayMicroseconds|delayMicroseconds|detach|DetachInterrupt|detachInterrupt|DigitalPinToInterrupt|digitalPinToInterrupt|DigitalRead|digitalRead|DigitalWrite|digitalWrite|disconnect|display|displayLogos|drawBMP|drawCompass|encryptionType|end|endPacket|endSMS|endsWith|endTransmission|endWrite|equals|equalsIgnoreCase|exists|exitValue|Exp|exp|expf|fabs|fabsf|fdim|fdimf|fill|find|findUntil|float|floor|Floor|floorf|flush|fma|fmaf|fmax|fmaxf|fmin|fminf|fmod|fmodf|gatewayIP|get|getAsynchronously|getBand|getButton|getBytes|getCurrentCarrier|getIMEI|getKey|getModifiers|getOemKey|getPINUsed|getResult|getSignalStrength|getSocket|getVoiceCallStatus|getXChange|getYChange|hangCall|height|highByte|HighByte|home|hypot|hypotf|image|indexOf|int|interrupts|IPAddress|IRread|isActionDone|isAlpha|isAlphaNumeric|isAscii|isControl|isDigit|isDirectory|isfinite|isGraph|isHexadecimalDigit|isinf|isListening|isLowerCase|isnan|isPIN|isPressed|isPrintable|isPunct|isSpace|isUpperCase|isValid|isWhitespace|keyboardRead|keyPressed|keyReleased|knobRead|lastIndexOf|ldexp|ldexpf|leftToRight|length|line|lineFollowConfig|listen|listenOnLocalhost|loadImage|localIP|log|Log|log10|log10f|logf|long|lowByte|LowByte|lrint|lrintf|lround|lroundf|macAddress|maintain|map|Map|Max|max|messageAvailable|Micros|micros|millis|Millis|Min|min|mkdir|motorsStop|motorsWrite|mouseDragged|mouseMoved|mousePressed|mouseReleased|move|noAutoscroll|noBlink|noBuffer|noCursor|noDisplay|noFill|noInterrupts|NoInterrupts|noListenOnLocalhost|noStroke|noTone|NoTone|onReceive|onRequest|open|openNextFile|overflow|parseCommand|parseFloat|parseInt|parsePacket|pauseMode|peek|PinMode|pinMode|playFile|playMelody|point|pointTo|position|Pow|pow|powf|prepare|press|print|printFirmwareVersion|println|printVersion|process|processInput|PulseIn|pulseIn|pulseInLong|PulseInLong|put|radians|random|Random|randomSeed|RandomSeed|read|readAccelerometer|readBlue|readButton|readBytes|readBytesUntil|readGreen|readJoystickButton|readJoystickSwitch|readJoystickX|readJoystickY|readLightSensor|readMessage|readMicrophone|readNetworks|readRed|readSlider|readString|readStringUntil|readTemperature|ready|rect|release|releaseAll|remoteIP|remoteNumber|remotePort|remove|replace|requestFrom|retrieveCallingNumber|rewindDirectory|rightToLeft|rmdir|robotNameRead|robotNameWrite|round|roundf|RSSI|run|runAsynchronously|running|runShellCommand|runShellCommandAsynchronously|scanNetworks|scrollDisplayLeft|scrollDisplayRight|seek|sendAnalog|sendDigitalPortPair|sendDigitalPorts|sendString|sendSysex|Serial_Available|Serial_Begin|Serial_End|Serial_Flush|Serial_Peek|Serial_Print|Serial_Println|Serial_Read|serialEvent|setBand|setBitOrder|setCharAt|setClockDivider|setCursor|setDataMode|setDNS|setFirmwareVersion|setMode|setPINUsed|setSpeed|setTextSize|setTimeout|ShiftIn|shiftIn|ShiftOut|shiftOut|shutdown|signbit|sin|Sin|sinf|sinh|sinhf|size|sizeof|Sq|sq|Sqrt|sqrt|sqrtf|SSID|startLoop|startsWith|step|stop|stroke|subnetMask|substring|switchPIN|tan|Tan|tanf|tanh|tanhf|tempoWrite|text|toCharArray|toInt|toLowerCase|tone|Tone|toUpperCase|transfer|trim|trunc|truncf|tuneWrite|turn|updateIR|userNameRead|userNameWrite|voiceCall|waitContinue|width|WiFiServer|word|write|writeBlue|writeGreen|writeJSON|writeMessage|writeMicroseconds|writeRed|writeRGB|yield|Yield)\b"
|
||||||
|
>
|
||||||
|
<token type="NameFunction" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(typename|__inline|restrict|_inline|thread|inline|naked)\b">
|
||||||
|
<token type="KeywordReserved" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(__m(128i|128d|128|64))\b">
|
||||||
|
<token type="KeywordReserved" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="__(forceinline|identifier|unaligned|declspec|fastcall|finally|stdcall|wchar_t|assume|except|int32|cdecl|int16|leave|based|raise|int64|noop|int8|w64|try|asm)\b"
|
||||||
|
>
|
||||||
|
<token type="KeywordReserved" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(true|false|NULL)\b">
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([a-zA-Z_]\w*)(\s*)(:)(?!:)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameLabel" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[a-zA-Z_]\w*">
|
||||||
|
<token type="Name" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="function">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace" />
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="statements" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<push />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\}">
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="if0">
|
||||||
|
<rule pattern="^\s*#if.*?(?<!\\)\n">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<push />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="^\s*#el(?:se|if).*\n">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="^\s*#endif.*?(?<!\\)\n">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=".*?\n">
|
||||||
|
<token type="Comment" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="classname">
|
||||||
|
<rule pattern="[a-zA-Z_]\w*">
|
||||||
|
<token type="NameClass" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s*(?=>)">
|
||||||
|
<token type="Text" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="statement">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace" />
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="statements" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[{}]">
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="((?:[\w*\s])+?(?:\s|[*]))([a-zA-Z_]\w*)(\s*\([^;]*?\))([^;{]*)(\{)">
|
||||||
|
<bygroups>
|
||||||
|
<usingself state="root" />
|
||||||
|
<token type="NameFunction" />
|
||||||
|
<usingself state="root" />
|
||||||
|
<usingself state="root" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="function" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="((?:[\w*\s])+?(?:\s|[*]))([a-zA-Z_]\w*)(\s*\([^;]*?\))([^;]*)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<usingself state="root" />
|
||||||
|
<token type="NameFunction" />
|
||||||
|
<usingself state="root" />
|
||||||
|
<usingself state="root" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<push state="statement" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="__(multiple_inheritance|virtual_inheritance|single_inheritance|interface|uuidof|super|event)\b">
|
||||||
|
<token type="KeywordReserved" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="__(offload|blockingoffload|outer)\b">
|
||||||
|
<token type="KeywordPseudo" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue