Compare commits

...
7 Commits
Author SHA1 Message Date
EnumDev ef401b5aaa Update README.md 2026-05-03 23:27:49 +03:00
EnumDev 612c0ba80c Update nightly workflow 2026-05-03 17:35:27 +03:00
EnumDev d82d53d659 Update nightly workflow 2026-05-03 17:33:55 +03:00
EnumDev abafc12b47 Add github workflow for nightly releases 2026-05-03 17:32:01 +03:00
EnumDev 811a8f7ee9 Standardize config fetching 2026-05-03 14:55:06 +03:00
EnumDev 18462d1df9 Update README.md 2026-04-28 21:36:51 +03:00
EnumDev b40d89dc43 Update .gitignore 2026-04-28 21:35:30 +03:00
10 changed files with 195 additions and 136 deletions
+69
View File
@@ -0,0 +1,69 @@
name: Publish nightly
on:
push:
branches:
- "master"
paths:
- "**"
- "!*.md"
- "!.github/**"
- ".github/workflows/publish_nightly.yml"
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
os: [linux, darwin, freebsd, windows]
arch: [amd64, arm64]
steps:
- name: Setup environment
run: echo "FILENAME=typer-nightly-$(date --iso-8601)-${{ matrix.os }}-${{ matrix.arch }}" >> $GITHUB_ENV
- name: Fetch repository
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24
- name: Build using make
run: make GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }}
- name: Setup archive contents
run: |
mkdir -p staging/$FILENAME
cp build/* -t staging/$FILENAME/
cp -r config staging/$FILENAME/config
- name: Create tarball
if: matrix.os != 'windows'
run: |
cd staging
tar -czvf $FILENAME.tar.gz $FILENAME
- name: Create zip
if: matrix.os == 'windows'
run: |
cd staging/$FILENAME
zip -r ../$FILENAME.zip *
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.os }}-${{ matrix.arch }}
path: staging/${{ env.FILENAME }}.*
publish:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Update nightly release
uses: andelf/nightly-release@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: nightly
name: Nightly
body: |
**This is the nightly release of Typer. Expect bugs and unfinished features**
files: artifacts/*
+1 -1
View File
@@ -2,4 +2,4 @@
/build/
# IDE files
.idea
/.idea
+7
View File
@@ -10,6 +10,13 @@
| Distribution | Package name |
|:----------------------:|:---------------------|
| Arch Linux/Artix Linux | `typer` from the AUR |
| Tide Linux | `typer` from the main repository |
#### From the releases section:
- Go to the [releases section](https://github.com/EnumeratedDev/typer/releases)
- Choose either the latest stable release (**Recommended**) or nightly pre-release
- Download the archive that corresponds to your operating system and architecture
- Optional: Add the extracted directory to your PATH so that typer can be launched from anywhere
- Optional: On Unix-based systems you can move the `typer` executable into `/usr/local/bin/typer` and `config` directory into `/usr/local/etc/typer` for a system-wide installation
#### From source:
- Download `go` from your package manager or from the go website
- Downlaod `which` from your package manager
+15 -42
View File
@@ -3,8 +3,6 @@ package main
import (
"log"
"os"
"path/filepath"
"runtime"
"gopkg.in/yaml.v3"
)
@@ -21,7 +19,7 @@ type TyperConfig struct {
var Config TyperConfig
func readConfig() {
func readMainConfig() {
Config = TyperConfig{
SelectedStyle: "default",
FallbackStyle: "default-fallback",
@@ -32,49 +30,24 @@ func readConfig() {
TabIndentation: 4,
}
homeDir, err := os.UserHomeDir()
// Get main config path
mainConfigPath := GetConfigPath("config.yml")
// Ensure config exists at path
if mainConfigPath == "" {
log.Fatalf("config.yml not found in any config directory")
}
// Read config file
data, err := os.ReadFile(mainConfigPath)
if err != nil {
log.Fatalf("Could not get home directory: %s", err)
log.Fatalf("Could not read config.yml: %s", err)
}
execPath, err := os.Executable()
// Unmarshal contents into struct
err = yaml.Unmarshal(data, &Config)
if err != nil {
log.Fatalf("Could not get path to executable: %s", err)
}
configPaths := make([]string, 0)
switch runtime.GOOS {
case "windows":
configPaths = append(configPaths, filepath.Join(homeDir, "AppData/Roaming/Typer/config.yml"))
configPaths = append(configPaths, filepath.Join(filepath.Dir(execPath), "etc/typer/config.yml"))
case "darwin":
configPaths = append(configPaths, filepath.Join(homeDir, "Library/Typer/config.yml"))
configPaths = append(configPaths, "/Library/Typer/config.yml")
configPaths = append(configPaths, filepath.Join(sysconfdir, "typer/config.yml"))
default:
configPaths = append(configPaths, filepath.Join(homeDir, ".config/typer/config.yml"))
configPaths = append(configPaths, filepath.Join(sysconfdir, "typer/config.yml"))
}
for _, configPath := range configPaths {
// Ensure config exists at path
if _, err := os.Stat(configPath); err != nil {
continue
}
// Read config file
data, err := os.ReadFile(configPath)
if err != nil {
log.Fatalf("Could not read config.yml: %s", err)
}
// Unmarshal contents into struct
err = yaml.Unmarshal(data, &Config)
if err != nil {
log.Fatalf("Could not unmarshal config.yml: %s", err)
}
break
log.Fatalf("Could not unmarshal config.yml: %s", err)
}
// Validate config options
+1
View File
@@ -9,6 +9,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.10 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
+2
View File
@@ -11,6 +11,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+15 -42
View File
@@ -3,8 +3,6 @@ package main
import (
"log"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/gdamore/tcell/v2"
@@ -23,54 +21,29 @@ type Keybinding struct {
var Keybindings TyperKeybindings
func readKeybindings() {
func readKeybindingsConfig() {
Keybindings = TyperKeybindings{
Keybindings: make([]Keybinding, 0),
}
homeDir, err := os.UserHomeDir()
// Get keybindings config path
keybindingsConfigPath := GetConfigPath("keybindings.yml")
// Ensure config exists at path
if keybindingsConfigPath == "" {
log.Fatalf("keybindings.yml not found in any config directory")
}
// Read config file
data, err := os.ReadFile(keybindingsConfigPath)
if err != nil {
log.Fatalf("Could not get home directory: %s", err)
log.Fatalf("Could not read keybindings.yml: %s", err)
}
execPath, err := os.Executable()
// Unmarshal contents into struct
err = yaml.Unmarshal(data, &Keybindings)
if err != nil {
log.Fatalf("Could not get path to executable: %s", err)
}
configPaths := make([]string, 0)
switch runtime.GOOS {
case "windows":
configPaths = append(configPaths, filepath.Join(homeDir, "AppData/Roaming/Typer/keybindings.yml"))
configPaths = append(configPaths, filepath.Join(filepath.Dir(execPath), "etc/typer/keybindings.yml"))
case "darwin":
configPaths = append(configPaths, filepath.Join(homeDir, "Library/Typer/keybindings.yml"))
configPaths = append(configPaths, "/Library/Typer/keybindings.yml")
configPaths = append(configPaths, filepath.Join(sysconfdir, "typer/keybindings.yml"))
default:
configPaths = append(configPaths, filepath.Join(homeDir, ".config/typer/keybindings.yml"))
configPaths = append(configPaths, filepath.Join(sysconfdir, "typer/keybindings.yml"))
}
for _, configPath := range configPaths {
// Ensure config exists at path
if _, err := os.Stat(configPath); err != nil {
continue
}
// Read config file
data, err := os.ReadFile(configPath)
if err != nil {
log.Fatalf("Could not read keybindings.yml: %s", err)
}
// Unmarshal contents into struct
err = yaml.Unmarshal(data, &Keybindings)
if err != nil {
log.Fatalf("Could not unmarshal keybindings.yml: %s", err)
}
break
log.Fatalf("Could not unmarshal keybindings.yml: %s", err)
}
}
+18 -8
View File
@@ -2,19 +2,25 @@ package main
import (
"log"
"os"
flag "github.com/spf13/pflag"
)
var sysconfdir = "/etc/"
var configDirFlag = flag.StringP("config", "c", "", "Path to config directory")
func main() {
// Read config
readConfig()
// Read flags
readFlags()
// Read key bindings
readKeybindings()
// Read main config
readMainConfig()
// Read styles
// Read keybindings config
readKeybindingsConfig()
// Read styles directory
readStyles()
// Initialize commands
@@ -25,8 +31,8 @@ func main() {
log.Fatalf("Failed to create window: %v", err)
}
if len(os.Args) > 1 {
for i, file := range os.Args[1:] {
if flag.NArg() > 0 {
for i, file := range flag.Args() {
b, err := CreateFileBuffer(file, true)
if err != nil {
window.PrintMessage("Could not open file: " + file)
@@ -48,3 +54,7 @@ func main() {
window.screen.Fini()
window.screen = nil
}
func readFlags() {
flag.Parse()
}
+18 -42
View File
@@ -6,7 +6,6 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"slices"
"strconv"
"strings"
@@ -73,54 +72,31 @@ var AvailableStyles = make(map[string]TyperStyle)
var CurrentStyle = FallbackStyle
func readStyles() {
homeDir, err := os.UserHomeDir()
// Get styles directory path
stylesDirPath := GetConfigPath("styles")
// Ensure directory exists at path
if stat, err := os.Stat(stylesDirPath); stylesDirPath == "" || err != nil || !stat.IsDir() {
return
}
// Get directory entries
entries, err := os.ReadDir(stylesDirPath)
if err != nil {
log.Fatalf("Could not get home directory: %s", err)
log.Fatalf("Could not read user style directory: %s", err)
}
execPath, err := os.Executable()
if err != nil {
log.Fatalf("Could not get path to executable: %s", err)
}
// Read entries in directory
for _, entry := range entries {
entryPath := filepath.Join(stylesDirPath, entry.Name())
stylesPaths := make([]string, 0)
switch runtime.GOOS {
case "windows":
stylesPaths = append(stylesPaths, filepath.Join(homeDir, "AppData/Roaming/Typer/styles"))
stylesPaths = append(stylesPaths, filepath.Join(filepath.Dir(execPath), "config/styles"))
case "darwin":
stylesPaths = append(stylesPaths, filepath.Join(homeDir, "Library/Typer/styles"))
stylesPaths = append(stylesPaths, "/Library/Typer/styles")
stylesPaths = append(stylesPaths, filepath.Join(sysconfdir, "typer/styles"))
default:
stylesPaths = append(stylesPaths, filepath.Join(homeDir, ".config/typer/styles"))
stylesPaths = append(stylesPaths, filepath.Join(sysconfdir, "typer/styles"))
}
for _, stylesPath := range stylesPaths {
// Ensure directory exists at path
if stat, err := os.Stat(stylesPath); err != nil || !stat.IsDir() {
continue
}
// Get directory entries
entries, err := os.ReadDir(stylesPath)
style, err := readStyleYamlFile(entryPath)
if err != nil {
log.Fatalf("Could not read user style directory: %s", err)
log.Fatalf("Could not read style file (%s): %s", entryPath, err)
}
// Read entries in directory
for _, entry := range entries {
entryPath := filepath.Join(stylesPath, entry.Name())
style, err := readStyleYamlFile(entryPath)
if err != nil {
log.Fatalf("Could not read style file (%s): %s", entryPath, err)
}
if _, ok := AvailableStyles[style.Name]; !ok {
AvailableStyles[style.Name] = style
}
if _, ok := AvailableStyles[style.Name]; !ok {
AvailableStyles[style.Name] = style
}
}
}
+49 -1
View File
@@ -1,6 +1,54 @@
package main
import "github.com/gdamore/tcell/v2"
import (
"log"
"os"
"path"
"path/filepath"
"runtime"
"github.com/gdamore/tcell/v2"
)
func GetConfigPath(relativeConfigPath string) string {
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatalf("Could not get home directory: %s", err)
}
execPath, err := os.Executable()
if err != nil {
log.Fatalf("Could not get path to executable: %s", err)
}
paths := make([]string, 0)
if *configDirFlag != "" {
paths = append(paths, path.Join(*configDirFlag, relativeConfigPath))
}
switch runtime.GOOS {
case "windows":
paths = append(paths, filepath.Join(homeDir, "AppData/Roaming/Typer", relativeConfigPath))
paths = append(paths, "C:/ProgramData/Typer", relativeConfigPath)
case "darwin":
paths = append(paths, filepath.Join(homeDir, "Library/Typer", relativeConfigPath))
paths = append(paths, filepath.Join(homeDir, "Library/typer", relativeConfigPath))
paths = append(paths, filepath.Join(sysconfdir, "Typer", relativeConfigPath))
paths = append(paths, filepath.Join(sysconfdir, "typer", relativeConfigPath))
default:
paths = append(paths, filepath.Join(homeDir, ".config/typer", relativeConfigPath))
paths = append(paths, filepath.Join(sysconfdir, "typer", relativeConfigPath))
}
paths = append(paths, filepath.Join(filepath.Dir(execPath), "config", relativeConfigPath))
for _, p := range paths {
// Return true if path exists
if _, err := os.Stat(p); err == nil {
return p
}
}
return ""
}
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
row := y1