Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e29c7e31c1 | |||
| 43f4680176 | |||
| d9f27c1775 | |||
| bb7ceb37fe | |||
| 6a759ef3d1 | |||
| cb735f0754 | |||
| 80fb49bc5e | |||
| 9190df81dd | |||
| 9235ef5e08 | |||
| b91d6b33b5 | |||
| 30ef1db010 | |||
| 2d97a47ee1 | |||
| 72200ea72e | |||
| 608893a3d6 | |||
| 53ff745d5d | |||
| 17bc8ed395 | |||
| a447b68b22 | |||
| 4303dcf59b | |||
| e828d48798 | |||
| 6e470a9239 |
+15
-13
@@ -43,16 +43,17 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
convertSourceType string
|
||||
convertSourcePath string
|
||||
convertSourceConn string
|
||||
convertFromList []string
|
||||
convertTargetType string
|
||||
convertTargetPath string
|
||||
convertPackageName string
|
||||
convertSchemaFilter string
|
||||
convertFlattenSchema bool
|
||||
convertNullableTypes string
|
||||
convertSourceType string
|
||||
convertSourcePath string
|
||||
convertSourceConn string
|
||||
convertFromList []string
|
||||
convertTargetType string
|
||||
convertTargetPath string
|
||||
convertPackageName string
|
||||
convertSchemaFilter string
|
||||
convertFlattenSchema bool
|
||||
convertNullableTypes string
|
||||
convertContinueOnError bool
|
||||
)
|
||||
|
||||
var convertCmd = &cobra.Command{
|
||||
@@ -177,6 +178,7 @@ func init() {
|
||||
convertCmd.Flags().StringVar(&convertSchemaFilter, "schema", "", "Filter to a specific schema by name (required for formats like dctx that only support single schemas)")
|
||||
convertCmd.Flags().BoolVar(&convertFlattenSchema, "flatten-schema", false, "Flatten schema.table names to schema_table (useful for databases like SQLite that do not support schemas)")
|
||||
convertCmd.Flags().StringVar(&convertNullableTypes, "types", "", "Nullable type package for code-gen writers (bun/gorm): 'resolvespec' (default) or 'stdlib' (database/sql)")
|
||||
convertCmd.Flags().BoolVar(&convertContinueOnError, "continue-on-error", false, "Prepend \\set ON_ERROR_STOP off to generated SQL so psql continues past errors (pgsql output only)")
|
||||
|
||||
err := convertCmd.MarkFlagRequired("from")
|
||||
if err != nil {
|
||||
@@ -243,7 +245,7 @@ func runConvert(cmd *cobra.Command, args []string) error {
|
||||
fmt.Fprintf(os.Stderr, " Schema: %s\n", convertSchemaFilter)
|
||||
}
|
||||
|
||||
if err := writeDatabase(db, convertTargetType, convertTargetPath, convertPackageName, convertSchemaFilter, convertFlattenSchema, convertNullableTypes); err != nil {
|
||||
if err := writeDatabase(db, convertTargetType, convertTargetPath, convertPackageName, convertSchemaFilter, convertFlattenSchema, convertNullableTypes, convertContinueOnError); err != nil {
|
||||
return fmt.Errorf("failed to write target: %w", err)
|
||||
}
|
||||
|
||||
@@ -383,10 +385,10 @@ func readDatabaseForConvert(dbType, filePath, connString string) (*models.Databa
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func writeDatabase(db *models.Database, dbType, outputPath, packageName, schemaFilter string, flattenSchema bool, nullableTypes string) error {
|
||||
func writeDatabase(db *models.Database, dbType, outputPath, packageName, schemaFilter string, flattenSchema bool, nullableTypes string, continueOnError bool) error {
|
||||
var writer writers.Writer
|
||||
|
||||
writerOpts := newWriterOptions(outputPath, packageName, flattenSchema, nullableTypes)
|
||||
writerOpts := newWriterOptions(outputPath, packageName, flattenSchema, nullableTypes, continueOnError)
|
||||
|
||||
switch strings.ToLower(dbType) {
|
||||
case "dbml":
|
||||
|
||||
+13
-13
@@ -323,31 +323,31 @@ func writeDatabaseForEdit(dbType, filePath, connString string, db *models.Databa
|
||||
|
||||
switch strings.ToLower(dbType) {
|
||||
case "dbml":
|
||||
writer = wdbml.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wdbml.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "dctx":
|
||||
writer = wdctx.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wdctx.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "drawdb":
|
||||
writer = wdrawdb.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wdrawdb.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "graphql":
|
||||
writer = wgraphql.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wgraphql.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "json":
|
||||
writer = wjson.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wjson.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "yaml":
|
||||
writer = wyaml.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wyaml.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "gorm":
|
||||
writer = wgorm.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wgorm.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "bun":
|
||||
writer = wbun.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wbun.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "drizzle":
|
||||
writer = wdrizzle.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wdrizzle.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "prisma":
|
||||
writer = wprisma.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wprisma.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "typeorm":
|
||||
writer = wtypeorm.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wtypeorm.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "sqlite", "sqlite3":
|
||||
writer = wsqlite.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wsqlite.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
case "pgsql":
|
||||
writer = wpgsql.NewWriter(newWriterOptions(filePath, "", false, ""))
|
||||
writer = wpgsql.NewWriter(newWriterOptions(filePath, "", false, "", false))
|
||||
default:
|
||||
return fmt.Errorf("%s: unsupported format: %s", label, dbType)
|
||||
}
|
||||
|
||||
+18
-13
@@ -258,6 +258,11 @@ func runMerge(cmd *cobra.Command, args []string) error {
|
||||
fmt.Fprintf(os.Stderr, " ✓ Merge complete\n\n")
|
||||
fmt.Fprintf(os.Stderr, "%s\n", merge.GetMergeSummary(result))
|
||||
|
||||
if strings.EqualFold(mergeOutputType, "pgsql") && len(result.TypeConflicts) > 0 {
|
||||
return fmt.Errorf("merge detected conflicting existing column types and cannot safely continue with pgsql output\n%s",
|
||||
merge.GetColumnTypeConflictSummary(result, 10))
|
||||
}
|
||||
|
||||
// Step 4: Write output
|
||||
fmt.Fprintf(os.Stderr, "\n[4/4] Writing output...\n")
|
||||
fmt.Fprintf(os.Stderr, " Format: %s\n", mergeOutputType)
|
||||
@@ -370,61 +375,61 @@ func writeDatabaseForMerge(dbType, filePath, connString string, db *models.Datab
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for DBML format", label)
|
||||
}
|
||||
writer = wdbml.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wdbml.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "dctx":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for DCTX format", label)
|
||||
}
|
||||
writer = wdctx.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wdctx.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "drawdb":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for DrawDB format", label)
|
||||
}
|
||||
writer = wdrawdb.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wdrawdb.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "graphql":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for GraphQL format", label)
|
||||
}
|
||||
writer = wgraphql.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wgraphql.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "json":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for JSON format", label)
|
||||
}
|
||||
writer = wjson.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wjson.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "yaml":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for YAML format", label)
|
||||
}
|
||||
writer = wyaml.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wyaml.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "gorm":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for GORM format", label)
|
||||
}
|
||||
writer = wgorm.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wgorm.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "bun":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for Bun format", label)
|
||||
}
|
||||
writer = wbun.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wbun.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "drizzle":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for Drizzle format", label)
|
||||
}
|
||||
writer = wdrizzle.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wdrizzle.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "prisma":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for Prisma format", label)
|
||||
}
|
||||
writer = wprisma.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wprisma.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "typeorm":
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("%s: file path is required for TypeORM format", label)
|
||||
}
|
||||
writer = wtypeorm.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wtypeorm.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "sqlite", "sqlite3":
|
||||
writer = wsqlite.NewWriter(newWriterOptions(filePath, "", flattenSchema, ""))
|
||||
writer = wsqlite.NewWriter(newWriterOptions(filePath, "", flattenSchema, "", false))
|
||||
case "pgsql":
|
||||
writerOpts := newWriterOptions(filePath, "", flattenSchema, "")
|
||||
writerOpts := newWriterOptions(filePath, "", flattenSchema, "", false)
|
||||
if connString != "" {
|
||||
writerOpts.Metadata = map[string]interface{}{
|
||||
"connection_string": connString,
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -160,3 +161,38 @@ func TestRunMerge_FromListMissingSourceType(t *testing.T) {
|
||||
t.Error("expected error when neither --source-path nor --from-list is provided")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunMerge_PgsqlOutputRejectsColumnTypeConflict(t *testing.T) {
|
||||
saved := saveMergeState()
|
||||
defer restoreMergeState(saved)
|
||||
|
||||
dir := t.TempDir()
|
||||
targetFile := filepath.Join(dir, "target.json")
|
||||
sourceFile := filepath.Join(dir, "source.json")
|
||||
writeTestJSONWithSingleColumnType(t, targetFile, "users", "integer")
|
||||
writeTestJSONWithSingleColumnType(t, sourceFile, "users", "uuid")
|
||||
|
||||
mergeTargetType = "json"
|
||||
mergeTargetPath = targetFile
|
||||
mergeTargetConn = ""
|
||||
mergeSourceType = "json"
|
||||
mergeSourcePath = sourceFile
|
||||
mergeSourceConn = ""
|
||||
mergeFromList = nil
|
||||
mergeOutputType = "pgsql"
|
||||
mergeOutputPath = ""
|
||||
mergeOutputConn = "postgres://relspec:secret@localhost/testdb"
|
||||
mergeSkipTables = ""
|
||||
mergeReportPath = ""
|
||||
|
||||
err := runMerge(nil, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected pgsql output merge to fail on column type conflict")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "column type conflicts detected") {
|
||||
t.Fatalf("expected conflict summary in error, got: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "public.users.id") {
|
||||
t.Fatalf("expected conflicting column path in error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,13 @@ func newReaderOptions(filePath, connString string) *readers.ReaderOptions {
|
||||
}
|
||||
}
|
||||
|
||||
func newWriterOptions(outputPath, packageName string, flattenSchema bool, nullableTypes string) *writers.WriterOptions {
|
||||
func newWriterOptions(outputPath, packageName string, flattenSchema bool, nullableTypes string, continueOnError bool) *writers.WriterOptions {
|
||||
return &writers.WriterOptions{
|
||||
OutputPath: outputPath,
|
||||
PackageName: packageName,
|
||||
FlattenSchema: flattenSchema,
|
||||
NullableTypes: nullableTypes,
|
||||
Prisma7: prisma7,
|
||||
OutputPath: outputPath,
|
||||
PackageName: packageName,
|
||||
FlattenSchema: flattenSchema,
|
||||
NullableTypes: nullableTypes,
|
||||
Prisma7: prisma7,
|
||||
ContinueOnError: continueOnError,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,7 @@ func runSplit(cmd *cobra.Command, args []string) error {
|
||||
"", // no schema filter for split
|
||||
false, // no flatten-schema for split
|
||||
splitNullableTypes,
|
||||
false, // no continue-on-error for split
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write output: %w", err)
|
||||
|
||||
@@ -71,6 +71,40 @@ func writeTestJSON(t *testing.T, path string, tableNames []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func writeTestJSONWithSingleColumnType(t *testing.T, path, tableName, columnType string) {
|
||||
t.Helper()
|
||||
|
||||
db := minimalDatabase{
|
||||
Name: "test_db",
|
||||
Schemas: []minimalSchema{{
|
||||
Name: "public",
|
||||
Tables: []minimalTable{{
|
||||
Name: tableName,
|
||||
Schema: "public",
|
||||
Columns: map[string]minimalColumn{
|
||||
"id": {
|
||||
Name: "id",
|
||||
Table: tableName,
|
||||
Schema: "public",
|
||||
Type: columnType,
|
||||
NotNull: true,
|
||||
IsPrimaryKey: true,
|
||||
AutoIncrement: true,
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(db)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal test JSON: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
t.Fatalf("failed to write test file %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
// convertState captures and restores all convert global vars.
|
||||
type convertState struct {
|
||||
sourceType string
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
module git.warky.dev/wdevs/relspecgo
|
||||
|
||||
go 1.24.0
|
||||
go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell/v2 v2.8.1
|
||||
github.com/gdamore/tcell/v2 v2.13.9
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
github.com/microsoft/go-mssqldb v1.9.6
|
||||
github.com/jackc/pgx/v5 v5.9.2
|
||||
github.com/microsoft/go-mssqldb v1.10.0
|
||||
github.com/rivo/tview v0.42.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/uptrace/bun v1.2.16
|
||||
golang.org/x/text v0.31.0
|
||||
github.com/uptrace/bun v1.2.18
|
||||
golang.org/x/text v0.37.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.44.3
|
||||
modernc.org/sqlite v1.50.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -27,9 +27,8 @@ require (
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
@@ -41,11 +40,11 @@ require (
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
modernc.org/libc v1.72.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -19,15 +19,14 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/gdamore/tcell/v2 v2.13.9 h1:uI5l3DYPcFvHINKlGft+en23evOKL+dwtD21QR8ejVA=
|
||||
github.com/gdamore/tcell/v2 v2.13.9/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -40,8 +39,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
@@ -52,14 +51,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
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-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/microsoft/go-mssqldb v1.9.6 h1:1MNQg5UiSsokiPz3++K2KPx4moKrwIqly1wv+RyCKTw=
|
||||
github.com/microsoft/go-mssqldb v1.9.6/go.mod h1:yYMPDufyoF2vVuVCUGtZARr06DKFIhMrluTcgWlXpr4=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY=
|
||||
github.com/microsoft/go-mssqldb v1.10.0/go.mod h1:mnG7lGa9iYJbzJqGCXyuQCegStKMr3kogDLD6+bmggg=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
@@ -73,8 +70,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/tview v0.42.0 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c=
|
||||
github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
@@ -95,8 +90,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/uptrace/bun v1.2.16 h1:QlObi6ZIK5Ao7kAALnh91HWYNZUBbVwye52fmlQM9kc=
|
||||
github.com/uptrace/bun v1.2.16/go.mod h1:jMoNg2n56ckaawi/O/J92BHaECmrz6IRjuMWqlMaMTM=
|
||||
github.com/uptrace/bun v1.2.18 h1:3HnRcMfS6OBPMG1eSOzlbFJ/X/AyMEJb7rMxE6VQvDU=
|
||||
github.com/uptrace/bun v1.2.18/go.mod h1:wNltaKJk4JtOt4SG5I5zmA7v0/Mzjh1+/S906Rayd3Y=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
@@ -105,83 +100,49 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
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=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -189,30 +150,30 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
||||
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
|
||||
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
|
||||
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
|
||||
modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
|
||||
modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Maintainer: Hein (Warky Devs) <hein@warky.dev>
|
||||
pkgname=relspec
|
||||
pkgver=1.0.51
|
||||
pkgver=1.0.59
|
||||
pkgrel=1
|
||||
pkgdesc="RelSpec is a comprehensive database relations management tool that reads, transforms, and writes database table specifications across multiple formats and ORMs."
|
||||
arch=('x86_64' 'aarch64')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: relspec
|
||||
Version: 1.0.51
|
||||
Version: 1.0.59
|
||||
Release: 1%{?dist}
|
||||
Summary: RelSpec is a comprehensive database relations management tool that reads, transforms, and writes database table specifications across multiple formats and ORMs.
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package mariadb
|
||||
|
||||
import "strings"
|
||||
|
||||
// MariaDBToCanonicalTypes maps MariaDB/MySQL type names to canonical types.
|
||||
var MariaDBToCanonicalTypes = map[string]string{
|
||||
// Integer types
|
||||
"tinyint": "int8",
|
||||
"smallint": "int16",
|
||||
"mediumint": "int",
|
||||
"int": "int",
|
||||
"integer": "int",
|
||||
"int2": "int16",
|
||||
"int4": "int",
|
||||
"int8": "int64",
|
||||
"bigint": "int64",
|
||||
// Boolean (TINYINT(1) alias)
|
||||
"boolean": "bool",
|
||||
"bool": "bool",
|
||||
"bit": "bool",
|
||||
// Float types
|
||||
"float": "float32",
|
||||
"double": "float64",
|
||||
"real": "float64",
|
||||
"double precision": "float64",
|
||||
// Decimal types
|
||||
"decimal": "decimal",
|
||||
"numeric": "decimal",
|
||||
"dec": "decimal",
|
||||
"fixed": "decimal",
|
||||
// String types
|
||||
"char": "string",
|
||||
"character": "string",
|
||||
"varchar": "string",
|
||||
"nchar": "string",
|
||||
"nvarchar": "string",
|
||||
"tinytext": "text",
|
||||
"text": "text",
|
||||
"mediumtext": "text",
|
||||
"longtext": "text",
|
||||
// Binary/blob types
|
||||
"binary": "bytea",
|
||||
"varbinary": "bytea",
|
||||
"tinyblob": "bytea",
|
||||
"blob": "bytea",
|
||||
"mediumblob": "bytea",
|
||||
"longblob": "bytea",
|
||||
// Date/time types
|
||||
"date": "date",
|
||||
"time": "time",
|
||||
"datetime": "timestamp",
|
||||
"timestamp": "timestamp",
|
||||
"year": "int",
|
||||
// Other types
|
||||
"json": "json",
|
||||
"enum": "string",
|
||||
"set": "string",
|
||||
"uuid": "uuid",
|
||||
}
|
||||
|
||||
// CanonicalToMariaDBTypes maps canonical types to MariaDB/MySQL types.
|
||||
var CanonicalToMariaDBTypes = map[string]string{
|
||||
"bool": "TINYINT(1)",
|
||||
"int8": "TINYINT",
|
||||
"int16": "SMALLINT",
|
||||
"int": "INT",
|
||||
"int32": "INT",
|
||||
"int64": "BIGINT",
|
||||
"uint": "INT UNSIGNED",
|
||||
"uint8": "TINYINT UNSIGNED",
|
||||
"uint16": "SMALLINT UNSIGNED",
|
||||
"uint32": "INT UNSIGNED",
|
||||
"uint64": "BIGINT UNSIGNED",
|
||||
"float32": "FLOAT",
|
||||
"float64": "DOUBLE",
|
||||
"decimal": "DECIMAL",
|
||||
"string": "VARCHAR(255)",
|
||||
"text": "TEXT",
|
||||
"date": "DATE",
|
||||
"time": "TIME",
|
||||
"timestamp": "DATETIME",
|
||||
"timestamptz": "DATETIME",
|
||||
"uuid": "CHAR(36)",
|
||||
"json": "JSON",
|
||||
"jsonb": "JSON",
|
||||
"bytea": "BLOB",
|
||||
}
|
||||
|
||||
// MariaDBTypeSynonyms maps MariaDB/MySQL type aliases to their canonical MariaDB name.
|
||||
var MariaDBTypeSynonyms = map[string]string{
|
||||
"integer": "int",
|
||||
"int2": "smallint",
|
||||
"int4": "int",
|
||||
"int8": "bigint",
|
||||
"double precision": "double",
|
||||
"character": "char",
|
||||
"dec": "decimal",
|
||||
"fixed": "decimal",
|
||||
"numeric": "decimal",
|
||||
"boolean": "tinyint",
|
||||
"bool": "tinyint",
|
||||
}
|
||||
|
||||
// NormalizeMariaDBType maps a MariaDB/MySQL base type (no dimension parameters)
|
||||
// to its canonical MariaDB form. Unknown types are returned as-is (lowercased).
|
||||
func NormalizeMariaDBType(baseType string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(baseType))
|
||||
if canonical, ok := MariaDBTypeSynonyms[lower]; ok {
|
||||
return canonical
|
||||
}
|
||||
return lower
|
||||
}
|
||||
|
||||
// ConvertMariaDBToCanonical converts a MariaDB/MySQL type name to the canonical type.
|
||||
// Strips dimension parameters and normalizes aliases. Defaults to "string".
|
||||
func ConvertMariaDBToCanonical(mariadbType string) string {
|
||||
base := strings.ToLower(strings.TrimSpace(mariadbType))
|
||||
if idx := strings.Index(base, "("); idx >= 0 {
|
||||
base = strings.TrimSpace(base[:idx])
|
||||
}
|
||||
|
||||
if canonical, ok := MariaDBToCanonicalTypes[base]; ok {
|
||||
return canonical
|
||||
}
|
||||
|
||||
// Prefix match for composite types (e.g., "unsigned bigint")
|
||||
for key, canonical := range MariaDBToCanonicalTypes {
|
||||
if strings.HasPrefix(base, key) {
|
||||
return canonical
|
||||
}
|
||||
}
|
||||
|
||||
return "string"
|
||||
}
|
||||
|
||||
// ConvertCanonicalToMariaDB converts a canonical type to a MariaDB/MySQL type.
|
||||
// Defaults to VARCHAR(255) for unrecognised types.
|
||||
func ConvertCanonicalToMariaDB(canonicalType string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(canonicalType))
|
||||
if idx := strings.Index(lower, "("); idx >= 0 {
|
||||
lower = strings.TrimSpace(lower[:idx])
|
||||
}
|
||||
|
||||
if mariadbType, ok := CanonicalToMariaDBTypes[lower]; ok {
|
||||
return mariadbType
|
||||
}
|
||||
|
||||
// Prefix fallback
|
||||
for canonical, mariadb := range CanonicalToMariaDBTypes {
|
||||
if strings.HasPrefix(lower, canonical) {
|
||||
return mariadb
|
||||
}
|
||||
}
|
||||
|
||||
return "VARCHAR(255)"
|
||||
}
|
||||
+126
-1
@@ -5,9 +5,11 @@ package merge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/pgsql"
|
||||
)
|
||||
|
||||
// MergeResult represents the result of a merge operation
|
||||
@@ -22,6 +24,16 @@ type MergeResult struct {
|
||||
EnumsAdded int
|
||||
ViewsAdded int
|
||||
SequencesAdded int
|
||||
TypeConflicts []ColumnTypeConflict
|
||||
}
|
||||
|
||||
// ColumnTypeConflict describes a column that exists in both schemas but with incompatible types.
|
||||
type ColumnTypeConflict struct {
|
||||
Schema string
|
||||
Table string
|
||||
Column string
|
||||
TargetType string
|
||||
SourceType string
|
||||
}
|
||||
|
||||
// MergeOptions contains options for merge operations
|
||||
@@ -146,11 +158,19 @@ func (r *MergeResult) mergeColumns(table *models.Table, srcTable *models.Table)
|
||||
|
||||
// Merge columns
|
||||
for colName, srcCol := range srcTable.Columns {
|
||||
if _, exists := existingColumns[colName]; !exists {
|
||||
if tgtCol, exists := existingColumns[colName]; !exists {
|
||||
// Column doesn't exist, add it
|
||||
newCol := cloneColumn(srcCol)
|
||||
table.Columns[colName] = newCol
|
||||
r.ColumnsAdded++
|
||||
} else if columnTypeConflict(tgtCol, srcCol) {
|
||||
r.TypeConflicts = append(r.TypeConflicts, ColumnTypeConflict{
|
||||
Schema: firstNonEmpty(table.Schema, srcTable.Schema, srcCol.Schema),
|
||||
Table: firstNonEmpty(table.Name, srcTable.Name, srcCol.Table),
|
||||
Column: firstNonEmpty(tgtCol.Name, srcCol.Name, colName),
|
||||
TargetType: describeColumnType(tgtCol),
|
||||
SourceType: describeColumnType(srcCol),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,6 +446,78 @@ func cloneColumn(col *models.Column) *models.Column {
|
||||
return newCol
|
||||
}
|
||||
|
||||
func columnTypeConflict(target, source *models.Column) bool {
|
||||
if target == nil || source == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
tType, tLen, tPrec, tScale := extractTypeParts(target)
|
||||
sType, sLen, sPrec, sScale := extractTypeParts(source)
|
||||
|
||||
return tType != sType || tLen != sLen || tPrec != sPrec || tScale != sScale
|
||||
}
|
||||
|
||||
// extractTypeParts returns the canonical base type and dimensions for a column,
|
||||
// handling the case where dimensions are embedded in the type string (e.g. "char(2)")
|
||||
// rather than stored in the separate Length/Precision/Scale fields.
|
||||
func extractTypeParts(col *models.Column) (baseType string, length, precision, scale int) {
|
||||
typeName := strings.ToLower(strings.TrimSpace(col.Type))
|
||||
length, precision, scale = col.Length, col.Precision, col.Scale
|
||||
|
||||
if idx := strings.Index(typeName, "("); idx >= 0 {
|
||||
inner := strings.TrimRight(strings.TrimSpace(typeName[idx+1:]), ")")
|
||||
typeName = strings.TrimSpace(typeName[:idx])
|
||||
parts := strings.Split(inner, ",")
|
||||
if len(parts) == 2 {
|
||||
if p, err := strconv.Atoi(strings.TrimSpace(parts[0])); err == nil && p > 0 && precision == 0 {
|
||||
precision = p
|
||||
}
|
||||
if s, err := strconv.Atoi(strings.TrimSpace(parts[1])); err == nil && s > 0 && scale == 0 {
|
||||
scale = s
|
||||
}
|
||||
} else if len(parts) == 1 {
|
||||
if l, err := strconv.Atoi(strings.TrimSpace(parts[0])); err == nil && l > 0 && length == 0 && precision == 0 {
|
||||
length = l
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typeName = pgsql.NormalizePGType(typeName)
|
||||
|
||||
return typeName, length, precision, scale
|
||||
}
|
||||
|
||||
func describeColumnType(col *models.Column) string {
|
||||
if col == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
typeName := strings.TrimSpace(col.Type)
|
||||
if typeName == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch {
|
||||
case col.Precision > 0 && col.Scale > 0:
|
||||
return fmt.Sprintf("%s(%d,%d)", typeName, col.Precision, col.Scale)
|
||||
case col.Precision > 0:
|
||||
return fmt.Sprintf("%s(%d)", typeName, col.Precision)
|
||||
case col.Length > 0:
|
||||
return fmt.Sprintf("%s(%d)", typeName, col.Length)
|
||||
default:
|
||||
return typeName
|
||||
}
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func cloneConstraint(constraint *models.Constraint) *models.Constraint {
|
||||
if constraint == nil {
|
||||
return nil
|
||||
@@ -609,6 +701,7 @@ func GetMergeSummary(result *MergeResult) string {
|
||||
fmt.Sprintf("Enums added: %d", result.EnumsAdded),
|
||||
fmt.Sprintf("Relations added: %d", result.RelationsAdded),
|
||||
fmt.Sprintf("Domains added: %d", result.DomainsAdded),
|
||||
fmt.Sprintf("Type conflicts: %d", len(result.TypeConflicts)),
|
||||
}
|
||||
|
||||
totalAdded := result.SchemasAdded + result.TablesAdded + result.ColumnsAdded +
|
||||
@@ -625,3 +718,35 @@ func GetMergeSummary(result *MergeResult) string {
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
// GetColumnTypeConflictSummary returns a short, human-readable conflict summary.
|
||||
func GetColumnTypeConflictSummary(result *MergeResult, limit int) string {
|
||||
if result == nil || len(result.TypeConflicts) == 0 {
|
||||
return ""
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = len(result.TypeConflicts)
|
||||
}
|
||||
|
||||
lines := make([]string, 0, min(limit, len(result.TypeConflicts))+1)
|
||||
lines = append(lines, "column type conflicts detected:")
|
||||
for i, conflict := range result.TypeConflicts {
|
||||
if i >= limit {
|
||||
break
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf(" - %s.%s.%s: target=%s source=%s",
|
||||
conflict.Schema, conflict.Table, conflict.Column, conflict.TargetType, conflict.SourceType))
|
||||
}
|
||||
if len(result.TypeConflicts) > limit {
|
||||
lines = append(lines, fmt.Sprintf(" ... and %d more", len(result.TypeConflicts)-limit))
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package merge
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
@@ -140,6 +141,61 @@ func TestMergeColumns_NewColumn(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeColumns_TypeConflictIsDetected(t *testing.T) {
|
||||
target := &models.Database{
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "public",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "users",
|
||||
Schema: "public",
|
||||
Columns: map[string]*models.Column{
|
||||
"email": {Name: "email", Type: "varchar", Length: 255},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
source := &models.Database{
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "public",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "users",
|
||||
Schema: "public",
|
||||
Columns: map[string]*models.Column{
|
||||
"email": {Name: "email", Type: "text"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := MergeDatabases(target, source, nil)
|
||||
|
||||
if len(result.TypeConflicts) != 1 {
|
||||
t.Fatalf("Expected 1 type conflict, got %d", len(result.TypeConflicts))
|
||||
}
|
||||
conflict := result.TypeConflicts[0]
|
||||
if conflict.Schema != "public" || conflict.Table != "users" || conflict.Column != "email" {
|
||||
t.Fatalf("Unexpected conflict location: %+v", conflict)
|
||||
}
|
||||
if conflict.TargetType != "varchar(255)" {
|
||||
t.Fatalf("Expected target type varchar(255), got %q", conflict.TargetType)
|
||||
}
|
||||
if conflict.SourceType != "text" {
|
||||
t.Fatalf("Expected source type text, got %q", conflict.SourceType)
|
||||
}
|
||||
|
||||
if got := target.Schemas[0].Tables[0].Columns["email"].Type; got != "varchar" {
|
||||
t.Fatalf("Expected target column type to remain unchanged, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConstraints_NewConstraint(t *testing.T) {
|
||||
target := &models.Database{
|
||||
Schemas: []*models.Schema{
|
||||
@@ -509,6 +565,9 @@ func TestGetMergeSummary(t *testing.T) {
|
||||
ConstraintsAdded: 3,
|
||||
IndexesAdded: 2,
|
||||
ViewsAdded: 1,
|
||||
TypeConflicts: []ColumnTypeConflict{
|
||||
{Schema: "public", Table: "users", Column: "email", TargetType: "varchar(255)", SourceType: "text"},
|
||||
},
|
||||
}
|
||||
|
||||
summary := GetMergeSummary(result)
|
||||
@@ -518,6 +577,9 @@ func TestGetMergeSummary(t *testing.T) {
|
||||
if len(summary) < 50 {
|
||||
t.Errorf("Summary seems too short: %s", summary)
|
||||
}
|
||||
if !strings.Contains(summary, "Type conflicts: 1") {
|
||||
t.Errorf("Expected type conflict count in summary, got: %s", summary)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMergeSummary_Nil(t *testing.T) {
|
||||
|
||||
+94
-50
@@ -2,32 +2,73 @@ package mssql
|
||||
|
||||
import "strings"
|
||||
|
||||
// CanonicalToMSSQLTypes maps canonical types to MSSQL types
|
||||
// CanonicalToMSSQLTypes maps canonical types to MSSQL types.
|
||||
// Accepts both Go canonical names ("int", "string") and SQL canonical names
|
||||
// ("integer", "varchar") so the writer handles input from any reader.
|
||||
var CanonicalToMSSQLTypes = map[string]string{
|
||||
"bool": "BIT",
|
||||
"int8": "TINYINT",
|
||||
"int16": "SMALLINT",
|
||||
"int": "INT",
|
||||
"int32": "INT",
|
||||
"int64": "BIGINT",
|
||||
"uint": "BIGINT",
|
||||
"uint8": "SMALLINT",
|
||||
"uint16": "INT",
|
||||
"uint32": "BIGINT",
|
||||
"uint64": "BIGINT",
|
||||
"float32": "REAL",
|
||||
"float64": "FLOAT",
|
||||
"decimal": "NUMERIC",
|
||||
"string": "NVARCHAR(255)",
|
||||
"text": "NVARCHAR(MAX)",
|
||||
// Boolean — Go and SQL canonical
|
||||
"bool": "BIT",
|
||||
"boolean": "BIT",
|
||||
// Integer — Go canonical
|
||||
"int8": "TINYINT",
|
||||
"int16": "SMALLINT",
|
||||
"int": "INT",
|
||||
"int32": "INT",
|
||||
"int64": "BIGINT",
|
||||
"uint": "BIGINT",
|
||||
"uint8": "TINYINT",
|
||||
"uint16": "SMALLINT",
|
||||
"uint32": "BIGINT",
|
||||
"uint64": "BIGINT",
|
||||
// Integer — SQL canonical (serial types map to base integer; IDENTITY is set via AutoIncrement)
|
||||
"integer": "INT",
|
||||
"smallint": "SMALLINT",
|
||||
"bigint": "BIGINT",
|
||||
"tinyint": "TINYINT",
|
||||
"serial": "INT",
|
||||
"smallserial": "SMALLINT",
|
||||
"bigserial": "BIGINT",
|
||||
// Float — Go canonical
|
||||
"float32": "REAL",
|
||||
"float64": "FLOAT",
|
||||
// Float — SQL canonical
|
||||
"real": "REAL",
|
||||
"double precision": "FLOAT",
|
||||
"double": "FLOAT",
|
||||
// Decimal/numeric
|
||||
"decimal": "NUMERIC",
|
||||
"numeric": "NUMERIC",
|
||||
"money": "MONEY",
|
||||
// String — Go canonical
|
||||
"string": "NVARCHAR(255)",
|
||||
"text": "NVARCHAR(MAX)",
|
||||
// String — SQL canonical
|
||||
"varchar": "NVARCHAR(255)",
|
||||
"char": "NCHAR",
|
||||
"nvarchar": "NVARCHAR(255)",
|
||||
"nchar": "NCHAR",
|
||||
"citext": "NVARCHAR(MAX)",
|
||||
// Date/time
|
||||
"date": "DATE",
|
||||
"time": "TIME",
|
||||
"timetz": "DATETIMEOFFSET",
|
||||
"timestamp": "DATETIME2",
|
||||
"timestamptz": "DATETIMEOFFSET",
|
||||
"uuid": "UNIQUEIDENTIFIER",
|
||||
"json": "NVARCHAR(MAX)",
|
||||
"jsonb": "NVARCHAR(MAX)",
|
||||
"bytea": "VARBINARY(MAX)",
|
||||
"datetime": "DATETIME2",
|
||||
"interval": "NVARCHAR(50)",
|
||||
// UUID
|
||||
"uuid": "UNIQUEIDENTIFIER",
|
||||
// JSON — MSSQL has no native JSON type; stored as NVARCHAR(MAX)
|
||||
"json": "NVARCHAR(MAX)",
|
||||
"jsonb": "NVARCHAR(MAX)",
|
||||
// Binary
|
||||
"bytea": "VARBINARY(MAX)",
|
||||
"blob": "VARBINARY(MAX)",
|
||||
// Network/geo types — no MSSQL native equivalent
|
||||
"xml": "XML",
|
||||
"inet": "NVARCHAR(45)",
|
||||
"cidr": "NVARCHAR(43)",
|
||||
"macaddr": "NVARCHAR(17)",
|
||||
}
|
||||
|
||||
// MSSQLToCanonicalTypes maps MSSQL types to canonical types
|
||||
@@ -68,47 +109,50 @@ var MSSQLToCanonicalTypes = map[string]string{
|
||||
"geometry": "string",
|
||||
}
|
||||
|
||||
// ConvertCanonicalToMSSQL converts a canonical type to MSSQL type
|
||||
// MSSQLTypeSynonyms maps MSSQL type aliases to their canonical MSSQL name.
|
||||
var MSSQLTypeSynonyms = map[string]string{
|
||||
"integer": "int",
|
||||
"dec": "decimal",
|
||||
"float(n)": "float",
|
||||
}
|
||||
|
||||
// NormalizeMSSQLType maps an MSSQL base type (no dimension parameters) to its
|
||||
// canonical MSSQL form. Unknown types are returned as-is (lowercased).
|
||||
func NormalizeMSSQLType(baseType string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(baseType))
|
||||
if canonical, ok := MSSQLTypeSynonyms[lower]; ok {
|
||||
return canonical
|
||||
}
|
||||
return lower
|
||||
}
|
||||
|
||||
// ConvertCanonicalToMSSQL converts a canonical type (Go or SQL) to an MSSQL type.
|
||||
// Strips dimension parameters before lookup. Defaults to NVARCHAR(255) for unknown types.
|
||||
func ConvertCanonicalToMSSQL(canonicalType string) string {
|
||||
// Check direct mapping
|
||||
if mssqlType, exists := CanonicalToMSSQLTypes[strings.ToLower(canonicalType)]; exists {
|
||||
base := strings.ToLower(strings.TrimSpace(canonicalType))
|
||||
if idx := strings.Index(base, "("); idx >= 0 {
|
||||
base = strings.TrimSpace(base[:idx])
|
||||
}
|
||||
base = strings.TrimSuffix(base, "[]")
|
||||
|
||||
if mssqlType, exists := CanonicalToMSSQLTypes[base]; exists {
|
||||
return mssqlType
|
||||
}
|
||||
|
||||
// Try to find by prefix
|
||||
lowerType := strings.ToLower(canonicalType)
|
||||
for canonical, mssql := range CanonicalToMSSQLTypes {
|
||||
if strings.HasPrefix(lowerType, canonical) {
|
||||
return mssql
|
||||
}
|
||||
}
|
||||
|
||||
// Default to NVARCHAR
|
||||
return "NVARCHAR(255)"
|
||||
}
|
||||
|
||||
// ConvertMSSQLToCanonical converts an MSSQL type to canonical type
|
||||
// ConvertMSSQLToCanonical converts an MSSQL type to the canonical type.
|
||||
// Strips dimension parameters before lookup. Defaults to "string" for unknown types.
|
||||
func ConvertMSSQLToCanonical(mssqlType string) string {
|
||||
// Extract base type (remove parentheses and parameters)
|
||||
baseType := mssqlType
|
||||
if idx := strings.Index(baseType, "("); idx != -1 {
|
||||
baseType = baseType[:idx]
|
||||
base := strings.ToLower(strings.TrimSpace(mssqlType))
|
||||
if idx := strings.Index(base, "("); idx >= 0 {
|
||||
base = strings.TrimSpace(base[:idx])
|
||||
}
|
||||
baseType = strings.TrimSpace(baseType)
|
||||
|
||||
// Check direct mapping
|
||||
if canonicalType, exists := MSSQLToCanonicalTypes[strings.ToLower(baseType)]; exists {
|
||||
if canonicalType, exists := MSSQLToCanonicalTypes[base]; exists {
|
||||
return canonicalType
|
||||
}
|
||||
|
||||
// Try to find by prefix
|
||||
lowerType := strings.ToLower(baseType)
|
||||
for mssql, canonical := range MSSQLToCanonicalTypes {
|
||||
if strings.HasPrefix(lowerType, mssql) {
|
||||
return canonical
|
||||
}
|
||||
}
|
||||
|
||||
// Default to string
|
||||
return "string"
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ var GoToStdTypes = map[string]string{
|
||||
"sqldate": "date",
|
||||
"sqltime": "time",
|
||||
"sqltimestamp": "timestamp",
|
||||
"time.Time": "timestamp",
|
||||
}
|
||||
|
||||
var GoToPGSQLTypes = map[string]string{
|
||||
@@ -90,6 +91,7 @@ var GoToPGSQLTypes = map[string]string{
|
||||
"sqldate": "date",
|
||||
"sqltime": "time",
|
||||
"sqltimestamp": "timestamp",
|
||||
"time.Time": "timestamp",
|
||||
"citext": "citext",
|
||||
}
|
||||
|
||||
@@ -135,6 +137,62 @@ func ConvertSQLType(anytype string) string {
|
||||
return anytype
|
||||
}
|
||||
|
||||
// PGTypeCanonical maps PostgreSQL type aliases and synonyms to their canonical base name.
|
||||
// Input should be a base type (no dimension parameters, lowercase).
|
||||
var PGTypeCanonical = map[string]string{
|
||||
// integer aliases
|
||||
"int": "integer",
|
||||
"int4": "integer",
|
||||
"int2": "smallint",
|
||||
"int8": "bigint",
|
||||
// float aliases
|
||||
"float4": "real",
|
||||
"float8": "double precision",
|
||||
// bool alias
|
||||
"bool": "boolean",
|
||||
// char aliases
|
||||
"character": "char",
|
||||
"character varying": "varchar",
|
||||
"bpchar": "char",
|
||||
// timestamp aliases
|
||||
"timestamp without time zone": "timestamp",
|
||||
"timestamp with time zone": "timestamptz",
|
||||
// time aliases
|
||||
"time without time zone": "time",
|
||||
"time with time zone": "timetz",
|
||||
// decimal alias
|
||||
"decimal": "numeric",
|
||||
}
|
||||
|
||||
// knownPGBaseTypes is the set of canonical PostgreSQL base types (no aliases).
|
||||
var knownPGBaseTypes = map[string]struct{}{
|
||||
"integer": {}, "bigint": {}, "smallint": {},
|
||||
"serial": {}, "bigserial": {}, "smallserial": {},
|
||||
"numeric": {}, "real": {}, "double precision": {}, "money": {},
|
||||
"varchar": {}, "char": {}, "text": {}, "citext": {},
|
||||
"boolean": {},
|
||||
"date": {}, "time": {}, "timetz": {}, "timestamp": {}, "timestamptz": {}, "interval": {},
|
||||
"uuid": {}, "json": {}, "jsonb": {}, "bytea": {},
|
||||
"inet": {}, "cidr": {}, "macaddr": {}, "xml": {},
|
||||
}
|
||||
|
||||
// NormalizePGType maps a PostgreSQL base type (no dimension parameters) to its
|
||||
// canonical form. Unknown types are returned as-is (lowercased).
|
||||
func NormalizePGType(baseType string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(baseType))
|
||||
if canonical, ok := PGTypeCanonical[lower]; ok {
|
||||
return canonical
|
||||
}
|
||||
return lower
|
||||
}
|
||||
|
||||
// IsKnownPGBaseType reports whether the given name (after NormalizePGType) is a
|
||||
// recognized built-in PostgreSQL type. Custom types (e.g. vector, postgis) return false.
|
||||
func IsKnownPGBaseType(baseType string) bool {
|
||||
_, ok := knownPGBaseTypes[strings.ToLower(strings.TrimSpace(baseType))]
|
||||
return ok
|
||||
}
|
||||
|
||||
func IsGoType(pTypeName string) bool {
|
||||
for k := range GoToStdTypes {
|
||||
if strings.EqualFold(pTypeName, k) {
|
||||
|
||||
@@ -145,6 +145,24 @@ var postgresTypeAliases = map[string]string{
|
||||
"bool": "boolean",
|
||||
}
|
||||
|
||||
var postgresEquivalentBaseTypes = map[string]string{
|
||||
"character varying": "varchar",
|
||||
"character": "char",
|
||||
"timestamp without time zone": "timestamp",
|
||||
"timestamp with time zone": "timestamptz",
|
||||
"time without time zone": "time",
|
||||
"time with time zone": "timetz",
|
||||
}
|
||||
|
||||
var postgresEquivalentBaseTypeVariants = map[string][]string{
|
||||
"varchar": {"varchar", "character varying"},
|
||||
"char": {"char", "character"},
|
||||
"timestamp": {"timestamp", "timestamp without time zone"},
|
||||
"timestamptz": {"timestamptz", "timestamp with time zone"},
|
||||
"time": {"time", "time without time zone"},
|
||||
"timetz": {"timetz", "time with time zone"},
|
||||
}
|
||||
|
||||
// GetPostgresBaseTypes returns a sorted-ish stable list of registered base type names.
|
||||
func GetPostgresBaseTypes() []string {
|
||||
result := make([]string, 0, len(postgresBaseTypes))
|
||||
@@ -212,6 +230,86 @@ func CanonicalizeBaseType(baseType string) string {
|
||||
return base
|
||||
}
|
||||
|
||||
// EquivalentBaseType resolves broader SQL-equivalent spellings to a common comparable form.
|
||||
func EquivalentBaseType(baseType string) string {
|
||||
base := CanonicalizeBaseType(baseType)
|
||||
if equivalent, ok := postgresEquivalentBaseTypes[base]; ok {
|
||||
return equivalent
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
// NormalizeEquivalentSQLType returns a normalized SQL type string suitable for equality checks.
|
||||
// Equivalent spellings such as "character varying(255)" and "varchar(255)" normalize identically.
|
||||
func NormalizeEquivalentSQLType(sqlType string) string {
|
||||
t := normalizeTypeToken(sqlType)
|
||||
if t == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
arrayDepth := 0
|
||||
for strings.HasSuffix(t, "[]") {
|
||||
arrayDepth++
|
||||
t = strings.TrimSpace(strings.TrimSuffix(t, "[]"))
|
||||
}
|
||||
|
||||
modifier := ""
|
||||
if idx := strings.Index(t, "("); idx >= 0 {
|
||||
modifier = strings.TrimSpace(t[idx:])
|
||||
t = strings.TrimSpace(t[:idx])
|
||||
}
|
||||
|
||||
base := EquivalentBaseType(t)
|
||||
normalized := base + modifier
|
||||
for i := 0; i < arrayDepth; i++ {
|
||||
normalized += "[]"
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// EquivalentSQLTypeVariants returns equivalent PostgreSQL spellings for a SQL type.
|
||||
// Examples:
|
||||
// - varchar(255) -> ["varchar(255)", "character varying(255)"]
|
||||
// - timestamptz -> ["timestamptz", "timestamp with time zone"]
|
||||
func EquivalentSQLTypeVariants(sqlType string) []string {
|
||||
t := normalizeTypeToken(sqlType)
|
||||
if t == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
arrayDepth := 0
|
||||
for strings.HasSuffix(t, "[]") {
|
||||
arrayDepth++
|
||||
t = strings.TrimSpace(strings.TrimSuffix(t, "[]"))
|
||||
}
|
||||
|
||||
modifier := ""
|
||||
if idx := strings.Index(t, "("); idx >= 0 {
|
||||
modifier = strings.TrimSpace(t[idx:])
|
||||
t = strings.TrimSpace(t[:idx])
|
||||
}
|
||||
|
||||
base := EquivalentBaseType(t)
|
||||
bases := postgresEquivalentBaseTypeVariants[base]
|
||||
if len(bases) == 0 {
|
||||
bases = []string{base}
|
||||
}
|
||||
|
||||
seen := make(map[string]bool, len(bases))
|
||||
result := make([]string, 0, len(bases))
|
||||
for _, variantBase := range bases {
|
||||
variant := variantBase + modifier
|
||||
for i := 0; i < arrayDepth; i++ {
|
||||
variant += "[]"
|
||||
}
|
||||
if !seen[variant] {
|
||||
seen[variant] = true
|
||||
result = append(result, variant)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IsKnownPostgresType reports whether a type (including array forms) exists in the registry.
|
||||
func IsKnownPostgresType(sqlType string) bool {
|
||||
base := CanonicalizeBaseType(ExtractBaseTypeLower(sqlType))
|
||||
|
||||
@@ -97,3 +97,51 @@ func TestPostgresTypeRegistry_TypeParsingAndCapabilities(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeEquivalentSQLType(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{input: "character varying(255)", want: "varchar(255)"},
|
||||
{input: "varchar(255)", want: "varchar(255)"},
|
||||
{input: "timestamp with time zone", want: "timestamptz"},
|
||||
{input: "timestamptz", want: "timestamptz"},
|
||||
{input: "time without time zone", want: "time"},
|
||||
{input: "character varying(255)[]", want: "varchar(255)[]"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got := NormalizeEquivalentSQLType(tt.input)
|
||||
if got != tt.want {
|
||||
t.Fatalf("NormalizeEquivalentSQLType(%q) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEquivalentSQLTypeVariants(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want []string
|
||||
}{
|
||||
{input: "character varying(255)", want: []string{"varchar(255)", "character varying(255)"}},
|
||||
{input: "timestamptz", want: []string{"timestamptz", "timestamp with time zone"}},
|
||||
{input: "text[]", want: []string{"text[]"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got := EquivalentSQLTypeVariants(tt.input)
|
||||
if len(got) != len(tt.want) {
|
||||
t.Fatalf("EquivalentSQLTypeVariants(%q) len = %d, want %d (%v)", tt.input, len(got), len(tt.want), got)
|
||||
}
|
||||
for i := range tt.want {
|
||||
if got[i] != tt.want[i] {
|
||||
t.Fatalf("EquivalentSQLTypeVariants(%q)[%d] = %q, want %q", tt.input, i, got[i], tt.want[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,15 @@ func (r *Reader) queryColumns(schemaName string) (map[string]map[string]*models.
|
||||
}
|
||||
|
||||
if numPrecision != nil {
|
||||
column.Precision = *numPrecision
|
||||
// For integer and serial types, numeric_precision is a bit-width (32, 64, 16)
|
||||
// not a user-visible column parameter. Only store precision for types where
|
||||
// it represents actual decimal/scale precision (numeric, decimal, float).
|
||||
switch column.Type {
|
||||
case "integer", "bigint", "smallint", "serial", "bigserial", "smallserial":
|
||||
// skip — bit-width, not a column parameter
|
||||
default:
|
||||
column.Precision = *numPrecision
|
||||
}
|
||||
}
|
||||
|
||||
if numScale != nil {
|
||||
|
||||
+28
-62
@@ -259,12 +259,14 @@ func (r *Reader) close() {
|
||||
}
|
||||
}
|
||||
|
||||
// mapDataType maps PostgreSQL data types while preserving exact type text when available.
|
||||
// mapDataType maps a PostgreSQL data type to its canonical RelSpec name.
|
||||
// For known built-in types, dimensions are stripped from the type string (they are
|
||||
// stored separately in column.Length/Precision/Scale). For custom types (e.g.
|
||||
// vector(1536), postgis geometries), the full formatted type is preserved.
|
||||
func (r *Reader) mapDataType(pgType, udtName, formattedType string, hasNextval bool) string {
|
||||
normalizedPGType := strings.ToLower(strings.TrimSpace(pgType))
|
||||
|
||||
// If the column has a nextval default, it's likely a serial type
|
||||
// Map to the appropriate serial type instead of the base integer type
|
||||
// Detect serial types from nextval defaults before anything else.
|
||||
if hasNextval {
|
||||
switch normalizedPGType {
|
||||
case "integer", "int", "int4":
|
||||
@@ -276,73 +278,38 @@ func (r *Reader) mapDataType(pgType, udtName, formattedType string, hasNextval b
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer the database-provided formatted type; this preserves arrays/custom
|
||||
// types/modifiers like text[], vector(1536), numeric(10,2), etc.
|
||||
if strings.TrimSpace(formattedType) != "" {
|
||||
return formattedType
|
||||
}
|
||||
|
||||
// information_schema reports arrays generically as "ARRAY" with udt_name like "_text".
|
||||
if strings.EqualFold(pgType, "ARRAY") && strings.HasPrefix(udtName, "_") && len(udtName) > 1 {
|
||||
return udtName[1:] + "[]"
|
||||
}
|
||||
|
||||
// Map common PostgreSQL types
|
||||
typeMap := map[string]string{
|
||||
"integer": "integer",
|
||||
"bigint": "bigint",
|
||||
"smallint": "smallint",
|
||||
"int": "integer",
|
||||
"int2": "smallint",
|
||||
"int4": "integer",
|
||||
"int8": "bigint",
|
||||
"serial": "serial",
|
||||
"bigserial": "bigserial",
|
||||
"smallserial": "smallserial",
|
||||
"numeric": "numeric",
|
||||
"decimal": "decimal",
|
||||
"real": "real",
|
||||
"double precision": "double precision",
|
||||
"float4": "real",
|
||||
"float8": "double precision",
|
||||
"money": "money",
|
||||
"character varying": "varchar",
|
||||
"varchar": "varchar",
|
||||
"character": "char",
|
||||
"char": "char",
|
||||
"text": "text",
|
||||
"boolean": "boolean",
|
||||
"bool": "boolean",
|
||||
"date": "date",
|
||||
"time": "time",
|
||||
"time without time zone": "time",
|
||||
"time with time zone": "timetz",
|
||||
"timestamp": "timestamp",
|
||||
"timestamp without time zone": "timestamp",
|
||||
"timestamp with time zone": "timestamptz",
|
||||
"timestamptz": "timestamptz",
|
||||
"interval": "interval",
|
||||
"uuid": "uuid",
|
||||
"json": "json",
|
||||
"jsonb": "jsonb",
|
||||
"bytea": "bytea",
|
||||
"inet": "inet",
|
||||
"cidr": "cidr",
|
||||
"macaddr": "macaddr",
|
||||
"xml": "xml",
|
||||
// Use the database-formatted type when available. For known built-in types, strip
|
||||
// embedded dimensions (they are stored in column.Length/Precision/Scale separately).
|
||||
// For unknown/custom types, keep the full formatted string (e.g. vector(1536)).
|
||||
if strings.TrimSpace(formattedType) != "" {
|
||||
lower := strings.ToLower(strings.TrimSpace(formattedType))
|
||||
isArray := strings.HasSuffix(lower, "[]")
|
||||
base := strings.TrimSuffix(lower, "[]")
|
||||
if idx := strings.Index(base, "("); idx >= 0 {
|
||||
base = strings.TrimSpace(base[:idx])
|
||||
}
|
||||
canonical := pgsql.NormalizePGType(base)
|
||||
if pgsql.IsKnownPGBaseType(canonical) {
|
||||
if isArray {
|
||||
return canonical + "[]"
|
||||
}
|
||||
return canonical
|
||||
}
|
||||
return formattedType
|
||||
}
|
||||
|
||||
// Try mapped type first
|
||||
if mapped, exists := typeMap[normalizedPGType]; exists {
|
||||
return mapped
|
||||
// Fall back to normalizing the information_schema type name directly.
|
||||
canonical := pgsql.NormalizePGType(normalizedPGType)
|
||||
if pgsql.IsKnownPGBaseType(canonical) {
|
||||
return canonical
|
||||
}
|
||||
|
||||
// Use pgsql utilities if available
|
||||
if pgsql.ValidSQLType(pgType) {
|
||||
return pgsql.GetSQLType(pgType)
|
||||
}
|
||||
|
||||
// Return UDT name for custom types (including array fallback when needed)
|
||||
// Return UDT name for custom types.
|
||||
if udtName != "" {
|
||||
if strings.HasPrefix(udtName, "_") && len(udtName) > 1 {
|
||||
return udtName[1:] + "[]"
|
||||
@@ -350,7 +317,6 @@ func (r *Reader) mapDataType(pgType, udtName, formattedType string, hasNextval b
|
||||
return udtName
|
||||
}
|
||||
|
||||
// Default to the original type
|
||||
return pgType
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ func TestMapDataType(t *testing.T) {
|
||||
{"unknown_type", "custom", "", "custom"}, // Should return UDT name
|
||||
{"ARRAY", "_text", "", "text[]"},
|
||||
{"USER-DEFINED", "vector", "vector(1536)", "vector(1536)"},
|
||||
{"character varying", "varchar", "character varying(255)", "character varying(255)"},
|
||||
{"character varying", "varchar", "character varying(255)", "varchar"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/readers"
|
||||
sqlitepkg "git.warky.dev/wdevs/relspecgo/pkg/sqlite"
|
||||
)
|
||||
|
||||
// Reader implements the readers.Reader interface for SQLite databases
|
||||
@@ -183,59 +184,9 @@ func (r *Reader) close() {
|
||||
}
|
||||
}
|
||||
|
||||
// mapDataType maps SQLite data types to canonical types
|
||||
// mapDataType maps SQLite data types to canonical types.
|
||||
func (r *Reader) mapDataType(sqliteType string) string {
|
||||
// SQLite has a flexible type system, but we map common types
|
||||
typeMap := map[string]string{
|
||||
"INTEGER": "int",
|
||||
"INT": "int",
|
||||
"TINYINT": "int8",
|
||||
"SMALLINT": "int16",
|
||||
"MEDIUMINT": "int",
|
||||
"BIGINT": "int64",
|
||||
"UNSIGNED BIG INT": "uint64",
|
||||
"INT2": "int16",
|
||||
"INT8": "int64",
|
||||
"REAL": "float64",
|
||||
"DOUBLE": "float64",
|
||||
"DOUBLE PRECISION": "float64",
|
||||
"FLOAT": "float32",
|
||||
"NUMERIC": "decimal",
|
||||
"DECIMAL": "decimal",
|
||||
"BOOLEAN": "bool",
|
||||
"BOOL": "bool",
|
||||
"DATE": "date",
|
||||
"DATETIME": "timestamp",
|
||||
"TIMESTAMP": "timestamp",
|
||||
"TEXT": "string",
|
||||
"VARCHAR": "string",
|
||||
"CHAR": "string",
|
||||
"CHARACTER": "string",
|
||||
"VARYING CHARACTER": "string",
|
||||
"NCHAR": "string",
|
||||
"NVARCHAR": "string",
|
||||
"CLOB": "text",
|
||||
"BLOB": "bytea",
|
||||
}
|
||||
|
||||
// Try exact match first
|
||||
if mapped, exists := typeMap[sqliteType]; exists {
|
||||
return mapped
|
||||
}
|
||||
|
||||
// Try case-insensitive match for common types
|
||||
sqliteTypeUpper := sqliteType
|
||||
if len(sqliteType) > 0 {
|
||||
// Extract base type (e.g., "VARCHAR(255)" -> "VARCHAR")
|
||||
for baseType := range typeMap {
|
||||
if len(sqliteTypeUpper) >= len(baseType) && sqliteTypeUpper[:len(baseType)] == baseType {
|
||||
return typeMap[baseType]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to string for unknown types
|
||||
return "string"
|
||||
return sqlitepkg.ConvertSQLiteToCanonical(sqliteType)
|
||||
}
|
||||
|
||||
// deriveRelationship creates a relationship from a foreign key constraint
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
package sqlite
|
||||
|
||||
import "strings"
|
||||
|
||||
// SQLiteToCanonicalTypes maps SQLite type names to canonical types.
|
||||
// SQLite has type affinity rules; this maps common type names including
|
||||
// MySQL/PostgreSQL types that users write in SQLite schemas.
|
||||
var SQLiteToCanonicalTypes = map[string]string{
|
||||
// Integer affinity
|
||||
"integer": "int",
|
||||
"int": "int",
|
||||
"tinyint": "int8",
|
||||
"smallint": "int16",
|
||||
"mediumint": "int",
|
||||
"bigint": "int64",
|
||||
"unsigned big int": "uint64",
|
||||
"int2": "int16",
|
||||
"int8": "int64",
|
||||
// Real affinity
|
||||
"real": "float64",
|
||||
"double": "float64",
|
||||
"double precision": "float64",
|
||||
"float": "float32",
|
||||
// Numeric affinity
|
||||
"numeric": "decimal",
|
||||
"decimal": "decimal",
|
||||
// Boolean (stored as integer in SQLite)
|
||||
"boolean": "bool",
|
||||
"bool": "bool",
|
||||
// Date/time (stored as text in SQLite)
|
||||
"date": "date",
|
||||
"datetime": "timestamp",
|
||||
"timestamp": "timestamp",
|
||||
// Text affinity
|
||||
"text": "string",
|
||||
"varchar": "string",
|
||||
"char": "string",
|
||||
"character": "string",
|
||||
"varying character": "string",
|
||||
"nchar": "string",
|
||||
"nvarchar": "string",
|
||||
"clob": "text",
|
||||
// Blob affinity
|
||||
"blob": "bytea",
|
||||
}
|
||||
|
||||
// CanonicalToSQLiteAffinity maps type names to SQLite type affinity names.
|
||||
// Accepts both Go canonical names ("int", "string") and SQL canonical names
|
||||
// ("integer", "varchar") so the writer handles input from any reader.
|
||||
// The five SQLite type affinities are TEXT, INTEGER, REAL, NUMERIC, BLOB.
|
||||
var CanonicalToSQLiteAffinity = map[string]string{
|
||||
// INTEGER affinity — Go canonical
|
||||
"int": "INTEGER",
|
||||
"int8": "INTEGER",
|
||||
"int16": "INTEGER",
|
||||
"int32": "INTEGER",
|
||||
"int64": "INTEGER",
|
||||
"uint": "INTEGER",
|
||||
"uint8": "INTEGER",
|
||||
"uint16": "INTEGER",
|
||||
"uint32": "INTEGER",
|
||||
"uint64": "INTEGER",
|
||||
"bool": "INTEGER",
|
||||
// INTEGER affinity — SQL canonical
|
||||
"integer": "INTEGER",
|
||||
"smallint": "INTEGER",
|
||||
"bigint": "INTEGER",
|
||||
"serial": "INTEGER",
|
||||
"smallserial": "INTEGER",
|
||||
"bigserial": "INTEGER",
|
||||
"boolean": "INTEGER",
|
||||
"tinyint": "INTEGER",
|
||||
"mediumint": "INTEGER",
|
||||
// REAL affinity — Go canonical
|
||||
"float32": "REAL",
|
||||
"float64": "REAL",
|
||||
// REAL affinity — SQL canonical
|
||||
"real": "REAL",
|
||||
"float": "REAL",
|
||||
"double": "REAL",
|
||||
"double precision": "REAL",
|
||||
// NUMERIC affinity
|
||||
"decimal": "NUMERIC",
|
||||
"numeric": "NUMERIC",
|
||||
"money": "NUMERIC",
|
||||
"smallmoney": "NUMERIC",
|
||||
// BLOB affinity
|
||||
"bytea": "BLOB",
|
||||
"blob": "BLOB",
|
||||
// TEXT affinity — Go canonical
|
||||
"string": "TEXT",
|
||||
"text": "TEXT",
|
||||
// TEXT affinity — SQL canonical
|
||||
"varchar": "TEXT",
|
||||
"char": "TEXT",
|
||||
"nvarchar": "TEXT",
|
||||
"nchar": "TEXT",
|
||||
"citext": "TEXT",
|
||||
"date": "TEXT",
|
||||
"time": "TEXT",
|
||||
"timetz": "TEXT",
|
||||
"timestamp": "TEXT",
|
||||
"timestamptz": "TEXT",
|
||||
"datetime": "TEXT",
|
||||
"uuid": "TEXT",
|
||||
"json": "TEXT",
|
||||
"jsonb": "TEXT",
|
||||
"xml": "TEXT",
|
||||
"inet": "TEXT",
|
||||
"cidr": "TEXT",
|
||||
"macaddr": "TEXT",
|
||||
}
|
||||
|
||||
// ConvertSQLiteToCanonical converts a SQLite type name to the canonical type.
|
||||
// Strips dimension parameters (e.g. VARCHAR(255) → string) and handles
|
||||
// SQLite's flexible affinity rules. Defaults to "string" for unknown types.
|
||||
func ConvertSQLiteToCanonical(sqliteType string) string {
|
||||
base := strings.ToUpper(strings.TrimSpace(sqliteType))
|
||||
if idx := strings.Index(base, "("); idx >= 0 {
|
||||
base = strings.TrimSpace(base[:idx])
|
||||
}
|
||||
lower := strings.ToLower(base)
|
||||
|
||||
if canonical, ok := SQLiteToCanonicalTypes[lower]; ok {
|
||||
return canonical
|
||||
}
|
||||
|
||||
// Prefix match for types like "VARYING CHARACTER(255)"
|
||||
for key, canonical := range SQLiteToCanonicalTypes {
|
||||
if strings.HasPrefix(lower, key) {
|
||||
return canonical
|
||||
}
|
||||
}
|
||||
|
||||
return "string"
|
||||
}
|
||||
|
||||
// ConvertCanonicalToSQLite converts a canonical type (or any SQL type) to its
|
||||
// SQLite type affinity. Defaults to TEXT for unrecognised types.
|
||||
func ConvertCanonicalToSQLite(canonicalType string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(canonicalType))
|
||||
if idx := strings.Index(normalized, "("); idx >= 0 {
|
||||
normalized = strings.TrimSpace(normalized[:idx])
|
||||
}
|
||||
normalized = strings.TrimSuffix(normalized, "[]")
|
||||
|
||||
if affinity, ok := CanonicalToSQLiteAffinity[normalized]; ok {
|
||||
return affinity
|
||||
}
|
||||
|
||||
return "TEXT"
|
||||
}
|
||||
@@ -18,17 +18,20 @@ type TemplateData struct {
|
||||
|
||||
// ModelData represents a single model/struct in the template
|
||||
type ModelData struct {
|
||||
Name string
|
||||
TableName string // schema.table format
|
||||
SchemaName string
|
||||
TableNameOnly string // just table name without schema
|
||||
Comment string
|
||||
Fields []*FieldData
|
||||
Config *MethodConfig
|
||||
PrimaryKeyField string // Name of the primary key field
|
||||
PrimaryKeyIsSQL bool // Whether PK uses SQL type (needs .Int64() call)
|
||||
IDColumnName string // Name of the ID column in database
|
||||
Prefix string // 3-letter prefix
|
||||
Name string
|
||||
TableName string // schema.table format
|
||||
SchemaName string
|
||||
TableNameOnly string // just table name without schema
|
||||
Comment string
|
||||
Fields []*FieldData
|
||||
Config *MethodConfig
|
||||
PrimaryKeyField string // Name of the primary key field
|
||||
PrimaryKeyType string // Go type of the primary key field
|
||||
PrimaryKeyIsSQL bool // Whether PK uses SQL type (needs .Int64() call)
|
||||
PrimaryKeyIsStr bool // Whether helper methods should use string IDs
|
||||
PrimaryKeyIDType string // Helper method GetID/SetID/UpdateID type
|
||||
IDColumnName string // Name of the ID column in database
|
||||
Prefix string // 3-letter prefix
|
||||
}
|
||||
|
||||
// FieldData represents a single field in a struct
|
||||
@@ -140,7 +143,13 @@ func NewModelData(table *models.Table, schema string, typeMapper *TypeMapper, fl
|
||||
model.IDColumnName = safeName
|
||||
// Check if PK type is a SQL type (contains resolvespec_common or sql_types)
|
||||
goType := typeMapper.SQLTypeToGoType(col.Type, col.NotNull)
|
||||
model.PrimaryKeyType = goType
|
||||
model.PrimaryKeyIsSQL = strings.Contains(goType, "resolvespec_common") || strings.Contains(goType, "sql_types")
|
||||
model.PrimaryKeyIsStr = isStringLikePrimaryKeyType(goType)
|
||||
model.PrimaryKeyIDType = "int64"
|
||||
if model.PrimaryKeyIsStr {
|
||||
model.PrimaryKeyIDType = "string"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -192,6 +201,15 @@ func formatComment(description, comment string) string {
|
||||
return comment
|
||||
}
|
||||
|
||||
func isStringLikePrimaryKeyType(goType string) bool {
|
||||
switch goType {
|
||||
case "string", "sql.NullString", "resolvespec_common.SqlString", "resolvespec_common.SqlUUID":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// resolveFieldNameCollision checks if a field name conflicts with generated method names
|
||||
// and adds an underscore suffix if there's a collision
|
||||
func resolveFieldNameCollision(fieldName string) string {
|
||||
|
||||
@@ -44,33 +44,55 @@ func (m {{.Name}}) SchemaName() string {
|
||||
{{end}}
|
||||
{{if and .Config.GenerateGetID .PrimaryKeyField}}
|
||||
// GetID returns the primary key value
|
||||
func (m {{.Name}}) GetID() int64 {
|
||||
func (m {{.Name}}) GetID() {{.PrimaryKeyIDType}} {
|
||||
{{if .PrimaryKeyIsSQL -}}
|
||||
{{if .PrimaryKeyIsStr -}}
|
||||
return m.{{.PrimaryKeyField}}.String()
|
||||
{{- else -}}
|
||||
return m.{{.PrimaryKeyField}}.Int64()
|
||||
{{- end}}
|
||||
{{- else -}}
|
||||
{{if .PrimaryKeyIsStr -}}
|
||||
return m.{{.PrimaryKeyField}}
|
||||
{{- else -}}
|
||||
return int64(m.{{.PrimaryKeyField}})
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
{{if and .Config.GenerateGetIDStr .PrimaryKeyField}}
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m {{.Name}}) GetIDStr() string {
|
||||
{{if .PrimaryKeyIsSQL -}}
|
||||
return m.{{.PrimaryKeyField}}.String()
|
||||
{{- else if .PrimaryKeyIsStr -}}
|
||||
return m.{{.PrimaryKeyField}}
|
||||
{{- else -}}
|
||||
return fmt.Sprintf("%d", m.{{.PrimaryKeyField}})
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
{{if and .Config.GenerateSetID .PrimaryKeyField}}
|
||||
// SetID sets the primary key value
|
||||
func (m {{.Name}}) SetID(newid int64) {
|
||||
func (m {{.Name}}) SetID(newid {{.PrimaryKeyIDType}}) {
|
||||
m.UpdateID(newid)
|
||||
}
|
||||
{{end}}
|
||||
{{if and .Config.GenerateUpdateID .PrimaryKeyField}}
|
||||
// UpdateID updates the primary key value
|
||||
func (m *{{.Name}}) UpdateID(newid int64) {
|
||||
func (m *{{.Name}}) UpdateID(newid {{.PrimaryKeyIDType}}) {
|
||||
{{if .PrimaryKeyIsSQL -}}
|
||||
m.{{.PrimaryKeyField}}.FromString(fmt.Sprintf("%d", newid))
|
||||
{{if .PrimaryKeyIsStr -}}
|
||||
m.{{.PrimaryKeyField}}.FromString(newid)
|
||||
{{- else -}}
|
||||
m.{{.PrimaryKeyField}} = int32(newid)
|
||||
m.{{.PrimaryKeyField}}.FromString(fmt.Sprintf("%d", newid))
|
||||
{{- end}}
|
||||
{{- else -}}
|
||||
{{if .PrimaryKeyIsStr -}}
|
||||
m.{{.PrimaryKeyField}} = newid
|
||||
{{- else -}}
|
||||
m.{{.PrimaryKeyField}} = {{.PrimaryKeyType}}(newid)
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
@@ -323,7 +323,7 @@ func (tm *TypeMapper) BuildBunTag(column *models.Column, table *models.Table) st
|
||||
}
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("type:%s", typeStr))
|
||||
if isArray {
|
||||
if isArray && tm.typeStyle == writers.NullableTypeStdlib {
|
||||
parts = append(parts, "array")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,8 +102,8 @@ func (w *Writer) writeSingleFile(db *models.Database) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Add fmt import if GetIDStr is enabled
|
||||
if w.config.GenerateGetIDStr {
|
||||
// Add fmt import when generated helper methods need string formatting.
|
||||
if w.needsFmtImport(templateData.Models) {
|
||||
templateData.AddImport("\"fmt\"")
|
||||
}
|
||||
|
||||
@@ -195,8 +195,8 @@ func (w *Writer) writeMultiFile(db *models.Database) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Add fmt import if GetIDStr is enabled
|
||||
if w.config.GenerateGetIDStr {
|
||||
// Add fmt import when generated helper methods need string formatting.
|
||||
if w.needsFmtImport(templateData.Models) {
|
||||
templateData.AddImport("\"fmt\"")
|
||||
}
|
||||
|
||||
@@ -301,6 +301,26 @@ func (w *Writer) addRelationshipFields(modelData *ModelData, table *models.Table
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) needsFmtImport(models []*ModelData) bool {
|
||||
if w.config.GenerateGetIDStr {
|
||||
for _, model := range models {
|
||||
if model.PrimaryKeyField != "" && !model.PrimaryKeyIsSQL && !model.PrimaryKeyIsStr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if w.config.GenerateUpdateID {
|
||||
for _, model := range models {
|
||||
if model.PrimaryKeyField != "" && model.PrimaryKeyIsSQL && !model.PrimaryKeyIsStr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// findTable finds a table by schema and name in the database
|
||||
func (w *Writer) findTable(schemaName, tableName string, db *models.Database) *models.Table {
|
||||
for _, schema := range db.Schemas {
|
||||
|
||||
@@ -590,6 +590,116 @@ func TestTypeMapper_SQLTypeToGoType_Bun(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriter_UpdateIDTypeSafety_Bun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkType string
|
||||
expectedPK string
|
||||
expectedLine string
|
||||
forbidInt32 bool
|
||||
}{
|
||||
{"int32_pk", "int", "int32", "m.ID = int32(newid)", false},
|
||||
{"sql_int16_pk", "smallint", "resolvespec_common.SqlInt16", "m.ID.FromString(fmt.Sprintf(\"%d\", newid))", true},
|
||||
{"int64_pk", "bigint", "int64", "m.ID = int64(newid)", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
table := models.InitTable("test_table", "public")
|
||||
table.Columns["id"] = &models.Column{
|
||||
Name: "id",
|
||||
Type: tt.pkType,
|
||||
NotNull: true,
|
||||
IsPrimaryKey: true,
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
opts := &writers.WriterOptions{
|
||||
PackageName: "models",
|
||||
OutputPath: filepath.Join(tmpDir, "test.go"),
|
||||
}
|
||||
|
||||
writer := NewWriter(opts)
|
||||
err := writer.WriteTable(table)
|
||||
if err != nil {
|
||||
t.Fatalf("WriteTable failed: %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(opts.OutputPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read generated file: %v", err)
|
||||
}
|
||||
|
||||
generated := string(content)
|
||||
|
||||
if !strings.Contains(generated, tt.expectedLine) {
|
||||
t.Errorf("Expected UpdateID to include %s\nGenerated:\n%s", tt.expectedLine, generated)
|
||||
}
|
||||
|
||||
if !strings.Contains(generated, "ID "+tt.expectedPK) {
|
||||
t.Errorf("Expected generated primary key field type %s\nGenerated:\n%s", tt.expectedPK, generated)
|
||||
}
|
||||
|
||||
if tt.forbidInt32 && strings.Contains(generated, "int32(newid)") {
|
||||
t.Errorf("UpdateID should not cast to int32 for %s type\nGenerated:\n%s", tt.pkType, generated)
|
||||
}
|
||||
|
||||
if !strings.Contains(generated, "UpdateID(newid int64)") {
|
||||
t.Errorf("UpdateID should accept int64 parameter\nGenerated:\n%s", generated)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriter_StringPrimaryKeyHelpers_Bun(t *testing.T) {
|
||||
table := models.InitTable("accounts", "public")
|
||||
table.Columns["id"] = &models.Column{
|
||||
Name: "id",
|
||||
Type: "uuid",
|
||||
NotNull: true,
|
||||
IsPrimaryKey: true,
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
opts := &writers.WriterOptions{
|
||||
PackageName: "models",
|
||||
OutputPath: filepath.Join(tmpDir, "test.go"),
|
||||
}
|
||||
|
||||
writer := NewWriter(opts)
|
||||
err := writer.WriteTable(table)
|
||||
if err != nil {
|
||||
t.Fatalf("WriteTable failed: %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(opts.OutputPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read generated file: %v", err)
|
||||
}
|
||||
|
||||
generated := string(content)
|
||||
|
||||
expectations := []string{
|
||||
"resolvespec_common.SqlUUID",
|
||||
"func (m ModelPublicAccounts) GetID() string",
|
||||
"return m.ID.String()",
|
||||
"func (m ModelPublicAccounts) GetIDStr() string",
|
||||
"func (m ModelPublicAccounts) SetID(newid string)",
|
||||
"func (m *ModelPublicAccounts) UpdateID(newid string)",
|
||||
"m.ID.FromString(newid)",
|
||||
}
|
||||
|
||||
for _, expected := range expectations {
|
||||
if !strings.Contains(generated, expected) {
|
||||
t.Errorf("Generated code missing expected content: %q\nGenerated:\n%s", expected, generated)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(generated, "GetID() int64") || strings.Contains(generated, "UpdateID(newid int64)") {
|
||||
t.Errorf("String primary keys should not use int64 helper signatures\nGenerated:\n%s", generated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeMapper_BuildBunTag(t *testing.T) {
|
||||
mapper := NewTypeMapper("")
|
||||
|
||||
@@ -696,7 +806,7 @@ func TestTypeMapper_BuildBunTag(t *testing.T) {
|
||||
Type: "text[]",
|
||||
NotNull: false,
|
||||
},
|
||||
want: []string{"tags,", "type:text[],", "array,"},
|
||||
want: []string{"tags,", "type:text[],"},
|
||||
},
|
||||
{
|
||||
name: "integer array type",
|
||||
@@ -705,7 +815,7 @@ func TestTypeMapper_BuildBunTag(t *testing.T) {
|
||||
Type: "integer[]",
|
||||
NotNull: true,
|
||||
},
|
||||
want: []string{"scores,", "type:integer[],", "array,"},
|
||||
want: []string{"scores,", "type:integer[],"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -717,6 +827,30 @@ func TestTypeMapper_BuildBunTag(t *testing.T) {
|
||||
t.Errorf("BuildBunTag() = %q, missing %q", result, part)
|
||||
}
|
||||
}
|
||||
// resolvespec mode must NOT add "array" — SqlXxxArray uses sql.Scanner
|
||||
if strings.Contains(result, ",array,") || strings.HasSuffix(result, ",array,") {
|
||||
t.Errorf("BuildBunTag() = %q, must not contain 'array' in resolvespec mode", result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeMapper_BuildBunTag_StdlibArrayHasArrayTag(t *testing.T) {
|
||||
mapper := NewTypeMapper(writers.NullableTypeStdlib)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
column *models.Column
|
||||
}{
|
||||
{name: "text array", column: &models.Column{Name: "tags", Type: "text[]"}},
|
||||
{name: "integer array", column: &models.Column{Name: "scores", Type: "integer[]", NotNull: true}},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := mapper.BuildBunTag(tt.column, nil)
|
||||
if !strings.Contains(result, "array") {
|
||||
t.Errorf("BuildBunTag() = %q, expected 'array' in stdlib mode", result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package gorm
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
||||
@@ -17,17 +18,20 @@ type TemplateData struct {
|
||||
|
||||
// ModelData represents a single model/struct in the template
|
||||
type ModelData struct {
|
||||
Name string
|
||||
TableName string // schema.table format
|
||||
SchemaName string
|
||||
TableNameOnly string // just table name without schema
|
||||
Comment string
|
||||
Fields []*FieldData
|
||||
Config *MethodConfig
|
||||
PrimaryKeyField string // Name of the primary key field
|
||||
PrimaryKeyType string // Go type of the primary key field
|
||||
IDColumnName string // Name of the ID column in database
|
||||
Prefix string // 3-letter prefix
|
||||
Name string
|
||||
TableName string // schema.table format
|
||||
SchemaName string
|
||||
TableNameOnly string // just table name without schema
|
||||
Comment string
|
||||
Fields []*FieldData
|
||||
Config *MethodConfig
|
||||
PrimaryKeyField string // Name of the primary key field
|
||||
PrimaryKeyType string // Go type of the primary key field
|
||||
PrimaryKeyIsSQL bool // Whether PK uses a SQL wrapper type
|
||||
PrimaryKeyIsStr bool // Whether helper methods should use string IDs
|
||||
PrimaryKeyIDType string // Helper method GetID/SetID/UpdateID type
|
||||
IDColumnName string // Name of the ID column in database
|
||||
Prefix string // 3-letter prefix
|
||||
}
|
||||
|
||||
// FieldData represents a single field in a struct
|
||||
@@ -136,7 +140,14 @@ func NewModelData(table *models.Table, schema string, typeMapper *TypeMapper, fl
|
||||
// Sanitize column name to remove backticks
|
||||
safeName := writers.SanitizeStructTagValue(col.Name)
|
||||
model.PrimaryKeyField = SnakeCaseToPascalCase(safeName)
|
||||
model.PrimaryKeyType = typeMapper.SQLTypeToGoType(col.Type, col.NotNull)
|
||||
goType := typeMapper.SQLTypeToGoType(col.Type, col.NotNull)
|
||||
model.PrimaryKeyType = goType
|
||||
model.PrimaryKeyIsSQL = strings.Contains(goType, "sql_types.") || strings.Contains(goType, "sql.")
|
||||
model.PrimaryKeyIsStr = isStringLikePrimaryKeyType(goType)
|
||||
model.PrimaryKeyIDType = "int64"
|
||||
if model.PrimaryKeyIsStr {
|
||||
model.PrimaryKeyIDType = "string"
|
||||
}
|
||||
model.IDColumnName = safeName
|
||||
break
|
||||
}
|
||||
@@ -189,6 +200,15 @@ func formatComment(description, comment string) string {
|
||||
return comment
|
||||
}
|
||||
|
||||
func isStringLikePrimaryKeyType(goType string) bool {
|
||||
switch goType {
|
||||
case "string", "sql.NullString", "sql_types.SqlString", "sql_types.SqlUUID":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// resolveFieldNameCollision checks if a field name conflicts with generated method names
|
||||
// and adds an underscore suffix if there's a collision
|
||||
func resolveFieldNameCollision(fieldName string) string {
|
||||
|
||||
@@ -43,26 +43,56 @@ func (m {{.Name}}) SchemaName() string {
|
||||
{{end}}
|
||||
{{if and .Config.GenerateGetID .PrimaryKeyField}}
|
||||
// GetID returns the primary key value
|
||||
func (m {{.Name}}) GetID() int64 {
|
||||
func (m {{.Name}}) GetID() {{.PrimaryKeyIDType}} {
|
||||
{{if .PrimaryKeyIsSQL -}}
|
||||
{{if .PrimaryKeyIsStr -}}
|
||||
return m.{{.PrimaryKeyField}}.String()
|
||||
{{- else -}}
|
||||
return m.{{.PrimaryKeyField}}.Int64()
|
||||
{{- end}}
|
||||
{{- else -}}
|
||||
{{if .PrimaryKeyIsStr -}}
|
||||
return m.{{.PrimaryKeyField}}
|
||||
{{- else -}}
|
||||
return int64(m.{{.PrimaryKeyField}})
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
{{if and .Config.GenerateGetIDStr .PrimaryKeyField}}
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m {{.Name}}) GetIDStr() string {
|
||||
{{if .PrimaryKeyIsSQL -}}
|
||||
return m.{{.PrimaryKeyField}}.String()
|
||||
{{- else if .PrimaryKeyIsStr -}}
|
||||
return m.{{.PrimaryKeyField}}
|
||||
{{- else -}}
|
||||
return fmt.Sprintf("%d", m.{{.PrimaryKeyField}})
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
{{if and .Config.GenerateSetID .PrimaryKeyField}}
|
||||
// SetID sets the primary key value
|
||||
func (m {{.Name}}) SetID(newid int64) {
|
||||
func (m {{.Name}}) SetID(newid {{.PrimaryKeyIDType}}) {
|
||||
m.UpdateID(newid)
|
||||
}
|
||||
{{end}}
|
||||
{{if and .Config.GenerateUpdateID .PrimaryKeyField}}
|
||||
// UpdateID updates the primary key value
|
||||
func (m *{{.Name}}) UpdateID(newid int64) {
|
||||
func (m *{{.Name}}) UpdateID(newid {{.PrimaryKeyIDType}}) {
|
||||
{{if .PrimaryKeyIsSQL -}}
|
||||
{{if .PrimaryKeyIsStr -}}
|
||||
m.{{.PrimaryKeyField}}.FromString(newid)
|
||||
{{- else -}}
|
||||
m.{{.PrimaryKeyField}}.FromString(fmt.Sprintf("%d", newid))
|
||||
{{- end}}
|
||||
{{- else -}}
|
||||
{{if .PrimaryKeyIsStr -}}
|
||||
m.{{.PrimaryKeyField}} = newid
|
||||
{{- else -}}
|
||||
m.{{.PrimaryKeyField}} = {{.PrimaryKeyType}}(newid)
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
{{if and .Config.GenerateGetIDName .IDColumnName}}
|
||||
|
||||
@@ -99,8 +99,8 @@ func (w *Writer) writeSingleFile(db *models.Database) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Add fmt import if GetIDStr is enabled
|
||||
if w.config.GenerateGetIDStr {
|
||||
// Add fmt import when generated helper methods need string formatting.
|
||||
if w.needsFmtImport(templateData.Models) {
|
||||
templateData.AddImport("\"fmt\"")
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ func (w *Writer) writeMultiFile(db *models.Database) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Add fmt import if GetIDStr is enabled
|
||||
if w.config.GenerateGetIDStr {
|
||||
// Add fmt import when generated helper methods need string formatting.
|
||||
if w.needsFmtImport(templateData.Models) {
|
||||
templateData.AddImport("\"fmt\"")
|
||||
}
|
||||
|
||||
@@ -295,6 +295,26 @@ func (w *Writer) addRelationshipFields(modelData *ModelData, table *models.Table
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) needsFmtImport(models []*ModelData) bool {
|
||||
if w.config.GenerateGetIDStr {
|
||||
for _, model := range models {
|
||||
if model.PrimaryKeyField != "" && !model.PrimaryKeyIsSQL && !model.PrimaryKeyIsStr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if w.config.GenerateUpdateID {
|
||||
for _, model := range models {
|
||||
if model.PrimaryKeyField != "" && model.PrimaryKeyIsSQL && !model.PrimaryKeyIsStr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// findTable finds a table by schema and name in the database
|
||||
func (w *Writer) findTable(schemaName, tableName string, db *models.Database) *models.Table {
|
||||
for _, schema := range db.Schemas {
|
||||
|
||||
@@ -598,6 +598,55 @@ func TestWriter_UpdateIDTypeSafety(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriter_StringPrimaryKeyHelpers_Gorm(t *testing.T) {
|
||||
table := models.InitTable("accounts", "public")
|
||||
table.Columns["id"] = &models.Column{
|
||||
Name: "id",
|
||||
Type: "uuid",
|
||||
NotNull: true,
|
||||
IsPrimaryKey: true,
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
opts := &writers.WriterOptions{
|
||||
PackageName: "models",
|
||||
OutputPath: filepath.Join(tmpDir, "test.go"),
|
||||
}
|
||||
|
||||
writer := NewWriter(opts)
|
||||
err := writer.WriteTable(table)
|
||||
if err != nil {
|
||||
t.Fatalf("WriteTable failed: %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(opts.OutputPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read generated file: %v", err)
|
||||
}
|
||||
|
||||
generated := string(content)
|
||||
|
||||
expectations := []string{
|
||||
"ID string",
|
||||
"func (m ModelPublicAccounts) GetID() string",
|
||||
"return m.ID",
|
||||
"func (m ModelPublicAccounts) GetIDStr() string",
|
||||
"func (m ModelPublicAccounts) SetID(newid string)",
|
||||
"func (m *ModelPublicAccounts) UpdateID(newid string)",
|
||||
"m.ID = newid",
|
||||
}
|
||||
|
||||
for _, expected := range expectations {
|
||||
if !strings.Contains(generated, expected) {
|
||||
t.Errorf("Generated code missing expected content: %q\nGenerated:\n%s", expected, generated)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(generated, "GetID() int64") || strings.Contains(generated, "UpdateID(newid int64)") {
|
||||
t.Errorf("String primary keys should not use int64 helper signatures\nGenerated:\n%s", generated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameConverter_SnakeCaseToPascalCase(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
|
||||
@@ -31,6 +31,10 @@ type MigrationWriter struct {
|
||||
|
||||
// NewMigrationWriter creates a new templated migration writer
|
||||
func NewMigrationWriter(options *writers.WriterOptions) (*MigrationWriter, error) {
|
||||
if options == nil {
|
||||
options = &writers.WriterOptions{}
|
||||
}
|
||||
|
||||
executor, err := NewTemplateExecutor(options.FlattenSchema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create template executor: %w", err)
|
||||
@@ -44,6 +48,16 @@ func NewMigrationWriter(options *writers.WriterOptions) (*MigrationWriter, error
|
||||
|
||||
// WriteMigration generates migration scripts using templates
|
||||
func (w *MigrationWriter) WriteMigration(model *models.Database, current *models.Database) error {
|
||||
if model == nil {
|
||||
return fmt.Errorf("model database is required")
|
||||
}
|
||||
if w.options == nil {
|
||||
w.options = &writers.WriterOptions{}
|
||||
}
|
||||
if current == nil {
|
||||
current = models.InitDatabase(model.Name)
|
||||
}
|
||||
|
||||
var writer io.Writer
|
||||
var file *os.File
|
||||
var err error
|
||||
@@ -86,9 +100,16 @@ func (w *MigrationWriter) WriteMigration(model *models.Database, current *models
|
||||
|
||||
// Process each schema in the model
|
||||
for _, modelSchema := range model.Schemas {
|
||||
if modelSchema == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find corresponding schema in current database
|
||||
var currentSchema *models.Schema
|
||||
for _, cs := range current.Schemas {
|
||||
if cs == nil {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(cs.Name, modelSchema.Name) {
|
||||
currentSchema = cs
|
||||
break
|
||||
@@ -123,7 +144,11 @@ func (w *MigrationWriter) WriteMigration(model *models.Database, current *models
|
||||
// Write header
|
||||
fmt.Fprintf(w.writer, "-- PostgreSQL Migration Script\n")
|
||||
fmt.Fprintf(w.writer, "-- Generated by RelSpec\n")
|
||||
fmt.Fprintf(w.writer, "-- Source: %s -> %s\n\n", current.Name, model.Name)
|
||||
fmt.Fprintf(w.writer, "-- Source: %s -> %s\n", current.Name, model.Name)
|
||||
if w.options.ContinueOnError {
|
||||
fmt.Fprintf(w.writer, "\\set ON_ERROR_STOP off\n")
|
||||
}
|
||||
fmt.Fprintf(w.writer, "\n")
|
||||
|
||||
// Write scripts
|
||||
for _, script := range scripts {
|
||||
@@ -139,13 +164,26 @@ func (w *MigrationWriter) WriteMigration(model *models.Database, current *models
|
||||
func (w *MigrationWriter) generateSchemaScripts(model *models.Schema, current *models.Schema) ([]MigrationScript, error) {
|
||||
scripts := make([]MigrationScript, 0)
|
||||
|
||||
// Phase 1: Drop constraints and indexes that changed (Priority 11-50)
|
||||
if schemaRequiresPGTrgm(model) {
|
||||
scripts = append(scripts, MigrationScript{
|
||||
ObjectName: "extension.pg_trgm",
|
||||
ObjectType: "create extension",
|
||||
Schema: model.Name,
|
||||
Priority: 80,
|
||||
Sequence: len(scripts),
|
||||
Body: "CREATE EXTENSION IF NOT EXISTS pg_trgm;",
|
||||
})
|
||||
}
|
||||
|
||||
// Phase 1: Drop constraints and indexes that changed (Priority 5-50)
|
||||
var droppedFKs map[string]bool
|
||||
if current != nil {
|
||||
dropScripts, err := w.generateDropScripts(model, current)
|
||||
dropScripts, dropped, err := w.generateDropScripts(model, current)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate drop scripts: %w", err)
|
||||
}
|
||||
scripts = append(scripts, dropScripts...)
|
||||
droppedFKs = dropped
|
||||
}
|
||||
|
||||
// Phase 3: Create/Alter tables and columns (Priority 100-145)
|
||||
@@ -163,7 +201,7 @@ func (w *MigrationWriter) generateSchemaScripts(model *models.Schema, current *m
|
||||
scripts = append(scripts, indexScripts...)
|
||||
|
||||
// Phase 5: Create foreign keys (Priority 195)
|
||||
fkScripts, err := w.generateForeignKeyScripts(model, current)
|
||||
fkScripts, err := w.generateForeignKeyScripts(model, current, droppedFKs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate foreign key scripts: %w", err)
|
||||
}
|
||||
@@ -179,9 +217,12 @@ func (w *MigrationWriter) generateSchemaScripts(model *models.Schema, current *m
|
||||
return scripts, nil
|
||||
}
|
||||
|
||||
// generateDropScripts generates DROP scripts using templates
|
||||
func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *models.Schema) ([]MigrationScript, error) {
|
||||
// generateDropScripts generates DROP scripts using templates.
|
||||
// Returns the scripts and a set of FK constraint keys (schema.table.name) that were
|
||||
// explicitly dropped because their referenced PK was being dropped, so they can be force-recreated.
|
||||
func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *models.Schema) ([]MigrationScript, map[string]bool, error) {
|
||||
scripts := make([]MigrationScript, 0)
|
||||
droppedFKs := make(map[string]bool)
|
||||
|
||||
// Build map of model tables for quick lookup
|
||||
modelTables := make(map[string]*models.Table)
|
||||
@@ -208,6 +249,44 @@ func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *mod
|
||||
shouldDrop = true
|
||||
}
|
||||
|
||||
if shouldDrop && currentConstraint.Type == models.PrimaryKeyConstraint {
|
||||
// Drop FK constraints that depend on this PK before dropping the PK itself.
|
||||
for _, otherTable := range current.Tables {
|
||||
for fkName, fkConstraint := range otherTable.Constraints {
|
||||
if fkConstraint.Type != models.ForeignKeyConstraint {
|
||||
continue
|
||||
}
|
||||
refTable := fkConstraint.ReferencedTable
|
||||
refSchema := fkConstraint.ReferencedSchema
|
||||
if refSchema == "" {
|
||||
refSchema = current.Name
|
||||
}
|
||||
if strings.EqualFold(refTable, currentTable.Name) && strings.EqualFold(refSchema, current.Name) {
|
||||
fkKey := fmt.Sprintf("%s.%s.%s", current.Name, otherTable.Name, fkName)
|
||||
if !droppedFKs[fkKey] {
|
||||
droppedFKs[fkKey] = true
|
||||
sql, err := w.executor.ExecuteDropConstraint(DropConstraintData{
|
||||
SchemaName: current.Name,
|
||||
TableName: otherTable.Name,
|
||||
ConstraintName: fkName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
scripts = append(scripts, MigrationScript{
|
||||
ObjectName: fkKey,
|
||||
ObjectType: "drop constraint",
|
||||
Schema: current.Name,
|
||||
Priority: 5,
|
||||
Sequence: len(scripts),
|
||||
Body: sql,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shouldDrop {
|
||||
sql, err := w.executor.ExecuteDropConstraint(DropConstraintData{
|
||||
SchemaName: current.Name,
|
||||
@@ -215,7 +294,7 @@ func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *mod
|
||||
ConstraintName: constraintName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
script := MigrationScript{
|
||||
@@ -247,7 +326,7 @@ func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *mod
|
||||
IndexName: indexName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
script := MigrationScript{
|
||||
@@ -263,7 +342,7 @@ func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *mod
|
||||
}
|
||||
}
|
||||
|
||||
return scripts, nil
|
||||
return scripts, droppedFKs, nil
|
||||
}
|
||||
|
||||
// generateTableScripts generates CREATE/ALTER TABLE scripts using templates
|
||||
@@ -340,7 +419,7 @@ func (w *MigrationWriter) generateAlterTableScripts(schema *models.Schema, model
|
||||
SchemaName: schema.Name,
|
||||
TableName: modelTable.Name,
|
||||
ColumnName: modelCol.Name,
|
||||
ColumnType: pgsql.ConvertSQLType(modelCol.Type),
|
||||
ColumnType: effectiveColumnSQLType(modelCol),
|
||||
Default: defaultVal,
|
||||
NotNull: modelCol.NotNull,
|
||||
})
|
||||
@@ -359,12 +438,13 @@ func (w *MigrationWriter) generateAlterTableScripts(schema *models.Schema, model
|
||||
scripts = append(scripts, script)
|
||||
} else if !columnsEqual(modelCol, currentCol) {
|
||||
// Column exists but properties changed
|
||||
if modelCol.Type != currentCol.Type {
|
||||
if !columnTypesEqual(modelCol, currentCol) {
|
||||
sql, err := w.executor.ExecuteAlterColumnType(AlterColumnTypeData{
|
||||
SchemaName: schema.Name,
|
||||
TableName: modelTable.Name,
|
||||
ColumnName: modelCol.Name,
|
||||
NewType: pgsql.ConvertSQLType(modelCol.Type),
|
||||
NewType: effectiveAlterColumnSQLType(modelCol),
|
||||
UsingExpr: buildAlterColumnUsingExpression(modelCol.Name, effectiveAlterColumnSQLType(modelCol)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -545,12 +625,17 @@ func (w *MigrationWriter) generateIndexScripts(model *models.Schema, current *mo
|
||||
indexType = modelIndex.Type
|
||||
}
|
||||
|
||||
columnExprs := buildIndexColumnExpressions(modelTable, modelIndex, indexType)
|
||||
if len(columnExprs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
sql, err := w.executor.ExecuteCreateIndex(CreateIndexData{
|
||||
SchemaName: model.Name,
|
||||
TableName: modelTable.Name,
|
||||
IndexName: indexName,
|
||||
IndexType: indexType,
|
||||
Columns: strings.Join(modelIndex.Columns, ", "),
|
||||
Columns: strings.Join(columnExprs, ", "),
|
||||
Unique: modelIndex.Unique,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -573,8 +658,30 @@ func (w *MigrationWriter) generateIndexScripts(model *models.Schema, current *mo
|
||||
return scripts, nil
|
||||
}
|
||||
|
||||
// generateForeignKeyScripts generates ADD CONSTRAINT FOREIGN KEY scripts using templates
|
||||
func (w *MigrationWriter) generateForeignKeyScripts(model *models.Schema, current *models.Schema) ([]MigrationScript, error) {
|
||||
func buildIndexColumnExpressions(table *models.Table, index *models.Index, indexType string) []string {
|
||||
columnExprs := make([]string, 0, len(index.Columns))
|
||||
for _, colName := range index.Columns {
|
||||
colExpr := colName
|
||||
if table != nil {
|
||||
if col, ok := resolveIndexColumn(table, colName); ok && col != nil {
|
||||
colExpr = col.SQLName()
|
||||
if strings.EqualFold(indexType, "gin") {
|
||||
opClass := ginOperatorClassForColumn(col, index.Comment)
|
||||
if opClass != "" {
|
||||
colExpr = fmt.Sprintf("%s %s", col.SQLName(), opClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
columnExprs = append(columnExprs, colExpr)
|
||||
}
|
||||
return columnExprs
|
||||
}
|
||||
|
||||
// generateForeignKeyScripts generates ADD CONSTRAINT FOREIGN KEY scripts using templates.
|
||||
// forceRecreate is a set of FK constraint keys (schema.table.name) that must be recreated
|
||||
// even if unchanged, because their referenced PK was dropped and recreated.
|
||||
func (w *MigrationWriter) generateForeignKeyScripts(model *models.Schema, current *models.Schema, forceRecreate map[string]bool) ([]MigrationScript, error) {
|
||||
scripts := make([]MigrationScript, 0)
|
||||
|
||||
// Build map of current tables
|
||||
@@ -595,13 +702,16 @@ func (w *MigrationWriter) generateForeignKeyScripts(model *models.Schema, curren
|
||||
continue
|
||||
}
|
||||
|
||||
shouldCreate := true
|
||||
fkKey := fmt.Sprintf("%s.%s.%s", model.Name, modelTable.Name, constraintName)
|
||||
shouldCreate := forceRecreate[fkKey]
|
||||
|
||||
if currentTable != nil {
|
||||
if currentConstraint, exists := currentTable.Constraints[constraintName]; exists {
|
||||
if constraintsEqual(constraint, currentConstraint) {
|
||||
shouldCreate = false
|
||||
}
|
||||
if !shouldCreate {
|
||||
if currentTable == nil {
|
||||
shouldCreate = true
|
||||
} else if currentConstraint, exists := currentTable.Constraints[constraintName]; !exists {
|
||||
shouldCreate = true
|
||||
} else if !constraintsEqual(constraint, currentConstraint) {
|
||||
shouldCreate = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,11 +938,21 @@ func columnsEqual(col1, col2 *models.Column) bool {
|
||||
if col1 == nil || col2 == nil {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(col1.Type, col2.Type) &&
|
||||
return columnTypesEqual(col1, col2) &&
|
||||
col1.NotNull == col2.NotNull &&
|
||||
fmt.Sprintf("%v", col1.Default) == fmt.Sprintf("%v", col2.Default)
|
||||
}
|
||||
|
||||
func columnTypesEqual(col1, col2 *models.Column) bool {
|
||||
if col1 == nil || col2 == nil {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(
|
||||
pgsql.NormalizeEquivalentSQLType(effectiveColumnSQLType(col1)),
|
||||
pgsql.NormalizeEquivalentSQLType(effectiveColumnSQLType(col2)),
|
||||
)
|
||||
}
|
||||
|
||||
// constraintsEqual checks if two constraints are equal
|
||||
func constraintsEqual(c1, c2 *models.Constraint) bool {
|
||||
if c1 == nil || c2 == nil {
|
||||
|
||||
@@ -97,6 +97,370 @@ func TestWriteMigration_ArrayDefault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_AltersColumnTypeWhenActualTypeDiffers(t *testing.T) {
|
||||
current := models.InitDatabase("testdb")
|
||||
currentSchema := models.InitSchema("public")
|
||||
currentTable := models.InitTable("learnings", "public")
|
||||
currentDetails := models.InitColumn("details", "learnings", "public")
|
||||
currentDetails.Type = "jsonb"
|
||||
currentTable.Columns["details"] = currentDetails
|
||||
currentSchema.Tables = append(currentSchema.Tables, currentTable)
|
||||
current.Schemas = append(current.Schemas, currentSchema)
|
||||
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
modelTable := models.InitTable("learnings", "public")
|
||||
modelDetails := models.InitColumn("details", "learnings", "public")
|
||||
modelDetails.Type = "text"
|
||||
modelTable.Columns["details"] = modelDetails
|
||||
modelSchema.Tables = append(modelSchema.Tables, modelTable)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, current); err != nil {
|
||||
t.Fatalf("WriteMigration failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "ALTER TABLE public.learnings") || !strings.Contains(output, "ALTER COLUMN details TYPE text") {
|
||||
t.Fatalf("expected migration to alter mismatched column type, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, `ALTER COLUMN details TYPE text USING details::text;`) {
|
||||
t.Fatalf("expected migration type alter to include USING cast, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_UsesStorageTypeForSerialAlterStatements(t *testing.T) {
|
||||
current := models.InitDatabase("testdb")
|
||||
currentSchema := models.InitSchema("public")
|
||||
currentTable := models.InitTable("learnings", "public")
|
||||
currentID := models.InitColumn("id", "learnings", "public")
|
||||
currentID.Type = "uuid"
|
||||
currentTable.Columns["id"] = currentID
|
||||
currentSchema.Tables = append(currentSchema.Tables, currentTable)
|
||||
current.Schemas = append(current.Schemas, currentSchema)
|
||||
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
modelTable := models.InitTable("learnings", "public")
|
||||
modelID := models.InitColumn("id", "learnings", "public")
|
||||
modelID.Type = "bigserial"
|
||||
modelTable.Columns["id"] = modelID
|
||||
modelSchema.Tables = append(modelSchema.Tables, modelTable)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, current); err != nil {
|
||||
t.Fatalf("WriteMigration failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "ALTER COLUMN id TYPE bigint") {
|
||||
t.Fatalf("expected serial alter to use bigint storage type, got:\n%s", output)
|
||||
}
|
||||
if strings.Contains(output, "ALTER COLUMN id TYPE bigserial;") {
|
||||
t.Fatalf("did not expect invalid bigserial alter statement, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, `ALTER COLUMN id TYPE bigint USING id::bigint;`) {
|
||||
t.Fatalf("expected serial alter to include USING cast, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_ArrayAlterIncludesUsingCast(t *testing.T) {
|
||||
current := models.InitDatabase("testdb")
|
||||
currentSchema := models.InitSchema("public")
|
||||
currentTable := models.InitTable("learnings", "public")
|
||||
currentTags := models.InitColumn("tags", "learnings", "public")
|
||||
currentTags.Type = "text"
|
||||
currentTable.Columns["tags"] = currentTags
|
||||
currentSchema.Tables = append(currentSchema.Tables, currentTable)
|
||||
current.Schemas = append(current.Schemas, currentSchema)
|
||||
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
modelTable := models.InitTable("learnings", "public")
|
||||
modelTags := models.InitColumn("tags", "learnings", "public")
|
||||
modelTags.Type = "text[]"
|
||||
modelTable.Columns["tags"] = modelTags
|
||||
modelSchema.Tables = append(modelSchema.Tables, modelTable)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, current); err != nil {
|
||||
t.Fatalf("WriteMigration failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, `ALTER COLUMN tags TYPE text[] USING tags::text[];`) {
|
||||
t.Fatalf("expected array alter to include USING cast, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_DoesNotAlterEquivalentNormalizedColumnType(t *testing.T) {
|
||||
current := models.InitDatabase("testdb")
|
||||
currentSchema := models.InitSchema("public")
|
||||
currentTable := models.InitTable("users", "public")
|
||||
currentEmail := models.InitColumn("email", "users", "public")
|
||||
currentEmail.Type = "character varying"
|
||||
currentEmail.Length = 255
|
||||
currentTable.Columns["email"] = currentEmail
|
||||
currentSchema.Tables = append(currentSchema.Tables, currentTable)
|
||||
current.Schemas = append(current.Schemas, currentSchema)
|
||||
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
modelTable := models.InitTable("users", "public")
|
||||
modelEmail := models.InitColumn("email", "users", "public")
|
||||
modelEmail.Type = "varchar(255)"
|
||||
modelTable.Columns["email"] = modelEmail
|
||||
modelSchema.Tables = append(modelSchema.Tables, modelTable)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, current); err != nil {
|
||||
t.Fatalf("WriteMigration failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if strings.Contains(output, "ALTER COLUMN email TYPE") {
|
||||
t.Fatalf("did not expect alter type for equivalent normalized types, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_GinIndexOnTextUsesTrigramOperatorClass(t *testing.T) {
|
||||
current := models.InitDatabase("testdb")
|
||||
currentSchema := models.InitSchema("public")
|
||||
current.Schemas = append(current.Schemas, currentSchema)
|
||||
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("articles", "public")
|
||||
titleCol := models.InitColumn("title", "articles", "public")
|
||||
titleCol.Type = "text"
|
||||
table.Columns["title"] = titleCol
|
||||
|
||||
index := &models.Index{
|
||||
Name: "idx_articles_title_gin",
|
||||
Type: "gin",
|
||||
Columns: []string{"title"},
|
||||
}
|
||||
table.Indexes[index.Name] = index
|
||||
|
||||
modelSchema.Tables = append(modelSchema.Tables, table)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, current); err != nil {
|
||||
t.Fatalf("WriteMigration failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "CREATE EXTENSION IF NOT EXISTS pg_trgm;") {
|
||||
t.Fatalf("expected trigram extension for text GIN migration index, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "USING gin (title gin_trgm_ops)") {
|
||||
t.Fatalf("expected GIN text index to include gin_trgm_ops, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_GinIndexOnQuotedTextColumnUsesTrigramOperatorClass(t *testing.T) {
|
||||
current := models.InitDatabase("testdb")
|
||||
currentSchema := models.InitSchema("public")
|
||||
current.Schemas = append(current.Schemas, currentSchema)
|
||||
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("agent_personas", "public")
|
||||
nameCol := models.InitColumn("name", "agent_personas", "public")
|
||||
nameCol.Type = "text"
|
||||
table.Columns["name"] = nameCol
|
||||
|
||||
index := &models.Index{
|
||||
Name: "idx_agent_personas_name_gin",
|
||||
Type: "gin",
|
||||
Columns: []string{`"name"`},
|
||||
}
|
||||
table.Indexes[index.Name] = index
|
||||
|
||||
modelSchema.Tables = append(modelSchema.Tables, table)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, current); err != nil {
|
||||
t.Fatalf("WriteMigration failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "USING gin (name gin_trgm_ops)") {
|
||||
t.Fatalf("expected quoted text column GIN index to include gin_trgm_ops, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_GinIndexOnTextArrayDoesNotUseTrigramOperatorClass(t *testing.T) {
|
||||
current := models.InitDatabase("testdb")
|
||||
currentSchema := models.InitSchema("public")
|
||||
current.Schemas = append(current.Schemas, currentSchema)
|
||||
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("plans", "public")
|
||||
tagsCol := models.InitColumn("tags", "plans", "public")
|
||||
tagsCol.Type = "text[]"
|
||||
table.Columns["tags"] = tagsCol
|
||||
|
||||
index := &models.Index{
|
||||
Name: "idx_plans_tags",
|
||||
Type: "gin",
|
||||
Columns: []string{"tags"},
|
||||
}
|
||||
table.Indexes[index.Name] = index
|
||||
|
||||
modelSchema.Tables = append(modelSchema.Tables, table)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, current); err != nil {
|
||||
t.Fatalf("WriteMigration failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "USING gin (tags array_ops)") {
|
||||
t.Fatalf("expected GIN array index with array_ops, got:\n%s", output)
|
||||
}
|
||||
if strings.Contains(output, "gin_trgm_ops") {
|
||||
t.Fatalf("did not expect gin_trgm_ops for text[] migration index, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_GinIndexOnJSONBUsesJSONBOperatorClass(t *testing.T) {
|
||||
current := models.InitDatabase("testdb")
|
||||
currentSchema := models.InitSchema("public")
|
||||
current.Schemas = append(current.Schemas, currentSchema)
|
||||
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("learnings", "public")
|
||||
detailsCol := models.InitColumn("details", "learnings", "public")
|
||||
detailsCol.Type = "jsonb"
|
||||
table.Columns["details"] = detailsCol
|
||||
|
||||
index := &models.Index{
|
||||
Name: "idx_learnings_details",
|
||||
Type: "gin",
|
||||
Columns: []string{"details"},
|
||||
}
|
||||
table.Indexes[index.Name] = index
|
||||
|
||||
modelSchema.Tables = append(modelSchema.Tables, table)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, current); err != nil {
|
||||
t.Fatalf("WriteMigration failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "USING gin (details jsonb_ops)") {
|
||||
t.Fatalf("expected GIN jsonb index to include jsonb_ops, got:\n%s", output)
|
||||
}
|
||||
if strings.Contains(output, "gin_trgm_ops") {
|
||||
t.Fatalf("did not expect gin_trgm_ops for jsonb migration index, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_GinIndexOnJSONBIgnoresIncompatibleTrigramOperatorClass(t *testing.T) {
|
||||
current := models.InitDatabase("testdb")
|
||||
currentSchema := models.InitSchema("public")
|
||||
current.Schemas = append(current.Schemas, currentSchema)
|
||||
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("learnings", "public")
|
||||
detailsCol := models.InitColumn("details", "learnings", "public")
|
||||
detailsCol.Type = "jsonb"
|
||||
table.Columns["details"] = detailsCol
|
||||
|
||||
index := &models.Index{
|
||||
Name: "idx_learnings_details",
|
||||
Type: "gin",
|
||||
Columns: []string{"details"},
|
||||
Comment: "gin_trgm_ops",
|
||||
}
|
||||
table.Indexes[index.Name] = index
|
||||
|
||||
modelSchema.Tables = append(modelSchema.Tables, table)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(&writers.WriterOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, current); err != nil {
|
||||
t.Fatalf("WriteMigration failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "USING gin (details jsonb_ops)") {
|
||||
t.Fatalf("expected incompatible trigram hint on jsonb to fall back to jsonb_ops, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_WithAudit(t *testing.T) {
|
||||
// Current database (empty)
|
||||
current := models.InitDatabase("testdb")
|
||||
@@ -322,3 +686,46 @@ func TestWriteMigration_NumericConstraintNames(t *testing.T) {
|
||||
t.Error("Migration missing FOREIGN KEY")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMigrationWriter_NilOptions(t *testing.T) {
|
||||
writer, err := NewMigrationWriter(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("NewMigrationWriter(nil) returned error: %v", err)
|
||||
}
|
||||
if writer == nil {
|
||||
t.Fatal("expected writer instance")
|
||||
}
|
||||
if writer.options == nil {
|
||||
t.Fatal("expected default writer options to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteMigration_NilCurrentTreatsDatabaseAsEmpty(t *testing.T) {
|
||||
model := models.InitDatabase("testdb")
|
||||
modelSchema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("users", "public")
|
||||
idCol := models.InitColumn("id", "users", "public")
|
||||
idCol.Type = "integer"
|
||||
idCol.NotNull = true
|
||||
table.Columns["id"] = idCol
|
||||
|
||||
modelSchema.Tables = append(modelSchema.Tables, table)
|
||||
model.Schemas = append(model.Schemas, modelSchema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer, err := NewMigrationWriter(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create writer: %v", err)
|
||||
}
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteMigration(model, nil); err != nil {
|
||||
t.Fatalf("WriteMigration with nil current failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "CREATE TABLE") {
|
||||
t.Fatalf("expected CREATE TABLE in migration output, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/pgsql"
|
||||
)
|
||||
|
||||
// TemplateFunctions returns a map of custom template functions
|
||||
@@ -162,24 +164,12 @@ func quoteIdent(s string) string {
|
||||
|
||||
// Type conversion functions
|
||||
|
||||
// goTypeToSQL converts Go type to PostgreSQL type
|
||||
// goTypeToSQL converts Go type to PostgreSQL type using the shared pgsql type map.
|
||||
func goTypeToSQL(goType string) string {
|
||||
typeMap := map[string]string{
|
||||
"string": "text",
|
||||
"int": "integer",
|
||||
"int32": "integer",
|
||||
"int64": "bigint",
|
||||
"float32": "real",
|
||||
"float64": "double precision",
|
||||
"bool": "boolean",
|
||||
"time.Time": "timestamp",
|
||||
"[]byte": "bytea",
|
||||
}
|
||||
|
||||
if sqlType, ok := typeMap[goType]; ok {
|
||||
if sqlType, ok := pgsql.GoToPGSQLTypes[goType]; ok {
|
||||
return sqlType
|
||||
}
|
||||
return "text" // Default
|
||||
return "text"
|
||||
}
|
||||
|
||||
// sqlTypeToGo converts PostgreSQL type to Go type
|
||||
|
||||
@@ -95,6 +95,16 @@ type AlterColumnTypeData struct {
|
||||
TableName string
|
||||
ColumnName string
|
||||
NewType string
|
||||
UsingExpr string
|
||||
}
|
||||
|
||||
type AlterColumnTypeWithCheckData struct {
|
||||
SchemaName string
|
||||
TableName string
|
||||
ColumnName string
|
||||
NewType string
|
||||
EquivalentTypes string
|
||||
UsingExpr string
|
||||
}
|
||||
|
||||
// AlterColumnDefaultData contains data for alter column default template
|
||||
@@ -267,6 +277,7 @@ type CreatePrimaryKeyWithAutoGenCheckData struct {
|
||||
ConstraintName string
|
||||
AutoGenNames string // Comma-separated list of names like "'name1', 'name2'"
|
||||
Columns string
|
||||
ColumnNames string // Comma-separated list of quoted column names like "'id', 'tenant_id'"
|
||||
}
|
||||
|
||||
// Execute methods for each template
|
||||
@@ -301,6 +312,15 @@ func (te *TemplateExecutor) ExecuteAlterColumnType(data AlterColumnTypeData) (st
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (te *TemplateExecutor) ExecuteAlterColumnTypeWithCheck(data AlterColumnTypeWithCheckData) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
err := te.templates.ExecuteTemplate(&buf, "alter_column_type_with_check.tmpl", data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute alter_column_type_with_check template: %w", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// ExecuteAlterColumnDefault executes the alter column default template
|
||||
func (te *TemplateExecutor) ExecuteAlterColumnDefault(data AlterColumnDefaultData) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
ALTER TABLE {{qual_table .SchemaName .TableName}}
|
||||
ALTER COLUMN {{quote_ident .ColumnName}} TYPE {{.NewType}};
|
||||
ALTER COLUMN {{quote_ident .ColumnName}} TYPE {{.NewType}}{{if .UsingExpr}} USING {{.UsingExpr}}{{end}};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
DO $$
|
||||
DECLARE
|
||||
current_type text;
|
||||
BEGIN
|
||||
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
||||
INTO current_type
|
||||
FROM pg_attribute a
|
||||
JOIN pg_class t ON t.oid = a.attrelid
|
||||
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||
WHERE n.nspname = '{{.SchemaName}}'
|
||||
AND t.relname = '{{.TableName}}'
|
||||
AND a.attname = '{{.ColumnName}}'
|
||||
AND a.attnum > 0
|
||||
AND NOT a.attisdropped;
|
||||
|
||||
IF current_type IS NOT NULL
|
||||
AND current_type <> ALL(ARRAY[{{.EquivalentTypes}}]) THEN
|
||||
ALTER TABLE {{qual_table .SchemaName .TableName}}
|
||||
ALTER COLUMN {{quote_ident .ColumnName}} TYPE {{.NewType}}{{if .UsingExpr}} USING {{.UsingExpr}}{{end}};
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
@@ -1,26 +1,42 @@
|
||||
DO $$
|
||||
DECLARE
|
||||
auto_pk_name text;
|
||||
current_pk_name text;
|
||||
current_pk_matches boolean := false;
|
||||
BEGIN
|
||||
-- Drop auto-generated primary key if it exists
|
||||
SELECT constraint_name INTO auto_pk_name
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_schema = '{{.SchemaName}}'
|
||||
AND table_name = '{{.TableName}}'
|
||||
AND constraint_type = 'PRIMARY KEY'
|
||||
AND constraint_name IN ({{.AutoGenNames}});
|
||||
SELECT tc.constraint_name,
|
||||
COALESCE(
|
||||
ARRAY(
|
||||
SELECT a.attname::text
|
||||
FROM pg_constraint c
|
||||
JOIN pg_class t ON t.oid = c.conrelid
|
||||
JOIN pg_namespace n ON n.oid = t.relnamespace
|
||||
JOIN unnest(c.conkey) WITH ORDINALITY AS cols(attnum, ord)
|
||||
ON TRUE
|
||||
JOIN pg_attribute a
|
||||
ON a.attrelid = t.oid
|
||||
AND a.attnum = cols.attnum
|
||||
WHERE c.contype = 'p'
|
||||
AND n.nspname = '{{.SchemaName}}'
|
||||
AND t.relname = '{{.TableName}}'
|
||||
ORDER BY cols.ord
|
||||
),
|
||||
ARRAY[]::text[]
|
||||
) = ARRAY[{{.ColumnNames}}]
|
||||
INTO current_pk_name, current_pk_matches
|
||||
FROM information_schema.table_constraints tc
|
||||
WHERE tc.table_schema = '{{.SchemaName}}'
|
||||
AND tc.table_name = '{{.TableName}}'
|
||||
AND tc.constraint_type = 'PRIMARY KEY';
|
||||
|
||||
IF auto_pk_name IS NOT NULL THEN
|
||||
EXECUTE 'ALTER TABLE {{qual_table .SchemaName .TableName}} DROP CONSTRAINT ' || quote_ident(auto_pk_name);
|
||||
IF current_pk_name IS NOT NULL
|
||||
AND NOT current_pk_matches
|
||||
AND current_pk_name IN ({{.AutoGenNames}}) THEN
|
||||
EXECUTE 'ALTER TABLE {{qual_table .SchemaName .TableName}} DROP CONSTRAINT ' || quote_ident(current_pk_name) || ' CASCADE';
|
||||
END IF;
|
||||
|
||||
-- Add named primary key if it doesn't exist
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE table_schema = '{{.SchemaName}}'
|
||||
AND table_name = '{{.TableName}}'
|
||||
AND constraint_name = '{{.ConstraintName}}'
|
||||
) THEN
|
||||
-- Add the desired primary key only when no matching primary key already exists.
|
||||
IF current_pk_name IS NULL
|
||||
OR (NOT current_pk_matches AND current_pk_name IN ({{.AutoGenNames}})) THEN
|
||||
ALTER TABLE {{qual_table .SchemaName .TableName}} ADD CONSTRAINT {{quote_ident .ConstraintName}} PRIMARY KEY ({{.Columns}});
|
||||
END IF;
|
||||
END;
|
||||
|
||||
+428
-51
@@ -99,7 +99,11 @@ func (w *Writer) WriteDatabase(db *models.Database) error {
|
||||
// Write header comment
|
||||
fmt.Fprintf(w.writer, "-- PostgreSQL Database Schema\n")
|
||||
fmt.Fprintf(w.writer, "-- Database: %s\n", db.Name)
|
||||
fmt.Fprintf(w.writer, "-- Generated by RelSpec\n\n")
|
||||
fmt.Fprintf(w.writer, "-- Generated by RelSpec\n")
|
||||
if w.options.ContinueOnError {
|
||||
fmt.Fprintf(w.writer, "\\set ON_ERROR_STOP off\n")
|
||||
}
|
||||
fmt.Fprintf(w.writer, "\n")
|
||||
|
||||
// Process each schema in the database
|
||||
for _, schema := range db.Schemas {
|
||||
@@ -143,6 +147,10 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
statements = append(statements, fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", schema.SQLName()))
|
||||
}
|
||||
|
||||
if schemaRequiresPGTrgm(schema) {
|
||||
statements = append(statements, `CREATE EXTENSION IF NOT EXISTS pg_trgm`)
|
||||
}
|
||||
|
||||
// Phase 2: Create sequences
|
||||
for _, table := range schema.Tables {
|
||||
pk := table.GetPrimaryKey()
|
||||
@@ -181,6 +189,12 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
}
|
||||
statements = append(statements, addColStmts...)
|
||||
|
||||
alterTypeStmts, err := w.GenerateAlterColumnTypeStatements(schema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate alter column type statements: %w", err)
|
||||
}
|
||||
statements = append(statements, alterTypeStmts...)
|
||||
|
||||
// Phase 4: Primary keys
|
||||
for _, table := range schema.Tables {
|
||||
// First check for explicit PrimaryKeyConstraint
|
||||
@@ -228,6 +242,7 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
ConstraintName: pkName,
|
||||
AutoGenNames: formatStringList(autoGenPKNames),
|
||||
Columns: strings.Join(pkColumns, ", "),
|
||||
ColumnNames: formatStringList(pkColumns),
|
||||
}
|
||||
|
||||
stmt, err := w.executor.ExecuteCreatePrimaryKeyWithAutoGenCheck(data)
|
||||
@@ -260,14 +275,11 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
columnExprs := make([]string, 0, len(index.Columns))
|
||||
for _, colName := range index.Columns {
|
||||
colExpr := colName
|
||||
if col, ok := table.Columns[colName]; ok {
|
||||
// For GIN indexes on text columns, add operator class
|
||||
if strings.EqualFold(indexType, "gin") && isTextType(col.Type) {
|
||||
opClass := extractOperatorClass(index.Comment)
|
||||
if opClass == "" {
|
||||
opClass = "gin_trgm_ops"
|
||||
if col, ok := resolveIndexColumn(table, colName); ok {
|
||||
if strings.EqualFold(indexType, "gin") {
|
||||
if opClass := ginOperatorClassForColumn(col, index.Comment); opClass != "" {
|
||||
colExpr = fmt.Sprintf("%s %s", colName, opClass)
|
||||
}
|
||||
colExpr = fmt.Sprintf("%s %s", colName, opClass)
|
||||
}
|
||||
}
|
||||
columnExprs = append(columnExprs, colExpr)
|
||||
@@ -436,6 +448,33 @@ func (w *Writer) GenerateAddColumnStatements(schema *models.Schema) ([]string, e
|
||||
return statements, nil
|
||||
}
|
||||
|
||||
func (w *Writer) GenerateAlterColumnTypeStatements(schema *models.Schema) ([]string, error) {
|
||||
statements := []string{}
|
||||
|
||||
statements = append(statements, fmt.Sprintf("-- Alter column types for schema: %s", schema.Name))
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
columns := getSortedColumns(table.Columns)
|
||||
for _, col := range columns {
|
||||
targetType := effectiveAlterColumnSQLType(col)
|
||||
stmt, err := w.executor.ExecuteAlterColumnTypeWithCheck(AlterColumnTypeWithCheckData{
|
||||
SchemaName: schema.Name,
|
||||
TableName: table.Name,
|
||||
ColumnName: col.Name,
|
||||
NewType: targetType,
|
||||
EquivalentTypes: equivalentTypeListSQL(targetType),
|
||||
UsingExpr: buildAlterColumnUsingExpression(col.Name, targetType),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate alter column type for %s.%s.%s: %w", schema.Name, table.Name, col.Name, err)
|
||||
}
|
||||
statements = append(statements, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
return statements, nil
|
||||
}
|
||||
|
||||
// GenerateAddColumnsForDatabase generates ALTER TABLE ADD COLUMN statements for the entire database
|
||||
func (w *Writer) GenerateAddColumnsForDatabase(db *models.Database) ([]string, error) {
|
||||
statements := []string{}
|
||||
@@ -488,31 +527,7 @@ func (w *Writer) generateCreateTableStatement(schema *models.Schema, table *mode
|
||||
func (w *Writer) generateColumnDefinition(col *models.Column) string {
|
||||
parts := []string{col.SQLName()}
|
||||
|
||||
// Type with length/precision - convert to valid PostgreSQL type
|
||||
baseType := pgsql.ConvertSQLType(col.Type)
|
||||
typeStr := baseType
|
||||
hasExplicitTypeModifier := pgsql.HasExplicitTypeModifier(baseType)
|
||||
|
||||
// Only add size specifiers for types that support them
|
||||
if !hasExplicitTypeModifier && col.Length > 0 && col.Precision == 0 {
|
||||
if pgsql.SupportsLength(baseType) {
|
||||
typeStr = fmt.Sprintf("%s(%d)", baseType, col.Length)
|
||||
} else if isTextTypeWithoutLength(baseType) {
|
||||
// Convert text with length to varchar
|
||||
typeStr = fmt.Sprintf("varchar(%d)", col.Length)
|
||||
}
|
||||
// For types that don't support length (integer, bigint, etc.), ignore the length
|
||||
} else if !hasExplicitTypeModifier && col.Precision > 0 {
|
||||
if pgsql.SupportsPrecision(baseType) {
|
||||
if col.Scale > 0 {
|
||||
typeStr = fmt.Sprintf("%s(%d,%d)", baseType, col.Precision, col.Scale)
|
||||
} else {
|
||||
typeStr = fmt.Sprintf("%s(%d)", baseType, col.Precision)
|
||||
}
|
||||
}
|
||||
// For types that don't support precision, ignore it
|
||||
}
|
||||
parts = append(parts, typeStr)
|
||||
parts = append(parts, effectiveColumnSQLType(col))
|
||||
|
||||
// NOT NULL
|
||||
if col.NotNull {
|
||||
@@ -534,6 +549,64 @@ func (w *Writer) generateColumnDefinition(col *models.Column) string {
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func effectiveColumnSQLType(col *models.Column) string {
|
||||
if col == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
baseType := pgsql.ConvertSQLType(col.Type)
|
||||
typeStr := baseType
|
||||
hasExplicitTypeModifier := pgsql.HasExplicitTypeModifier(baseType)
|
||||
|
||||
if !hasExplicitTypeModifier && col.Length > 0 && col.Precision == 0 {
|
||||
if pgsql.SupportsLength(baseType) {
|
||||
typeStr = fmt.Sprintf("%s(%d)", baseType, col.Length)
|
||||
} else if isTextTypeWithoutLength(baseType) {
|
||||
typeStr = fmt.Sprintf("varchar(%d)", col.Length)
|
||||
}
|
||||
} else if !hasExplicitTypeModifier && col.Precision > 0 {
|
||||
if pgsql.SupportsPrecision(baseType) {
|
||||
if col.Scale > 0 {
|
||||
typeStr = fmt.Sprintf("%s(%d,%d)", baseType, col.Precision, col.Scale)
|
||||
} else {
|
||||
typeStr = fmt.Sprintf("%s(%d)", baseType, col.Precision)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return typeStr
|
||||
}
|
||||
|
||||
func effectiveAlterColumnSQLType(col *models.Column) string {
|
||||
typeStr := effectiveColumnSQLType(col)
|
||||
switch strings.ToLower(strings.TrimSpace(typeStr)) {
|
||||
case "smallserial":
|
||||
return "smallint"
|
||||
case "serial":
|
||||
return "integer"
|
||||
case "bigserial":
|
||||
return "bigint"
|
||||
default:
|
||||
return typeStr
|
||||
}
|
||||
}
|
||||
|
||||
func buildAlterColumnUsingExpression(columnName, targetType string) string {
|
||||
if strings.TrimSpace(columnName) == "" || strings.TrimSpace(targetType) == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s::%s", quoteIdent(columnName), targetType)
|
||||
}
|
||||
|
||||
func equivalentTypeListSQL(sqlType string) string {
|
||||
variants := pgsql.EquivalentSQLTypeVariants(sqlType)
|
||||
quoted := make([]string, 0, len(variants))
|
||||
for _, variant := range variants {
|
||||
quoted = append(quoted, fmt.Sprintf("'%s'", escapeQuote(variant)))
|
||||
}
|
||||
return strings.Join(quoted, ", ")
|
||||
}
|
||||
|
||||
// WriteSchema writes a single schema and all its tables
|
||||
func (w *Writer) WriteSchema(schema *models.Schema) error {
|
||||
if w.writer == nil {
|
||||
@@ -545,6 +618,10 @@ func (w *Writer) WriteSchema(schema *models.Schema) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeRequiredExtensions(schema); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Phase 2: Create sequences (priority 80)
|
||||
if err := w.writeSequences(schema); err != nil {
|
||||
return err
|
||||
@@ -560,6 +637,10 @@ func (w *Writer) WriteSchema(schema *models.Schema) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeAlterColumnTypes(schema); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Phase 4: Create primary keys (priority 160)
|
||||
if err := w.writePrimaryKeys(schema); err != nil {
|
||||
return err
|
||||
@@ -660,6 +741,16 @@ func (w *Writer) writeCreateSchema(schema *models.Schema) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) writeRequiredExtensions(schema *models.Schema) error {
|
||||
if !schemaRequiresPGTrgm(schema) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintln(w.writer, "CREATE EXTENSION IF NOT EXISTS pg_trgm;")
|
||||
fmt.Fprintln(w.writer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeSequences generates CREATE SEQUENCE statements for identity columns
|
||||
func (w *Writer) writeSequences(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Sequences for schema: %s\n", schema.Name)
|
||||
@@ -753,6 +844,21 @@ func (w *Writer) writeAddColumns(schema *models.Schema) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) writeAlterColumnTypes(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Alter column types for schema: %s\n", schema.Name)
|
||||
|
||||
statements, err := w.GenerateAlterColumnTypeStatements(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stmt := range statements[1:] {
|
||||
fmt.Fprint(w.writer, stmt)
|
||||
fmt.Fprint(w.writer, "\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writePrimaryKeys generates ALTER TABLE statements for primary keys
|
||||
func (w *Writer) writePrimaryKeys(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Primary keys for schema: %s\n", schema.Name)
|
||||
@@ -806,6 +912,7 @@ func (w *Writer) writePrimaryKeys(schema *models.Schema) error {
|
||||
ConstraintName: pkName,
|
||||
AutoGenNames: formatStringList(autoGenPKNames),
|
||||
Columns: strings.Join(columnNames, ", "),
|
||||
ColumnNames: formatStringList(columnNames),
|
||||
}
|
||||
|
||||
sql, err := w.executor.ExecuteCreatePrimaryKeyWithAutoGenCheck(data)
|
||||
@@ -853,15 +960,13 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
||||
// Build column list with operator class support for GIN indexes
|
||||
columnExprs := make([]string, 0, len(index.Columns))
|
||||
for _, colName := range index.Columns {
|
||||
if col, ok := table.Columns[colName]; ok {
|
||||
if col, ok := resolveIndexColumn(table, colName); ok {
|
||||
colExpr := col.SQLName()
|
||||
// For GIN indexes on text columns, add operator class
|
||||
if strings.EqualFold(index.Type, "gin") && isTextType(col.Type) {
|
||||
opClass := extractOperatorClass(index.Comment)
|
||||
if opClass == "" {
|
||||
opClass = "gin_trgm_ops"
|
||||
if strings.EqualFold(index.Type, "gin") {
|
||||
opClass := ginOperatorClassForColumn(col, index.Comment)
|
||||
if opClass != "" {
|
||||
colExpr = fmt.Sprintf("%s %s", col.SQLName(), opClass)
|
||||
}
|
||||
colExpr = fmt.Sprintf("%s %s", col.SQLName(), opClass)
|
||||
}
|
||||
columnExprs = append(columnExprs, colExpr)
|
||||
}
|
||||
@@ -1248,23 +1353,126 @@ func isIntegerType(colType string) bool {
|
||||
}
|
||||
|
||||
// isTextType checks if a column type is a text type (for GIN index operator class)
|
||||
func isTextType(colType string) bool {
|
||||
textTypes := []string{"text", "varchar", "character varying", "char", "character", "string"}
|
||||
lowerType := strings.ToLower(colType)
|
||||
if strings.HasSuffix(lowerType, "[]") {
|
||||
// func isTextType(colType string) bool {
|
||||
// textTypes := []string{"text", "varchar", "character varying", "char", "character", "string"}
|
||||
// lowerType := strings.ToLower(colType)
|
||||
// if strings.HasSuffix(lowerType, "[]") {
|
||||
// return false
|
||||
// }
|
||||
// for _, t := range textTypes {
|
||||
// if strings.HasPrefix(lowerType, t) {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
|
||||
// isTextTypeWithoutLength checks if type is text (which should convert to varchar when length is specified)
|
||||
func isTextTypeWithoutLength(colType string) bool {
|
||||
return strings.EqualFold(colType, "text")
|
||||
}
|
||||
|
||||
func ginOperatorClassForColumn(col *models.Column, comment string) string {
|
||||
if col == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
sqlType := effectiveColumnSQLType(col)
|
||||
baseType := pgsql.CanonicalizeBaseType(pgsql.ExtractBaseTypeLower(sqlType))
|
||||
isArray := pgsql.IsArrayType(sqlType)
|
||||
requested := extractOperatorClass(comment)
|
||||
|
||||
if requested != "" && ginOperatorClassCompatible(baseType, isArray, requested) {
|
||||
return requested
|
||||
}
|
||||
|
||||
if isArray {
|
||||
return "array_ops"
|
||||
}
|
||||
|
||||
switch {
|
||||
case isTextGinBaseType(baseType):
|
||||
return "gin_trgm_ops"
|
||||
case baseType == "jsonb":
|
||||
return "jsonb_ops"
|
||||
default:
|
||||
return requested
|
||||
}
|
||||
}
|
||||
|
||||
func ginOperatorClassCompatible(baseType string, isArray bool, opClass string) bool {
|
||||
switch opClass {
|
||||
case "gin_trgm_ops", "gin_bigm_ops":
|
||||
return !isArray && isTextGinBaseType(baseType)
|
||||
case "jsonb_ops", "jsonb_path_ops":
|
||||
return !isArray && baseType == "jsonb"
|
||||
case "array_ops":
|
||||
return isArray
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func isTextGinBaseType(baseType string) bool {
|
||||
switch baseType {
|
||||
case "text", "varchar", "character varying", "char", "character", "string", "citext", "bpchar":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
for _, t := range textTypes {
|
||||
if strings.HasPrefix(lowerType, t) {
|
||||
return true
|
||||
}
|
||||
|
||||
func schemaRequiresPGTrgm(schema *models.Schema) bool {
|
||||
if schema == nil {
|
||||
return false
|
||||
}
|
||||
for _, table := range schema.Tables {
|
||||
if table == nil {
|
||||
continue
|
||||
}
|
||||
for _, index := range table.Indexes {
|
||||
if index == nil || !strings.EqualFold(index.Type, "gin") {
|
||||
continue
|
||||
}
|
||||
for _, colName := range index.Columns {
|
||||
col, ok := resolveIndexColumn(table, colName)
|
||||
if !ok || col == nil {
|
||||
continue
|
||||
}
|
||||
if ginOperatorClassForColumn(col, index.Comment) == "gin_trgm_ops" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isTextTypeWithoutLength checks if type is text (which should convert to varchar when length is specified)
|
||||
func isTextTypeWithoutLength(colType string) bool {
|
||||
return strings.EqualFold(colType, "text")
|
||||
func resolveIndexColumn(table *models.Table, colName string) (*models.Column, bool) {
|
||||
if table == nil {
|
||||
return nil, false
|
||||
}
|
||||
if col, ok := table.Columns[colName]; ok && col != nil {
|
||||
return col, true
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.Trim(colName, `"`))
|
||||
for key, col := range table.Columns {
|
||||
if col == nil {
|
||||
continue
|
||||
}
|
||||
if strings.ToLower(strings.Trim(key, `"`)) == normalized {
|
||||
return col, true
|
||||
}
|
||||
if strings.ToLower(strings.Trim(col.Name, `"`)) == normalized {
|
||||
return col, true
|
||||
}
|
||||
if strings.ToLower(strings.Trim(col.SQLName(), `"`)) == normalized {
|
||||
return col, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// formatStringList formats a list of strings as a SQL-safe comma-separated quoted list
|
||||
@@ -1385,7 +1593,12 @@ func (w *Writer) executeDatabaseSQL(db *models.Database, connString string) erro
|
||||
}
|
||||
|
||||
stmtType := detectStatementType(stmtTrimmed)
|
||||
fmt.Fprintf(os.Stderr, "Executing statement %d/%d [%s]...\n", i+1, len(statements), stmtType)
|
||||
stmtCtx := extractStatementContext(stmtTrimmed)
|
||||
if stmtCtx != "" {
|
||||
fmt.Fprintf(os.Stderr, "Executing statement %d/%d [%s] %s...\n", i+1, len(statements), stmtType, stmtCtx)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Executing statement %d/%d [%s]...\n", i+1, len(statements), stmtType)
|
||||
}
|
||||
|
||||
_, execErr := conn.Exec(ctx, stmt)
|
||||
if execErr != nil {
|
||||
@@ -1520,6 +1733,170 @@ func getCurrentTimestamp() string {
|
||||
return time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
// extractStatementContext returns a human-readable schema/table/column context string for a SQL statement.
|
||||
func extractStatementContext(stmt string) string {
|
||||
upper := strings.ToUpper(stmt)
|
||||
|
||||
// DO $$ blocks: extract identifiers from information_schema WHERE clauses
|
||||
if strings.HasPrefix(upper, "DO $$") || strings.HasPrefix(upper, "DO $") {
|
||||
schema := extractSQLStringValue(stmt, "table_schema")
|
||||
table := extractSQLStringValue(stmt, "table_name")
|
||||
column := extractSQLStringValue(stmt, "column_name")
|
||||
constraint := extractSQLStringValue(stmt, "constraint_name")
|
||||
return buildStmtContext(schema, table, column, constraint)
|
||||
}
|
||||
|
||||
// ALTER TABLE [schema.]table ...
|
||||
if strings.HasPrefix(upper, "ALTER TABLE") {
|
||||
schema, table := parseQualifiedIdent(strings.TrimSpace(stmt[11:]))
|
||||
if strings.Contains(upper, "ADD COLUMN") {
|
||||
col := firstIdentAfterKeyword(stmt, upper, "ADD COLUMN")
|
||||
return buildStmtContext(schema, table, col, "")
|
||||
}
|
||||
if strings.Contains(upper, "ALTER COLUMN") {
|
||||
col := firstIdentAfterKeyword(stmt, upper, "ALTER COLUMN")
|
||||
return buildStmtContext(schema, table, col, "")
|
||||
}
|
||||
if strings.Contains(upper, "ADD CONSTRAINT") {
|
||||
con := firstIdentAfterKeyword(stmt, upper, "ADD CONSTRAINT")
|
||||
return buildStmtContext(schema, table, "", con)
|
||||
}
|
||||
if strings.Contains(upper, "DROP CONSTRAINT") {
|
||||
con := firstIdentAfterKeyword(stmt, upper, "DROP CONSTRAINT")
|
||||
return buildStmtContext(schema, table, "", con)
|
||||
}
|
||||
return buildStmtContext(schema, table, "", "")
|
||||
}
|
||||
|
||||
// CREATE TABLE [IF NOT EXISTS] [schema.]table
|
||||
if strings.HasPrefix(upper, "CREATE TABLE") {
|
||||
rest := strings.TrimSpace(stmt[12:])
|
||||
if strings.HasPrefix(strings.ToUpper(rest), "IF NOT EXISTS") {
|
||||
rest = strings.TrimSpace(rest[13:])
|
||||
}
|
||||
schema, table := parseQualifiedIdent(rest)
|
||||
return buildStmtContext(schema, table, "", "")
|
||||
}
|
||||
|
||||
// CREATE SCHEMA name
|
||||
if strings.HasPrefix(upper, "CREATE SCHEMA") {
|
||||
name := firstBareIdent(strings.TrimSpace(stmt[13:]))
|
||||
return name
|
||||
}
|
||||
|
||||
// CREATE [UNIQUE] INDEX ... ON [schema.]table
|
||||
if strings.HasPrefix(upper, "CREATE INDEX") || strings.HasPrefix(upper, "CREATE UNIQUE INDEX") {
|
||||
onIdx := strings.Index(upper, " ON ")
|
||||
if onIdx != -1 {
|
||||
schema, table := parseQualifiedIdent(strings.TrimSpace(stmt[onIdx+4:]))
|
||||
return buildStmtContext(schema, table, "", "")
|
||||
}
|
||||
}
|
||||
|
||||
// COMMENT ON TABLE [schema.]table
|
||||
if strings.HasPrefix(upper, "COMMENT ON TABLE") {
|
||||
schema, table := parseQualifiedIdent(strings.TrimSpace(stmt[16:]))
|
||||
return buildStmtContext(schema, table, "", "")
|
||||
}
|
||||
|
||||
// COMMENT ON COLUMN [schema.]table.col
|
||||
if strings.HasPrefix(upper, "COMMENT ON COLUMN") {
|
||||
return firstBareIdent(strings.TrimSpace(stmt[17:]))
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractSQLStringValue extracts 'value' from patterns like: key = 'value' (case-insensitive key match).
|
||||
func extractSQLStringValue(stmt, key string) string {
|
||||
lower := strings.ToLower(stmt)
|
||||
idx := strings.Index(lower, strings.ToLower(key))
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
rest := strings.TrimSpace(stmt[idx+len(key):])
|
||||
eqIdx := strings.IndexByte(rest, '=')
|
||||
if eqIdx == -1 || eqIdx > 5 {
|
||||
return ""
|
||||
}
|
||||
rest = strings.TrimSpace(rest[eqIdx+1:])
|
||||
if len(rest) == 0 || rest[0] != '\'' {
|
||||
return ""
|
||||
}
|
||||
rest = rest[1:]
|
||||
end := strings.IndexByte(rest, '\'')
|
||||
if end == -1 {
|
||||
return ""
|
||||
}
|
||||
return rest[:end]
|
||||
}
|
||||
|
||||
// parseQualifiedIdent extracts (schema, name) from the start of s, handling optional "schema"."table" quoting.
|
||||
func parseQualifiedIdent(s string) (schema, name string) {
|
||||
token := firstBareIdent(s)
|
||||
parts := strings.SplitN(token, ".", 2)
|
||||
if len(parts) == 2 {
|
||||
return stripQuotes(parts[0]), stripQuotes(parts[1])
|
||||
}
|
||||
return "", stripQuotes(token)
|
||||
}
|
||||
|
||||
// firstBareIdent returns the first whitespace/delimiter-terminated token, stripping outer quotes.
|
||||
func firstBareIdent(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
end := strings.IndexAny(s, " \t\n(,;")
|
||||
if end == -1 {
|
||||
return s
|
||||
}
|
||||
return s[:end]
|
||||
}
|
||||
|
||||
// firstIdentAfterKeyword returns the first identifier token after keyword (matched case-insensitively via upperStmt).
|
||||
func firstIdentAfterKeyword(stmt, upperStmt, keyword string) string {
|
||||
idx := strings.Index(upperStmt, keyword)
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
return stripQuotes(firstBareIdent(strings.TrimSpace(stmt[idx+len(keyword):])))
|
||||
}
|
||||
|
||||
// stripQuotes removes surrounding double-quotes from an identifier.
|
||||
func stripQuotes(s string) string {
|
||||
return strings.Trim(s, "\"")
|
||||
}
|
||||
|
||||
// buildStmtContext assembles a display string from available identifiers.
|
||||
func buildStmtContext(schema, table, column, constraint string) string {
|
||||
var b strings.Builder
|
||||
if schema != "" && table != "" {
|
||||
b.WriteString(schema)
|
||||
b.WriteByte('.')
|
||||
b.WriteString(table)
|
||||
} else if table != "" {
|
||||
b.WriteString(table)
|
||||
}
|
||||
if column != "" {
|
||||
if b.Len() > 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteByte('(')
|
||||
b.WriteString(column)
|
||||
b.WriteByte(')')
|
||||
}
|
||||
if constraint != "" {
|
||||
if b.Len() > 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteByte('[')
|
||||
b.WriteString(constraint)
|
||||
b.WriteByte(']')
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// detectStatementType detects the type of SQL statement for logging
|
||||
func detectStatementType(stmt string) string {
|
||||
upperStmt := strings.ToUpper(stmt)
|
||||
|
||||
@@ -116,14 +116,88 @@ func TestWriteDatabase_GinIndexOnTextArrayDoesNotUseTrigramOperatorClass(t *test
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, `USING gin (tags)`) {
|
||||
t.Fatalf("expected GIN index on array column without explicit trigram opclass, got:\n%s", output)
|
||||
if !strings.Contains(output, `USING gin (tags array_ops)`) {
|
||||
t.Fatalf("expected GIN index on array column with array_ops, got:\n%s", output)
|
||||
}
|
||||
if strings.Contains(output, "gin_trgm_ops") {
|
||||
t.Fatalf("did not expect gin_trgm_ops for text[] column, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteDatabase_GinIndexOnQuotedTextColumnUsesTrigramOperatorClass(t *testing.T) {
|
||||
db := models.InitDatabase("testdb")
|
||||
schema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("agent_personas", "public")
|
||||
|
||||
nameCol := models.InitColumn("name", "agent_personas", "public")
|
||||
nameCol.Type = "text"
|
||||
table.Columns["name"] = nameCol
|
||||
|
||||
index := &models.Index{
|
||||
Name: "idx_agent_personas_name_gin",
|
||||
Type: "gin",
|
||||
Columns: []string{`"name"`},
|
||||
}
|
||||
table.Indexes[index.Name] = index
|
||||
|
||||
schema.Tables = append(schema.Tables, table)
|
||||
db.Schemas = append(db.Schemas, schema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&writers.WriterOptions{})
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteDatabase(db); err != nil {
|
||||
t.Fatalf("WriteDatabase failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, `CREATE EXTENSION IF NOT EXISTS pg_trgm`) {
|
||||
t.Fatalf("expected trigram extension for text GIN index, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, `USING gin (name gin_trgm_ops)`) {
|
||||
t.Fatalf("expected quoted text GIN index to include gin_trgm_ops, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteDatabase_GinIndexOnJSONBUsesJSONBOperatorClass(t *testing.T) {
|
||||
db := models.InitDatabase("testdb")
|
||||
schema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("learnings", "public")
|
||||
|
||||
detailsCol := models.InitColumn("details", "learnings", "public")
|
||||
detailsCol.Type = "jsonb"
|
||||
table.Columns["details"] = detailsCol
|
||||
|
||||
index := &models.Index{
|
||||
Name: "idx_learnings_details",
|
||||
Type: "gin",
|
||||
Columns: []string{"details"},
|
||||
}
|
||||
table.Indexes[index.Name] = index
|
||||
|
||||
schema.Tables = append(schema.Tables, table)
|
||||
db.Schemas = append(db.Schemas, schema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&writers.WriterOptions{})
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteDatabase(db); err != nil {
|
||||
t.Fatalf("WriteDatabase failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, `USING gin (details jsonb_ops)`) {
|
||||
t.Fatalf("expected GIN jsonb index to include jsonb_ops, got:\n%s", output)
|
||||
}
|
||||
if strings.Contains(output, "gin_trgm_ops") {
|
||||
t.Fatalf("did not expect gin_trgm_ops for jsonb column, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteForeignKeys(t *testing.T) {
|
||||
// Create a test database with two related tables
|
||||
db := models.InitDatabase("testdb")
|
||||
@@ -673,9 +747,14 @@ func TestPrimaryKeyExistenceCheck(t *testing.T) {
|
||||
t.Errorf("Output missing logic to drop auto-generated primary key\nFull output:\n%s", output)
|
||||
}
|
||||
|
||||
// Verify it checks for our specific named constraint before adding it
|
||||
if !strings.Contains(output, "constraint_name = 'pk_public_products'") {
|
||||
t.Errorf("Output missing check for our named primary key constraint\nFull output:\n%s", output)
|
||||
// Verify it compares the current primary key columns before dropping/recreating
|
||||
if !strings.Contains(output, "current_pk_matches") || !strings.Contains(output, "ARRAY['id']") {
|
||||
t.Errorf("Output missing safe primary key comparison logic\nFull output:\n%s", output)
|
||||
}
|
||||
|
||||
// Verify it only adds the desired key when no PK exists or an auto-generated mismatch was dropped
|
||||
if !strings.Contains(output, "current_pk_name IS NULL") || !strings.Contains(output, "current_pk_name IN ('products_pkey', 'public_products_pkey')") {
|
||||
t.Errorf("Output missing guarded primary key creation logic\nFull output:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,6 +845,43 @@ func TestColumnSizeSpecifiers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteDatabase_PrimaryKeyTemplateDoesNotDropMatchingAutoPrimaryKey(t *testing.T) {
|
||||
db := models.InitDatabase("testdb")
|
||||
schema := models.InitSchema("public")
|
||||
table := models.InitTable("learnings", "public")
|
||||
|
||||
idCol := models.InitColumn("id", "learnings", "public")
|
||||
idCol.Type = "bigint"
|
||||
idCol.IsPrimaryKey = true
|
||||
table.Columns["id"] = idCol
|
||||
|
||||
parentCol := models.InitColumn("duplicate_of_learning_id", "learnings", "public")
|
||||
parentCol.Type = "bigint"
|
||||
table.Columns["duplicate_of_learning_id"] = parentCol
|
||||
|
||||
schema.Tables = append(schema.Tables, table)
|
||||
db.Schemas = append(db.Schemas, schema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&writers.WriterOptions{})
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteDatabase(db); err != nil {
|
||||
t.Fatalf("WriteDatabase failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "current_pk_matches") {
|
||||
t.Fatalf("expected generated SQL to compare current PK columns, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "ARRAY['id']") {
|
||||
t.Fatalf("expected generated SQL to compare against desired PK columns, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "NOT current_pk_matches") {
|
||||
t.Fatalf("expected generated SQL to avoid dropping matching PKs, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateColumnDefinition_PreservesExplicitTypeModifiers(t *testing.T) {
|
||||
writer := NewWriter(&writers.WriterOptions{})
|
||||
|
||||
@@ -942,3 +1058,82 @@ func TestWriteAddColumnStatements(t *testing.T) {
|
||||
t.Errorf("Output missing DO block\nFull output:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteSchema_EmitsGuardedAlterColumnTypeStatements(t *testing.T) {
|
||||
db := models.InitDatabase("testdb")
|
||||
schema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("agent_skills", "public")
|
||||
|
||||
nameCol := models.InitColumn("name", "agent_skills", "public")
|
||||
nameCol.Type = "character varying"
|
||||
nameCol.Length = 255
|
||||
table.Columns["name"] = nameCol
|
||||
|
||||
tagsCol := models.InitColumn("tags", "agent_skills", "public")
|
||||
tagsCol.Type = "text[]"
|
||||
table.Columns["tags"] = tagsCol
|
||||
|
||||
schema.Tables = append(schema.Tables, table)
|
||||
db.Schemas = append(db.Schemas, schema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&writers.WriterOptions{})
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteDatabase(db); err != nil {
|
||||
t.Fatalf("WriteDatabase failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "-- Alter column types for schema: public") {
|
||||
t.Fatalf("expected alter column type section, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "pg_catalog.format_type") {
|
||||
t.Fatalf("expected guarded live-type check, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "ALTER COLUMN name TYPE character varying(255)") {
|
||||
t.Fatalf("expected guarded alter for character varying(255), got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "ARRAY['varchar(255)', 'character varying(255)']") {
|
||||
t.Fatalf("expected equivalent type spellings for varchar guard, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "ALTER COLUMN tags TYPE text[]") {
|
||||
t.Fatalf("expected guarded alter for array type, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, `ALTER COLUMN tags TYPE text[] USING tags::text[];`) {
|
||||
t.Fatalf("expected guarded alter for array type to include USING cast, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteSchema_UsesStorageTypeForSerialAlterStatements(t *testing.T) {
|
||||
db := models.InitDatabase("testdb")
|
||||
schema := models.InitSchema("public")
|
||||
|
||||
table := models.InitTable("learnings", "public")
|
||||
idCol := models.InitColumn("id", "learnings", "public")
|
||||
idCol.Type = "bigserial"
|
||||
table.Columns["id"] = idCol
|
||||
|
||||
schema.Tables = append(schema.Tables, table)
|
||||
db.Schemas = append(db.Schemas, schema)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := NewWriter(&writers.WriterOptions{})
|
||||
writer.writer = &buf
|
||||
|
||||
if err := writer.WriteDatabase(db); err != nil {
|
||||
t.Fatalf("WriteDatabase failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "ALTER COLUMN id TYPE bigint") {
|
||||
t.Fatalf("expected serial alter to use bigint storage type, got:\n%s", output)
|
||||
}
|
||||
if strings.Contains(output, "ALTER COLUMN id TYPE bigserial;") {
|
||||
t.Fatalf("did not expect invalid bigserial alter statement, got:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, `ALTER COLUMN id TYPE bigint USING id::bigint;`) {
|
||||
t.Fatalf("expected serial alter to include USING cast, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package sqlite
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
sqlitepkg "git.warky.dev/wdevs/relspecgo/pkg/sqlite"
|
||||
)
|
||||
|
||||
// SQLite type affinities
|
||||
// SQLite type affinity constants
|
||||
const (
|
||||
TypeText = "TEXT"
|
||||
TypeInteger = "INTEGER"
|
||||
@@ -13,72 +15,27 @@ const (
|
||||
TypeBlob = "BLOB"
|
||||
)
|
||||
|
||||
// MapPostgreSQLType maps PostgreSQL data types to SQLite type affinities
|
||||
// MapTypeToSQLite maps any SQL or Go canonical type to a SQLite type affinity.
|
||||
// Handles input from any reader (PostgreSQL, MSSQL, SQLite, Go canonical).
|
||||
func MapTypeToSQLite(colType string) string {
|
||||
return sqlitepkg.ConvertCanonicalToSQLite(colType)
|
||||
}
|
||||
|
||||
// MapPostgreSQLType is an alias for MapTypeToSQLite kept for compatibility.
|
||||
//
|
||||
// Deprecated: use MapTypeToSQLite.
|
||||
func MapPostgreSQLType(pgType string) string {
|
||||
// Normalize the type
|
||||
normalized := strings.ToLower(strings.TrimSpace(pgType))
|
||||
|
||||
// Remove array notation if present
|
||||
normalized = strings.TrimSuffix(normalized, "[]")
|
||||
|
||||
// Remove precision/scale if present
|
||||
if idx := strings.Index(normalized, "("); idx != -1 {
|
||||
normalized = normalized[:idx]
|
||||
}
|
||||
|
||||
// Map to SQLite type affinity
|
||||
switch normalized {
|
||||
// TEXT affinity
|
||||
case "varchar", "character varying", "text", "char", "character",
|
||||
"citext", "uuid", "timestamp", "timestamptz", "timestamp with time zone",
|
||||
"timestamp without time zone", "date", "time", "timetz", "time with time zone",
|
||||
"time without time zone", "json", "jsonb", "xml", "inet", "cidr", "macaddr":
|
||||
return TypeText
|
||||
|
||||
// INTEGER affinity
|
||||
case "int", "int2", "int4", "int8", "integer", "smallint", "bigint",
|
||||
"serial", "smallserial", "bigserial", "boolean", "bool":
|
||||
return TypeInteger
|
||||
|
||||
// REAL affinity
|
||||
case "real", "float", "float4", "float8", "double precision":
|
||||
return TypeReal
|
||||
|
||||
// NUMERIC affinity
|
||||
case "numeric", "decimal", "money":
|
||||
return TypeNumeric
|
||||
|
||||
// BLOB affinity
|
||||
case "bytea", "blob":
|
||||
return TypeBlob
|
||||
|
||||
default:
|
||||
// Default to TEXT for unknown types
|
||||
return TypeText
|
||||
}
|
||||
return MapTypeToSQLite(pgType)
|
||||
}
|
||||
|
||||
// IsIntegerType checks if a column type should be treated as integer
|
||||
// IsIntegerType reports whether a column type maps to SQLite INTEGER affinity.
|
||||
func IsIntegerType(colType string) bool {
|
||||
normalized := strings.ToLower(strings.TrimSpace(colType))
|
||||
normalized = strings.TrimSuffix(normalized, "[]")
|
||||
if idx := strings.Index(normalized, "("); idx != -1 {
|
||||
normalized = normalized[:idx]
|
||||
}
|
||||
|
||||
switch normalized {
|
||||
case "int", "int2", "int4", "int8", "integer", "smallint", "bigint",
|
||||
"serial", "smallserial", "bigserial":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return MapTypeToSQLite(colType) == TypeInteger
|
||||
}
|
||||
|
||||
// MapBooleanValue converts PostgreSQL boolean literals to SQLite (0/1)
|
||||
// MapBooleanValue converts common boolean literals to SQLite integers (1/0).
|
||||
func MapBooleanValue(value string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(value))
|
||||
switch normalized {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "true", "t", "yes", "y", "1":
|
||||
return "1"
|
||||
case "false", "f", "no", "n", "0":
|
||||
|
||||
@@ -54,6 +54,10 @@ type WriterOptions struct {
|
||||
// Prisma7 enables Prisma 7-specific output for Prisma writers.
|
||||
Prisma7 bool
|
||||
|
||||
// ContinueOnError instructs SQL writers to prepend `\set ON_ERROR_STOP off`
|
||||
// to their output so that psql continues past errors instead of stopping.
|
||||
ContinueOnError bool
|
||||
|
||||
// Additional options can be added here as needed
|
||||
Metadata map[string]interface{}
|
||||
}
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
version: 1.0.{build}
|
||||
clone_folder: c:\gopath\src\github.com\gdamore\tcell
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
build_script:
|
||||
- go version
|
||||
- go env
|
||||
- SET PATH=%LOCALAPPDATA%\atom\bin;%GOPATH%\bin;%PATH%
|
||||
- go get -t ./...
|
||||
- go build
|
||||
- go install ./...
|
||||
test_script:
|
||||
- go test ./...
|
||||
+2
@@ -1 +1,3 @@
|
||||
coverage.txt
|
||||
.zed
|
||||
.idea
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.15.x
|
||||
- master
|
||||
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
+2
-2
@@ -13,7 +13,7 @@ GOOS=js GOARCH=wasm go build -o yourfile.wasm
|
||||
|
||||
You also need 5 other files in the same directory as the wasm. Four (`tcell.html`, `tcell.js`, `termstyle.css`, and `beep.wav`) are provided in the `webfiles` directory. The last one, `wasm_exec.js`, can be copied from GOROOT into the current directory by executing
|
||||
```sh
|
||||
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./
|
||||
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ./
|
||||
```
|
||||
|
||||
In `tcell.js`, you also need to change the constant
|
||||
@@ -58,4 +58,4 @@ It is recommended to use an iframe if you want to embed the app into a webpage:
|
||||
|
||||
### Accessing files
|
||||
|
||||
`io.Open(filename)` and other related functions for reading file systems do not work; use `http.Get(filename)` instead.
|
||||
`io.Open(filename)` and other related functions for reading file systems do not work; use `http.Get(filename)` instead.
|
||||
|
||||
+25
-134
@@ -7,14 +7,14 @@ It was inspired by _termbox_, but includes many additional improvements.
|
||||
|
||||
[](https://stand-with-ukraine.pp.ua)
|
||||
[](https://github.com/gdamore/tcell/actions/workflows/linux.yml)
|
||||
[](https://github.com/gdamore/tcell/actions/workflows/windows.yml)
|
||||
[](https://github.com/gdamore/tcell/actions/workflows/windows.yml)
|
||||
[](https://github.com/gdamore/tcell/actions/workflows/webasm.yml)
|
||||
[](https://github.com/gdamore/tcell/blob/master/LICENSE)
|
||||
[](https://pkg.go.dev/github.com/gdamore/tcell/v2)
|
||||
[](https://discord.gg/urTTxDN)
|
||||
[](https://codecov.io/gh/gdamore/tcell)
|
||||
[](https://goreportcard.com/report/github.com/gdamore/tcell/v2)
|
||||
|
||||
Please see [here](UKRAINE.md) for an important message for the people of Russia.
|
||||
[](https://github.com/gdamore/tcell/releases)
|
||||
|
||||
NOTE: This is version 2 of _Tcell_. There are breaking changes relative to version 1.
|
||||
Version 1.x remains available using the import `github.com/gdamore/tcell`.
|
||||
@@ -25,59 +25,9 @@ A brief, and still somewhat rough, [tutorial](TUTORIAL.md) is available.
|
||||
|
||||
## Examples
|
||||
|
||||
- [proxima5](https://github.com/gdamore/proxima5) - space shooter ([video](https://youtu.be/jNxKTCmY_bQ))
|
||||
- [govisor](https://github.com/gdamore/govisor) - service management UI ([screenshot](http://2.bp.blogspot.com/--OsvnfzSNow/Vf7aqMw3zXI/AAAAAAAAARo/uOMtOvw4Sbg/s1600/Screen%2BShot%2B2015-09-20%2Bat%2B9.08.41%2BAM.png))
|
||||
- mouse demo - included mouse test ([screenshot](http://2.bp.blogspot.com/-fWvW5opT0es/VhIdItdKqJI/AAAAAAAAATE/7Ojc0L1SpB0/s1600/Screen%2BShot%2B2015-10-04%2Bat%2B11.47.13%2BPM.png))
|
||||
- [gomatrix](https://github.com/gdamore/gomatrix) - converted from Termbox
|
||||
- [micro](https://github.com/zyedidia/micro/) - lightweight text editor with syntax-highlighting and themes
|
||||
- [godu](https://github.com/viktomas/godu) - utility to discover large files/folders
|
||||
- [tview](https://github.com/rivo/tview/) - rich interactive widgets
|
||||
- [cview](https://code.rocketnine.space/tslocum/cview) - user interface toolkit (fork of _tview_)
|
||||
- [awesome gocui](https://github.com/awesome-gocui/gocui) - Go Console User Interface
|
||||
- [gomandelbrot](https://github.com/rgm3/gomandelbrot) - Mandelbrot!
|
||||
- [WTF](https://github.com/senorprogrammer/wtf) - personal information dashboard
|
||||
- [browsh](https://github.com/browsh-org/browsh) - modern web browser ([video](https://www.youtube.com/watch?v=HZq86XfBoRo))
|
||||
- [go-life](https://github.com/sachaos/go-life) - Conway's Game of Life
|
||||
- [gowid](https://github.com/gcla/gowid) - compositional widgets for terminal UIs, inspired by _urwid_
|
||||
- [termshark](https://termshark.io) - interface for _tshark_, inspired by Wireshark, built on _gowid_
|
||||
- [go-tetris](https://github.com/MichaelS11/go-tetris) - Go Tetris with AI option
|
||||
- [fzf](https://github.com/junegunn/fzf) - command-line fuzzy finder
|
||||
- [ascii-fluid](https://github.com/esimov/ascii-fluid) - fluid simulation controlled by webcam
|
||||
- [cbind](https://code.rocketnine.space/tslocum/cbind) - key event encoding, decoding and handling
|
||||
- [tpong](https://github.com/spinzed/tpong) - old-school Pong
|
||||
- [aerc](https://git.sr.ht/~sircmpwn/aerc) - email client
|
||||
- [tblogs](https://github.com/ezeoleaf/tblogs) - development blogs reader
|
||||
- [spinc](https://github.com/lallassu/spinc) - _irssi_ inspired chat application for Cisco Spark/WebEx
|
||||
- [gorss](https://github.com/lallassu/gorss) - RSS/Atom feed reader
|
||||
- [memoryalike](https://github.com/Bios-Marcel/memoryalike) - memorization game
|
||||
- [lf](https://github.com/gokcehan/lf) - file manager
|
||||
- [goful](https://github.com/anmitsu/goful) - CUI file manager
|
||||
- [gokeybr](https://github.com/bunyk/gokeybr) - deliberately practice your typing
|
||||
- [gonano](https://github.com/jbaramidze/gonano) - editor, mimics _nano_
|
||||
- [uchess](https://github.com/tmountain/uchess) - UCI chess client
|
||||
- [min](https://github.com/a-h/min) - Gemini browser
|
||||
- [ov](https://github.com/noborus/ov) - file pager
|
||||
- [tmux-wormhole](https://github.com/gcla/tmux-wormhole) - _tmux_ plugin to transfer files
|
||||
- [gruid-tcell](https://github.com/anaseto/gruid-tcell) - driver for the grid based UI and game framework
|
||||
- [aretext](https://github.com/aretext/aretext) - minimalist text editor with _vim_ key bindings
|
||||
- [sync](https://github.com/kyprifog/sync) - GitHub repo synchronization tool
|
||||
- [statusbar](https://github.com/kyprifog/statusbar) - statusbar motivation tool for tracking periodic tasks/goals
|
||||
- [todo](https://github.com/kyprifog/todo) - simple todo app
|
||||
- [gosnakego](https://github.com/liweiyi88/gosnakego) - a snake game
|
||||
- [gbb](https://github.com/sdemingo/gbb) - A classical bulletin board app for tildes or public unix servers
|
||||
- [lil](https://github.com/andrievsky/lil) - A simple and flexible interface for any service by implementing only list and get operations
|
||||
- [hero.go](https://github.com/barisbll/hero.go) - 2d monster shooter ([video](https://user-images.githubusercontent.com/40062673/277157369-240d7606-b471-4aa1-8c54-4379a513122b.mp4))
|
||||
- [go-tetris](https://github.com/aaronriekenberg/go-tetris) - simple tetris game for native terminal and WASM using github actions+pages
|
||||
- [oddshub](https://github.com/dos-2/oddshub) - A TUI designed for analyzing sports betting odds
|
||||
A number of example are posted up on our [Gallery](https://github.com/gdamore/tcell/wikis/Gallery/).
|
||||
|
||||
## Pure Go Terminfo Database
|
||||
|
||||
_Tcell_ includes a full parser and expander for terminfo capability strings,
|
||||
so that it can avoid hard coding escape strings for formatting. It also favors
|
||||
portability, and includes support for all POSIX systems.
|
||||
|
||||
The database is also flexible & extensible, and can be modified by either running
|
||||
a program to build the entire database, or an entry for just a single terminal.
|
||||
Let us know if you want to add your masterpiece to the list!
|
||||
|
||||
## More Portable
|
||||
|
||||
@@ -85,13 +35,10 @@ _Tcell_ is portable to a wide variety of systems, and is pure Go, without
|
||||
any need for CGO.
|
||||
_Tcell_ is believed to work with mainstream systems officially supported by golang.
|
||||
|
||||
## No Async IO
|
||||
|
||||
_Tcell_ is able to operate without requiring `SIGIO` signals (unlike _termbox_),
|
||||
or asynchronous I/O, and can instead use standard Go file objects and Go routines.
|
||||
This means it should be safe, especially for
|
||||
use with programs that use exec, or otherwise need to manipulate the tty streams.
|
||||
This model is also much closer to idiomatic Go, leading to fewer surprises.
|
||||
Following the Go support policy, _Tcell_ officially only supports the current ("stable") version of go,
|
||||
and the version immediately prior ("oldstable"). This policy is necessary to make sure that we can
|
||||
update dependencies to pick up security fixes and new features, and it allows us to adopt changes
|
||||
(such as library and language features) that are only supported in newer versions of Go.
|
||||
|
||||
## Rich Unicode & non-Unicode support
|
||||
|
||||
@@ -111,29 +58,11 @@ drawing certain characters.
|
||||
_Tcell_ also has richer support for a larger number of special keys that some
|
||||
terminals can send.
|
||||
|
||||
## Better Color Handling
|
||||
|
||||
_Tcell_ will respect your terminal's color space as specified within your terminfo entries.
|
||||
For example attempts to emit color sequences on VT100 terminals
|
||||
won't result in unintended consequences.
|
||||
|
||||
In legacy Windows mode, _Tcell_ supports 16 colors, bold, dim, and reverse,
|
||||
instead of just termbox's 8 colors with reverse. (Note that there is some
|
||||
conflation with bold/dim and colors.)
|
||||
Modern Windows 10 can benefit from much richer colors however.
|
||||
|
||||
_Tcell_ maps 16 colors down to 8, for terminals that need it.
|
||||
(The upper 8 colors are just brighter versions of the lower 8.)
|
||||
|
||||
## Better Mouse Support
|
||||
|
||||
_Tcell_ supports enhanced mouse tracking mode, so your application can receive
|
||||
regular mouse motion events, and wheel events, if your terminal supports it.
|
||||
|
||||
(Note: The Windows 10 Terminal application suffers from a flaw in this regard,
|
||||
and does not support mouse interaction. The stock Windows 10 console host
|
||||
fired up with cmd.exe or PowerShell works fine however.)
|
||||
|
||||
## _Termbox_ Compatibility
|
||||
|
||||
A compatibility layer for _termbox_ is provided in the `compat` directory.
|
||||
@@ -152,15 +81,15 @@ If you're lazy, and want them all anyway, see the `encoding` sub-directory.
|
||||
|
||||
## Wide & Combining Characters
|
||||
|
||||
The `SetContent()` API takes a primary rune, and an optional list of combining runes.
|
||||
If any of the runes is a wide (East Asian) rune occupying two cells,
|
||||
then the library will skip output from the following cell. Care must be
|
||||
taken in the application to avoid explicitly attempting to set content in the
|
||||
next cell, otherwise the results are undefined. (Normally the wide character
|
||||
is displayed, and the other character is not; do not depend on that behavior.)
|
||||
The `Put()` API takes a string, which should be legal UTF-8, and displays
|
||||
the first grapheme (which may composed of multiple runes). It returns the
|
||||
actual width displayed, which can be used to advance the column positiion
|
||||
for the next display grapheme. Alternatively, `PutStr()` or `PutStrStyled()`
|
||||
can be used to display a single line of text (which will be clipped at the
|
||||
edge of the screen).
|
||||
|
||||
Older terminal applications (especially on systems like Windows 8) lack support
|
||||
for advanced Unicode, and thus may not fare well.
|
||||
If a second character is displayed immediately in the cell adjacent to a
|
||||
wide character (offset by one instead of by two), then the results are undefined.
|
||||
|
||||
## Colors
|
||||
|
||||
@@ -175,11 +104,7 @@ a ticket.
|
||||
|
||||
_Tcell_ _supports 24-bit color!_ (That is, if your terminal can support it.)
|
||||
|
||||
NOTE: Technically the approach of using 24-bit RGB values for color is more
|
||||
accurately described as "direct color", but most people use the term "true color".
|
||||
We follow the (inaccurate) common convention.
|
||||
|
||||
There are a few ways you can enable (or disable) true color.
|
||||
There are a few ways you can enable (or disable) 24-bit color.
|
||||
|
||||
- For many terminals, we can detect it automatically if your terminal
|
||||
includes the `RGB` or `Tc` capabilities (or rather it did when the database
|
||||
@@ -197,8 +122,8 @@ There are a few ways you can enable (or disable) true color.
|
||||
- You can disable 24-bit color by setting `TCELL_TRUECOLOR=disable` in your
|
||||
environment.
|
||||
|
||||
When using TrueColor, programs will display the colors that the programmer
|
||||
intended, overriding any "`themes`" you may have set in your terminal
|
||||
When using 24-bit color, programs will display the colors that the programmer
|
||||
intended, overriding any "`themes`" the user may have set in their terminal
|
||||
emulator. (For some cases, accurate color fidelity is more important
|
||||
than respecting themes. For other cases, such as typical text apps that
|
||||
only use a few colors, its more desirable to respect the themes that
|
||||
@@ -209,38 +134,10 @@ the user has established.)
|
||||
Reasonable attempts have been made to minimize sending data to terminals,
|
||||
avoiding repeated sequences or drawing the same cell on refresh updates.
|
||||
|
||||
## Terminfo
|
||||
|
||||
(Not relevant for Windows users.)
|
||||
|
||||
The Terminfo implementation operates with a built-in database.
|
||||
This should satisfy most users. However, it can also (on systems
|
||||
with ncurses installed), dynamically parse the output from `infocmp`
|
||||
for terminals it does not already know about.
|
||||
|
||||
See the `terminfo/` directory for more information about generating
|
||||
new entries for the built-in database.
|
||||
|
||||
_Tcell_ requires that the terminal support the `cup` mode of cursor addressing.
|
||||
Ancient terminals without the ability to position the cursor directly
|
||||
are not supported.
|
||||
This is unlikely to be a problem; such terminals have not been mass-produced
|
||||
since the early 1970s.
|
||||
|
||||
## Mouse Support
|
||||
|
||||
Mouse support is detected via the `kmous` terminfo variable, however,
|
||||
enablement/disablement and decoding mouse events is done using hard coded
|
||||
sequences based on the XTerm X11 model. All popular
|
||||
terminals with mouse tracking support this model. (Full terminfo support
|
||||
is not possible as terminfo sequences are not defined.)
|
||||
|
||||
On Windows, the mouse works normally.
|
||||
|
||||
Mouse wheel buttons on various terminals are known to work, but the support
|
||||
in terminal emulators, as well as support for various buttons and
|
||||
live mouse tracking, varies widely.
|
||||
Modern _xterm_, macOS _Terminal_, and _iTerm_ all work well.
|
||||
Mouse tracking, buttons, and even wheel mice works fine on most terminal
|
||||
emulators, as well as Windows.
|
||||
|
||||
## Bracketed Paste
|
||||
|
||||
@@ -265,22 +162,16 @@ platforms (e.g., AIX) may need to be added. Pull requests are welcome!
|
||||
|
||||
Windows console mode applications are supported.
|
||||
|
||||
Modern console applications like ConEmu and the Windows 10 terminal,
|
||||
Modern console applications like ConEmu and the Windows Terminal,
|
||||
support all the good features (resize, mouse tracking, etc.)
|
||||
|
||||
### WASM
|
||||
|
||||
WASM is supported, but needs additional setup detailed in [README-wasm](README-wasm.md).
|
||||
|
||||
### Plan9 and others
|
||||
### Plan9 and its variants
|
||||
|
||||
These platforms won't work, but compilation stubs are supplied
|
||||
for folks that want to include parts of this in software for those
|
||||
platforms. The Simulation screen works, but as _Tcell_ doesn't know how to
|
||||
allocate a real screen object on those platforms, `NewScreen()` will fail.
|
||||
|
||||
If anyone has wisdom about how to improve support for these,
|
||||
please let me know. PRs are especially welcome.
|
||||
Plan 9 is supported on a limited basis. The Plan 9 backend opens `/dev/cons` for I/O, enables raw mode by writing `rawon`/`rawoff` to `/dev/consctl`, watches `/dev/wctl` for resize notifications, and then constructs a **terminfo-backed** `Screen` (so `NewScreen` works as on other platforms). Typical usage is inside `vt(1)` with `TERM=vt100`. Expect **monochrome text** and **no mouse reporting** under stock `vt(1)` (it generally does not emit ANSI color or xterm mouse sequences). If a Plan 9 terminal supplies ANSI color escape sequences and xterm-style mouse reporting, color can be picked up via **terminfo** and mouse support could be added by wiring those sequences into the Plan 9 TTY path; contributions that improve terminal detection and broaden feature support are welcome.
|
||||
|
||||
### Commercial Support
|
||||
|
||||
|
||||
+36
-21
@@ -107,23 +107,30 @@ s.SetStyle(defStyle)
|
||||
s.Clear()
|
||||
```
|
||||
|
||||
Text may be drawn on the screen using `SetContent`.
|
||||
Text may be drawn on the screen using `Put`, `PutStr`, or `PutStrStyled`.
|
||||
|
||||
```go
|
||||
s.SetContent(0, 0, 'H', nil, defStyle)
|
||||
s.SetContent(1, 0, 'i', nil, defStyle)
|
||||
s.SetContent(2, 0, '!', nil, defStyle)
|
||||
s.Put(0, 0, 'H', defStyle)
|
||||
s.Put(1, 0, 'i', defStyle)
|
||||
s.Put(2, 0, '!', defStyle)
|
||||
```
|
||||
|
||||
To draw text more easily, define a render function.
|
||||
which is equivalent to
|
||||
|
||||
```go
|
||||
s.PutStrStyled(0, 0, "Hi!", defStyle)
|
||||
````
|
||||
|
||||
To draw text more easily with wrapping, define a render function.
|
||||
|
||||
```go
|
||||
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
|
||||
row := y1
|
||||
col := x1
|
||||
for _, r := range []rune(text) {
|
||||
s.SetContent(col, row, r, nil, style)
|
||||
col++
|
||||
var width int
|
||||
for text != "" {
|
||||
text, width = s.Put(col, row, text, style)
|
||||
col += width
|
||||
if col >= x2 {
|
||||
row++
|
||||
col = x1
|
||||
@@ -131,6 +138,10 @@ func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string
|
||||
if row > y2 {
|
||||
break
|
||||
}
|
||||
if width == 0 {
|
||||
// incomplete grapheme at end of string
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -178,9 +189,10 @@ import (
|
||||
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
|
||||
row := y1
|
||||
col := x1
|
||||
for _, r := range []rune(text) {
|
||||
s.SetContent(col, row, r, nil, style)
|
||||
col++
|
||||
var width int
|
||||
for text != "" {
|
||||
text, width = s.Put(col, row, text, style)
|
||||
col += width
|
||||
if col >= x2 {
|
||||
row++
|
||||
col = x1
|
||||
@@ -188,6 +200,10 @@ func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string
|
||||
if row > y2 {
|
||||
break
|
||||
}
|
||||
if width == 0 {
|
||||
// incomplete grapheme at end of string
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,26 +218,26 @@ func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string)
|
||||
// Fill background
|
||||
for row := y1; row <= y2; row++ {
|
||||
for col := x1; col <= x2; col++ {
|
||||
s.SetContent(col, row, ' ', nil, style)
|
||||
s.Put(col, row, " ", style)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw borders
|
||||
for col := x1; col <= x2; col++ {
|
||||
s.SetContent(col, y1, tcell.RuneHLine, nil, style)
|
||||
s.SetContent(col, y2, tcell.RuneHLine, nil, style)
|
||||
s.Put(col, y1, string(tcell.RuneHLine), style)
|
||||
s.Put(col, y2, string(tcell.RuneHLine), style)
|
||||
}
|
||||
for row := y1 + 1; row < y2; row++ {
|
||||
s.SetContent(x1, row, tcell.RuneVLine, nil, style)
|
||||
s.SetContent(x2, row, tcell.RuneVLine, nil, style)
|
||||
s.Put(x1, row, string(tcell.RuneVLine), style)
|
||||
s.Put(x2, row, string(tcell.RuneVLine), style)
|
||||
}
|
||||
|
||||
// Only draw corners if necessary
|
||||
if y1 != y2 && x1 != x2 {
|
||||
s.SetContent(x1, y1, tcell.RuneULCorner, nil, style)
|
||||
s.SetContent(x2, y1, tcell.RuneURCorner, nil, style)
|
||||
s.SetContent(x1, y2, tcell.RuneLLCorner, nil, style)
|
||||
s.SetContent(x2, y2, tcell.RuneLRCorner, nil, style)
|
||||
s.Put(x1, y1, string(tcell.RuneULCorner), style)
|
||||
s.Put(x2, y1, string(tcell.RuneURCorner), style)
|
||||
s.Put(x1, y2, string(tcell.RuneLLCorner), style)
|
||||
s.Put(x2, y2, string(tcell.RuneLRCorner), style)
|
||||
}
|
||||
|
||||
drawText(s, x1+1, y1+1, x2-1, y2-1, style, text)
|
||||
@@ -310,4 +326,3 @@ func main() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
# Ukraine, Russia, and a World Tragedy
|
||||
|
||||
## A message to those inside Russia
|
||||
|
||||
### Written March 4, 2022.
|
||||
|
||||
It is with a very heavy heart that I write this. I am normally opposed to the use of open source
|
||||
projects to communicate political positions or advocate for things outside the immediate relevancy
|
||||
to that project.
|
||||
|
||||
However, the events occurring in Ukraine, and specifically the unprecedented invasion of Ukraine by
|
||||
Russian forces operating under orders from Russian President Vladimir Putin compel me to speak out.
|
||||
|
||||
Those who know me, know that I have family, friends, and colleagues in Russia, and Ukraine both. My closest friends
|
||||
have historically been Russian friends my wife's hometown of Chelyabinsk. I myself have in the past
|
||||
frequently traveled to Russia, and indeed operated a software development firm with offices in St. Petersburg.
|
||||
I had a special kinship with Russia and its people.
|
||||
|
||||
I say "had", because I fear that the actions of Putin, and the massive disinformation campaign that his regime
|
||||
has waged inside Russia, mean that it's likely that I won't see those friends again. At present, I'm not sure
|
||||
my wife will see her own mother again. We no longer feel it's safe for either of us to return Russia given
|
||||
actions taken by the regime to crack down on those who express disagreement.
|
||||
|
||||
Russian citizens are being led to believe it is acting purely defensively, and that only legitimate military
|
||||
targets are being targeted, and that all the information we have received in the West are fakes.
|
||||
|
||||
I am confident that nothing could be further from the truth.
|
||||
|
||||
This has caused many in Russia, including people whom I respect and believe to be smarter than this, to
|
||||
stand by Putin, and endorse his actions. The claim is that the entirety of NATO is operating at the behest
|
||||
of the USA, and that the entirety of Europe was poised to attack Russia. While this is clearly absurd to those
|
||||
of us with any understanding of western politics, Russian citizens are being fed this lie, and believing it.
|
||||
|
||||
If you're reading this from inside Russia -- YOU are the person that I hope this message reaches. Your
|
||||
government is LYING to you. Of course, all governments lie all the time. But consider this. Almost the
|
||||
entire world has condemned the invasion of Ukraine as criminal, and has applied sanctions. Even countries
|
||||
which have poor relations with the US sanctioning Russia, as well as nations which historically have remained
|
||||
neutral. (Famously neutral -- even during World War II, Switzerland has acted to apply sanctions in
|
||||
concert with the rest of the world.)
|
||||
|
||||
Ask yourself, why does Putin fear a free press so much, if what he says is true? Why the crack-downs on
|
||||
children expressing only a desire for peace with Ukraine? Why would the entire world unified against him,
|
||||
if Putin was in the right? Why would the only countries that stood with Russia against
|
||||
the UN resolution to condemn these acts as crimes be Belarus, North Korea, and Syria? Even countries normally
|
||||
allied to Russia could not bring themselves to do more than abstain from the vote to condemn it.
|
||||
|
||||
To be clear, I do not claim that the actions taken by the West or by the Ukrainian government were completely
|
||||
blameless. On the contrary, I understand that Western media is biased, and the truth is rarely exactly
|
||||
as reported. I believe that there is a kernel of truth in the claims of fascists and ultra-nationalist
|
||||
militias operating in Ukraine and specifically Donbas. However, I am also equally certain that Putin's
|
||||
response is out of proportion, and that concerns about such militias are principally just a pretext to justify
|
||||
an invasion.
|
||||
|
||||
Europe is at war, unlike we've seen in my lifetime. The world is more divided, and closer to nuclear holocaust
|
||||
than it has been since the Cold War. And that is 100% the fault of Putin.
|
||||
|
||||
While Putin remains in power, there cannot really be any way for Russian international relations to return
|
||||
to normal. Putin has set your country on a path to return to the Cold War, likely because he fancies himself
|
||||
to be a new Stalin. However, unlike the Soviet Union, the Russian economy does not have the wherewithal to
|
||||
stand on its own, and the invasion of Ukraine has fully ensured that Russia will not find any friends anywhere
|
||||
else in Europe, and probably few places in Asia.
|
||||
|
||||
The *only* paths forward for Russia are either a Russia without Putin (and those who would support his agenda),
|
||||
or a complete breakdown of Russian prosperity, likely followed by the increasing international conflict that will
|
||||
be the natural escalation from a country that is isolated and impoverished. Those of us observing from the West are
|
||||
gravely concerned, because we cannot see any end to this madness that does not result in nuclear conflict,
|
||||
unless from within.
|
||||
|
||||
In the meantime, the worst prices will be paid for by innocents in Ukraine, and by young Russian mean
|
||||
forced to carry out the orders of Putin's corrupt regime.
|
||||
|
||||
And *that* is why I write this -- to appeal to those within Russia to open your eyes, and think with
|
||||
your minds. It is right and proper to be proud of your country and its rich heritage. But it is also
|
||||
right and proper to look for ways to save it from the ruinous path that its current leadership has set it upon,
|
||||
and to recognize when that leadership is no longer acting in interest of the country or its people.
|
||||
|
||||
- Garrett D'Amore, March 4, 2022
|
||||
+81
-66
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The TCell Authors
|
||||
// Copyright 2025 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
@@ -15,23 +15,30 @@
|
||||
package tcell
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
type cell struct {
|
||||
currMain rune
|
||||
currComb []rune
|
||||
currStr string
|
||||
lastStr string
|
||||
currStyle Style
|
||||
lastMain rune
|
||||
lastStyle Style
|
||||
lastComb []rune
|
||||
width int
|
||||
lock bool
|
||||
}
|
||||
|
||||
func (c *cell) setDirty(dirty bool) {
|
||||
if dirty {
|
||||
c.lastStr = ""
|
||||
} else {
|
||||
if c.currStr == "" {
|
||||
c.currStr = " "
|
||||
}
|
||||
c.lastStr = c.currStr
|
||||
c.lastStyle = c.currStyle
|
||||
}
|
||||
}
|
||||
|
||||
// CellBuffer represents a two-dimensional array of character cells.
|
||||
// This is primarily intended for use by Screen implementors; it
|
||||
// contains much of the common code they need. To create one, just
|
||||
@@ -48,28 +55,47 @@ type CellBuffer struct {
|
||||
// and style) for a cell at a given location. If the background or
|
||||
// foreground of the style is set to ColorNone, then the respective
|
||||
// color is left un changed.
|
||||
func (cb *CellBuffer) SetContent(x int, y int,
|
||||
mainc rune, combc []rune, style Style,
|
||||
) {
|
||||
//
|
||||
// Deprecated: Use Put instead, which this is implemented in terms of.
|
||||
func (cb *CellBuffer) SetContent(x int, y int, mainc rune, combc []rune, style Style) {
|
||||
cb.Put(x, y, string(append([]rune{mainc}, combc...)), style)
|
||||
}
|
||||
|
||||
// Put a single styled grapheme using the given string and style
|
||||
// at the same location. Note that only the first grapheme in the string
|
||||
// will bre displayed, using only the 1 or 2 (depending on width) cells
|
||||
// located at x, y. It returns the rest of the string, and the width used.
|
||||
func (cb *CellBuffer) Put(x int, y int, str string, style Style) (string, int) {
|
||||
var width int = 0
|
||||
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
|
||||
var cl string
|
||||
c := &cb.cells[(y*cb.w)+x]
|
||||
state := -1
|
||||
for width == 0 && str != "" {
|
||||
var g string
|
||||
g, str, width, state = uniseg.FirstGraphemeClusterInString(str, state)
|
||||
cl += g
|
||||
if g == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wide characters: we want to mark the "wide" cells
|
||||
// dirty as well as the base cell, to make sure we consider
|
||||
// both cells as dirty together. We only need to do this
|
||||
// if we're changing content
|
||||
if (c.width > 0) && (mainc != c.currMain || len(combc) != len(c.currComb) || (len(combc) > 0 && !reflect.DeepEqual(combc, c.currComb))) {
|
||||
for i := 0; i < c.width; i++ {
|
||||
if width > 0 && cl != c.currStr {
|
||||
// Prevent unnecessary boundchecks for first cell, since we already
|
||||
// received that one.
|
||||
c.setDirty(true)
|
||||
for i := 1; i < width; i++ {
|
||||
cb.SetDirty(x+i, y, true)
|
||||
}
|
||||
}
|
||||
|
||||
c.currComb = append([]rune{}, combc...)
|
||||
c.currStr = cl
|
||||
c.width = width
|
||||
|
||||
if c.currMain != mainc {
|
||||
c.width = runewidth.RuneWidth(mainc)
|
||||
}
|
||||
c.currMain = mainc
|
||||
if style.fg == ColorNone {
|
||||
style.fg = c.currStyle.fg
|
||||
}
|
||||
@@ -78,23 +104,45 @@ func (cb *CellBuffer) SetContent(x int, y int,
|
||||
}
|
||||
c.currStyle = style
|
||||
}
|
||||
return str, width
|
||||
}
|
||||
|
||||
// Get the contents of a character cell (or two adjacent cells), including the
|
||||
// the style and the display width in cells. (The width can be either 1, normally,
|
||||
// or 2 for East Asian full-width characters. If the width is 0, then the cell is
|
||||
// is empty.)
|
||||
func (cb *CellBuffer) Get(x, y int) (string, Style, int) {
|
||||
var style Style
|
||||
var width int
|
||||
var str string
|
||||
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
|
||||
c := &cb.cells[(y*cb.w)+x]
|
||||
str, style = c.currStr, c.currStyle
|
||||
if width = c.width; width == 0 || str == "" {
|
||||
width = 1
|
||||
str = " "
|
||||
}
|
||||
}
|
||||
return str, style, width
|
||||
}
|
||||
|
||||
// GetContent returns the contents of a character cell, including the
|
||||
// primary rune, any combining character runes (which will usually be
|
||||
// nil), the style, and the display width in cells. (The width can be
|
||||
// either 1, normally, or 2 for East Asian full-width characters.)
|
||||
//
|
||||
// Deprecated: Use Get, which this implemented in terms of.
|
||||
func (cb *CellBuffer) GetContent(x, y int) (rune, []rune, Style, int) {
|
||||
var mainc rune
|
||||
var combc []rune
|
||||
var style Style
|
||||
var width int
|
||||
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
|
||||
c := &cb.cells[(y*cb.w)+x]
|
||||
mainc, combc, style = c.currMain, c.currComb, c.currStyle
|
||||
if width = c.width; width == 0 || mainc < ' ' {
|
||||
width = 1
|
||||
mainc = ' '
|
||||
var mainc rune
|
||||
var combc []rune
|
||||
str, style, width := cb.Get(x, y)
|
||||
for i, r := range str {
|
||||
if i == 0 {
|
||||
mainc = r
|
||||
} else {
|
||||
combc = append(combc, r)
|
||||
}
|
||||
}
|
||||
return mainc, combc, style, width
|
||||
@@ -108,7 +156,7 @@ func (cb *CellBuffer) Size() (int, int) {
|
||||
// Invalidate marks all characters within the buffer as dirty.
|
||||
func (cb *CellBuffer) Invalidate() {
|
||||
for i := range cb.cells {
|
||||
cb.cells[i].lastMain = rune(0)
|
||||
cb.cells[i].lastStr = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,23 +169,12 @@ func (cb *CellBuffer) Dirty(x, y int) bool {
|
||||
if c.lock {
|
||||
return false
|
||||
}
|
||||
if c.lastMain == rune(0) {
|
||||
return true
|
||||
}
|
||||
if c.lastMain != c.currMain {
|
||||
return true
|
||||
}
|
||||
if c.lastStyle != c.currStyle {
|
||||
return true
|
||||
}
|
||||
if len(c.lastComb) != len(c.currComb) {
|
||||
if c.lastStr != c.currStr {
|
||||
return true
|
||||
}
|
||||
for i := range c.lastComb {
|
||||
if c.lastComb[i] != c.currComb[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -148,16 +185,7 @@ func (cb *CellBuffer) Dirty(x, y int) bool {
|
||||
func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
|
||||
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
|
||||
c := &cb.cells[(y*cb.w)+x]
|
||||
if dirty {
|
||||
c.lastMain = rune(0)
|
||||
} else {
|
||||
if c.currMain == rune(0) {
|
||||
c.currMain = ' '
|
||||
}
|
||||
c.lastMain = c.currMain
|
||||
c.lastComb = c.currComb
|
||||
c.lastStyle = c.currStyle
|
||||
}
|
||||
c.setDirty(dirty)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,11 +231,10 @@ func (cb *CellBuffer) Resize(w, h int) {
|
||||
for x := 0; x < w && x < cb.w; x++ {
|
||||
oc := &cb.cells[(y*cb.w)+x]
|
||||
nc := &newc[(y*w)+x]
|
||||
nc.currMain = oc.currMain
|
||||
nc.currComb = oc.currComb
|
||||
nc.currStr = oc.currStr
|
||||
nc.currStyle = oc.currStyle
|
||||
nc.width = oc.width
|
||||
nc.lastMain = rune(0)
|
||||
nc.lastStr = ""
|
||||
}
|
||||
}
|
||||
cb.cells = newc
|
||||
@@ -223,8 +250,7 @@ func (cb *CellBuffer) Resize(w, h int) {
|
||||
func (cb *CellBuffer) Fill(r rune, style Style) {
|
||||
for i := range cb.cells {
|
||||
c := &cb.cells[i]
|
||||
c.currMain = r
|
||||
c.currComb = nil
|
||||
c.currStr = string(r)
|
||||
cs := style
|
||||
if cs.fg == ColorNone {
|
||||
cs.fg = c.currStyle.fg
|
||||
@@ -236,14 +262,3 @@ func (cb *CellBuffer) Fill(r rune, style Style) {
|
||||
c.width = 1
|
||||
}
|
||||
}
|
||||
|
||||
var runeConfig *runewidth.Condition
|
||||
|
||||
func init() {
|
||||
// The defaults for the runewidth package are poorly chosen for terminal
|
||||
// applications. We however will honor the setting in the environment if
|
||||
// it is set.
|
||||
if os.Getenv("RUNEWIDTH_EASTASIAN") == "" {
|
||||
runewidth.DefaultCondition.EastAsianWidth = false
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
//go:build plan9
|
||||
// +build plan9
|
||||
|
||||
// Copyright 2025 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
// You may obtain a copy of the license at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tcell
|
||||
|
||||
// Plan 9 uses UTF-8 system-wide, so we return "UTF-8" unconditionally.
|
||||
func getCharset() string {
|
||||
return "UTF-8"
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//go:build plan9 || nacl
|
||||
// +build plan9 nacl
|
||||
//go:build nacl
|
||||
// +build nacl
|
||||
|
||||
// Copyright 2015 The TCell Authors
|
||||
//
|
||||
|
||||
+1
-1
@@ -18,5 +18,5 @@
|
||||
package tcell
|
||||
|
||||
func getCharset() string {
|
||||
return "UTF-16"
|
||||
return "UTF-8"
|
||||
}
|
||||
|
||||
+90
-213
@@ -1,7 +1,7 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
// Copyright 2024 The TCell Authors
|
||||
// Copyright 2025 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
@@ -38,7 +38,6 @@ type cScreen struct {
|
||||
cury int
|
||||
style Style
|
||||
fini bool
|
||||
vten bool
|
||||
truecolor bool
|
||||
running bool
|
||||
disableAlt bool // disable the alternate screen
|
||||
@@ -106,7 +105,6 @@ var winColors = map[Color]Color{
|
||||
}
|
||||
|
||||
var (
|
||||
k32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
u32 = syscall.NewLazyDLL("user32.dll")
|
||||
)
|
||||
|
||||
@@ -117,18 +115,8 @@ var (
|
||||
// characters (Unicode) are in use. The documentation refers to them
|
||||
// without this suffix, as the resolution is made via preprocessor.
|
||||
var (
|
||||
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
|
||||
procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects")
|
||||
procCreateEvent = k32.NewProc("CreateEventW")
|
||||
procSetEvent = k32.NewProc("SetEvent")
|
||||
procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
|
||||
procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo")
|
||||
procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition")
|
||||
procSetConsoleMode = k32.NewProc("SetConsoleMode")
|
||||
procGetConsoleMode = k32.NewProc("GetConsoleMode")
|
||||
procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
|
||||
procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute")
|
||||
procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW")
|
||||
procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo")
|
||||
procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
|
||||
procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute")
|
||||
@@ -195,6 +183,10 @@ var vtCursorStyles = map[CursorStyle]string{
|
||||
// NewConsoleScreen returns a Screen for the Windows console associated
|
||||
// with the current process. The Screen makes use of the Windows Console
|
||||
// API to display content and read events.
|
||||
//
|
||||
// Deprecated: The console API based implementation will be fully replaced
|
||||
// with the VT based model. Use NewScreen() to get a reasonable screen
|
||||
// by default.
|
||||
func NewConsoleScreen() (Screen, error) {
|
||||
return &baseScreen{screenImpl: &cScreen{}}, nil
|
||||
}
|
||||
@@ -217,22 +209,11 @@ func (s *cScreen) Init() error {
|
||||
|
||||
s.truecolor = true
|
||||
|
||||
// ConEmu handling of colors and scrolling when in VT output mode is extremely poor.
|
||||
// The color palette will scroll even though characters do not, when
|
||||
// emitting stuff for the last character. In the future we might change this to
|
||||
// look at specific versions of ConEmu if they fix the bug.
|
||||
// We can also try disabling auto margin mode.
|
||||
tryVt := true
|
||||
if os.Getenv("ConEmuPID") != "" {
|
||||
s.truecolor = false
|
||||
tryVt = false
|
||||
}
|
||||
switch os.Getenv("TCELL_TRUECOLOR") {
|
||||
case "disable":
|
||||
s.truecolor = false
|
||||
case "enable":
|
||||
s.truecolor = true
|
||||
tryVt = true
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
@@ -249,33 +230,17 @@ func (s *cScreen) Init() error {
|
||||
s.fini = false
|
||||
s.setInMode(modeResizeEn | modeExtendFlg)
|
||||
|
||||
// If a user needs to force old style console, they may do so
|
||||
// by setting TCELL_VTMODE to disable. This is an undocumented safety net for now.
|
||||
// It may be removed in the future. (This mostly exists because of ConEmu.)
|
||||
switch os.Getenv("TCELL_VTMODE") {
|
||||
case "disable":
|
||||
tryVt = false
|
||||
case "enable":
|
||||
tryVt = true
|
||||
}
|
||||
switch os.Getenv("TCELL_ALTSCREEN") {
|
||||
case "enable":
|
||||
s.disableAlt = false // also the default
|
||||
case "disable":
|
||||
s.disableAlt = true
|
||||
}
|
||||
if tryVt {
|
||||
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
|
||||
var om uint32
|
||||
s.getOutMode(&om)
|
||||
if om&modeVtOutput == modeVtOutput {
|
||||
s.vten = true
|
||||
} else {
|
||||
s.truecolor = false
|
||||
s.setOutMode(0)
|
||||
}
|
||||
} else {
|
||||
s.setOutMode(0)
|
||||
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
|
||||
var om uint32
|
||||
s.getOutMode(&om)
|
||||
if om&modeVtOutput != modeVtOutput {
|
||||
return errors.New("failed to initialize: VT output not supported?")
|
||||
}
|
||||
|
||||
s.Unlock()
|
||||
@@ -349,17 +314,12 @@ func (s *cScreen) disengage() {
|
||||
|
||||
s.wg.Wait()
|
||||
|
||||
if s.vten {
|
||||
s.emitVtString(vtCursorStyles[CursorStyleDefault])
|
||||
s.emitVtString(vtCursorColorReset)
|
||||
s.emitVtString(vtEnableAm)
|
||||
if !s.disableAlt {
|
||||
s.emitVtString(vtRestoreTitle)
|
||||
s.emitVtString(vtExitCA)
|
||||
}
|
||||
} else if !s.disableAlt {
|
||||
s.clearScreen(StyleDefault, s.vten)
|
||||
s.setCursorPos(0, 0, false)
|
||||
s.emitVtString(vtCursorStyles[CursorStyleDefault])
|
||||
s.emitVtString(vtCursorColorReset)
|
||||
s.emitVtString(vtEnableAm)
|
||||
if !s.disableAlt {
|
||||
s.emitVtString(vtRestoreTitle)
|
||||
s.emitVtString(vtExitCA)
|
||||
}
|
||||
s.setCursorInfo(&s.ocursor)
|
||||
s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
|
||||
@@ -388,22 +348,18 @@ func (s *cScreen) engage() error {
|
||||
s.running = true
|
||||
s.cancelflag = syscall.Handle(cf)
|
||||
s.enableMouse(s.mouseEnabled)
|
||||
|
||||
if s.vten {
|
||||
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
|
||||
if !s.disableAlt {
|
||||
s.emitVtString(vtSaveTitle)
|
||||
s.emitVtString(vtEnterCA)
|
||||
}
|
||||
s.emitVtString(vtDisableAm)
|
||||
if s.title != "" {
|
||||
s.emitVtString(fmt.Sprintf(vtSetTitle, s.title))
|
||||
}
|
||||
} else {
|
||||
s.setOutMode(0)
|
||||
s.setInMode(modeVtInput | modeResizeEn | modeExtendFlg)
|
||||
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
|
||||
if !s.disableAlt {
|
||||
s.emitVtString(vtSaveTitle)
|
||||
s.emitVtString(vtEnterCA)
|
||||
}
|
||||
s.emitVtString(vtDisableAm)
|
||||
if s.title != "" {
|
||||
s.emitVtString(fmt.Sprintf(vtSetTitle, s.title))
|
||||
}
|
||||
|
||||
s.clearScreen(s.style, s.vten)
|
||||
s.clearScreen(s.style)
|
||||
s.hideCursor()
|
||||
|
||||
s.cells.Invalidate()
|
||||
@@ -445,26 +401,18 @@ func (s *cScreen) emitVtString(vs string) {
|
||||
}
|
||||
|
||||
func (s *cScreen) showCursor() {
|
||||
if s.vten {
|
||||
s.emitVtString(vtShowCursor)
|
||||
s.emitVtString(vtCursorStyles[s.cursorStyle])
|
||||
if s.cursorColor == ColorReset {
|
||||
s.emitVtString(vtCursorColorReset)
|
||||
} else if s.cursorColor.Valid() {
|
||||
r, g, b := s.cursorColor.RGB()
|
||||
s.emitVtString(fmt.Sprintf(vtCursorColorRGB, r, g, b))
|
||||
}
|
||||
} else {
|
||||
s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
|
||||
s.emitVtString(vtShowCursor)
|
||||
s.emitVtString(vtCursorStyles[s.cursorStyle])
|
||||
if s.cursorColor == ColorReset {
|
||||
s.emitVtString(vtCursorColorReset)
|
||||
} else if s.cursorColor.Valid() {
|
||||
r, g, b := s.cursorColor.RGB()
|
||||
s.emitVtString(fmt.Sprintf(vtCursorColorRGB, r, g, b))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cScreen) hideCursor() {
|
||||
if s.vten {
|
||||
s.emitVtString(vtHideCursor)
|
||||
} else {
|
||||
s.setCursorInfo(&cursorInfo{size: 1, visible: 0})
|
||||
}
|
||||
s.emitVtString(vtHideCursor)
|
||||
}
|
||||
|
||||
func (s *cScreen) ShowCursor(x, y int) {
|
||||
@@ -495,7 +443,7 @@ func (s *cScreen) doCursor() {
|
||||
if x < 0 || y < 0 || x >= s.w || y >= s.h {
|
||||
s.hideCursor()
|
||||
} else {
|
||||
s.setCursorPos(x, y, s.vten)
|
||||
s.setCursorPos(x, y)
|
||||
s.showCursor()
|
||||
}
|
||||
}
|
||||
@@ -504,20 +452,6 @@ func (s *cScreen) HideCursor() {
|
||||
s.ShowCursor(-1, -1)
|
||||
}
|
||||
|
||||
type inputRecord struct {
|
||||
typ uint16
|
||||
_ uint16
|
||||
data [16]byte
|
||||
}
|
||||
|
||||
const (
|
||||
keyEvent uint16 = 1
|
||||
mouseEvent uint16 = 2
|
||||
resizeEvent uint16 = 4
|
||||
menuEvent uint16 = 8 // don't use
|
||||
focusEvent uint16 = 16
|
||||
)
|
||||
|
||||
type mouseRecord struct {
|
||||
x int16
|
||||
y int16
|
||||
@@ -655,25 +589,28 @@ var vkKeys = map[uint16]Key{
|
||||
func getu32(v []byte) uint32 {
|
||||
return uint32(v[0]) + (uint32(v[1]) << 8) + (uint32(v[2]) << 16) + (uint32(v[3]) << 24)
|
||||
}
|
||||
|
||||
func geti32(v []byte) int32 {
|
||||
return int32(getu32(v))
|
||||
}
|
||||
|
||||
func getu16(v []byte) uint16 {
|
||||
return uint16(v[0]) + (uint16(v[1]) << 8)
|
||||
}
|
||||
|
||||
func geti16(v []byte) int16 {
|
||||
return int16(getu16(v))
|
||||
}
|
||||
|
||||
// Convert windows dwControlKeyState to modifier mask
|
||||
func mod2mask(cks uint32) ModMask {
|
||||
func mod2mask(cks uint32, filter_ctrl_alt bool) ModMask {
|
||||
mm := ModNone
|
||||
// Left or right control
|
||||
ctrl := (cks & (0x0008 | 0x0004)) != 0
|
||||
// Left or right alt
|
||||
alt := (cks & (0x0002 | 0x0001)) != 0
|
||||
// Filter out ctrl+alt (it means AltGr)
|
||||
if !(ctrl && alt) {
|
||||
if !filter_ctrl_alt || !(ctrl && alt) {
|
||||
if ctrl {
|
||||
mm |= ModCtrl
|
||||
}
|
||||
@@ -787,11 +724,15 @@ func (s *cScreen) getConsoleInput() error {
|
||||
if krec.ch != 0 {
|
||||
// synthesized key code
|
||||
for krec.repeat > 0 {
|
||||
if krec.ch < ' ' && mod2mask(krec.mod, false) == ModCtrl {
|
||||
krec.ch += '\x60'
|
||||
}
|
||||
|
||||
// convert shift+tab to backtab
|
||||
if mod2mask(krec.mod) == ModShift && krec.ch == vkTab {
|
||||
if mod2mask(krec.mod, false) == ModShift && krec.ch == vkTab {
|
||||
s.postEvent(NewEventKey(KeyBacktab, 0, ModNone))
|
||||
} else {
|
||||
s.postEvent(NewEventKey(KeyRune, rune(krec.ch), mod2mask(krec.mod)))
|
||||
s.postEvent(NewEventKey(KeyRune, rune(krec.ch), mod2mask(krec.mod, true)))
|
||||
}
|
||||
krec.repeat--
|
||||
}
|
||||
@@ -803,7 +744,7 @@ func (s *cScreen) getConsoleInput() error {
|
||||
return nil
|
||||
}
|
||||
for krec.repeat > 0 {
|
||||
s.postEvent(NewEventKey(key, rune(krec.ch), mod2mask(krec.mod)))
|
||||
s.postEvent(NewEventKey(key, rune(krec.ch), mod2mask(krec.mod, false)))
|
||||
krec.repeat--
|
||||
}
|
||||
|
||||
@@ -816,7 +757,7 @@ func (s *cScreen) getConsoleInput() error {
|
||||
mrec.flags = getu32(rec.data[12:])
|
||||
btns := mrec2btns(mrec.btns, mrec.flags)
|
||||
// we ignore double click, events are delivered normally
|
||||
s.postEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns, mod2mask(mrec.mod)))
|
||||
s.postEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns, mod2mask(mrec.mod, false)))
|
||||
|
||||
case resizeEvent:
|
||||
var rrec resizeRecord
|
||||
@@ -858,11 +799,10 @@ func (s *cScreen) scanInput(stopQ chan struct{}) {
|
||||
}
|
||||
|
||||
func (s *cScreen) Colors() int {
|
||||
if s.vten {
|
||||
return 1 << 24
|
||||
if !s.truecolor {
|
||||
return 16
|
||||
}
|
||||
// Windows console can display 8 colors, in either low or high intensity
|
||||
return 16
|
||||
return 1 << 24
|
||||
}
|
||||
|
||||
var vgaColors = map[Color]uint16{
|
||||
@@ -938,7 +878,7 @@ func (s *cScreen) mapStyle(style Style) uint16 {
|
||||
return attr
|
||||
}
|
||||
|
||||
func (s *cScreen) sendVtStyle(style Style) {
|
||||
func (s *cScreen) makeVtStyle(style Style) string {
|
||||
esc := &strings.Builder{}
|
||||
|
||||
fg, bg, attrs := style.fg, style.bg, style.attrs
|
||||
@@ -998,30 +938,32 @@ func (s *cScreen) sendVtStyle(style Style) {
|
||||
esc.WriteString(vtExitUrl)
|
||||
}
|
||||
|
||||
s.emitVtString(esc.String())
|
||||
return esc.String()
|
||||
}
|
||||
|
||||
func (s *cScreen) writeString(x, y int, style Style, ch []uint16) {
|
||||
func (s *cScreen) sendVtStyle(style Style) {
|
||||
s.emitVtString(s.makeVtStyle(style))
|
||||
}
|
||||
|
||||
func (s *cScreen) writeString(x, y int, style Style, vtBuf, ch []uint16) {
|
||||
// we assume the caller has hidden the cursor
|
||||
if len(ch) == 0 {
|
||||
return
|
||||
}
|
||||
s.setCursorPos(x, y, s.vten)
|
||||
|
||||
if s.vten {
|
||||
s.sendVtStyle(style)
|
||||
} else {
|
||||
_, _, _ = procSetConsoleTextAttribute.Call(
|
||||
uintptr(s.out),
|
||||
uintptr(s.mapStyle(style)))
|
||||
}
|
||||
_ = syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil)
|
||||
vtBuf = append(vtBuf, utf16.Encode([]rune(fmt.Sprintf(vtCursorPos, y+1, x+1)))...)
|
||||
styleStr := s.makeVtStyle(style)
|
||||
vtBuf = append(vtBuf, utf16.Encode([]rune(styleStr))...)
|
||||
vtBuf = append(vtBuf, ch...)
|
||||
_ = syscall.WriteConsole(s.out, &vtBuf[0], uint32(len(vtBuf)), nil, nil)
|
||||
vtBuf = vtBuf[:0]
|
||||
}
|
||||
|
||||
func (s *cScreen) draw() {
|
||||
// allocate a scratch line bit enough for no combining chars.
|
||||
// if you have combining characters, you may pay for extra allocations.
|
||||
buf := make([]uint16, 0, s.w)
|
||||
var vtBuf []uint16
|
||||
wcs := buf[:]
|
||||
lstyle := styleInvalid
|
||||
|
||||
@@ -1040,7 +982,7 @@ func (s *cScreen) draw() {
|
||||
// write out any data queued thus far
|
||||
// because we are going to skip over some
|
||||
// cells, or because we need to change styles
|
||||
s.writeString(lx, ly, lstyle, wcs)
|
||||
s.writeString(lx, ly, lstyle, vtBuf, wcs)
|
||||
wcs = buf[0:0]
|
||||
lstyle = StyleDefault
|
||||
if !dirty {
|
||||
@@ -1067,7 +1009,7 @@ func (s *cScreen) draw() {
|
||||
}
|
||||
x += width - 1
|
||||
}
|
||||
s.writeString(lx, ly, lstyle, wcs)
|
||||
s.writeString(lx, ly, lstyle, vtBuf, wcs)
|
||||
wcs = buf[0:0]
|
||||
lstyle = styleInvalid
|
||||
}
|
||||
@@ -1122,15 +1064,9 @@ func (s *cScreen) setCursorInfo(info *cursorInfo) {
|
||||
uintptr(unsafe.Pointer(info)))
|
||||
}
|
||||
|
||||
func (s *cScreen) setCursorPos(x, y int, vtEnable bool) {
|
||||
if vtEnable {
|
||||
// Note that the string is Y first. Origin is 1,1.
|
||||
s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1))
|
||||
} else {
|
||||
_, _, _ = procSetConsoleCursorPosition.Call(
|
||||
uintptr(s.out),
|
||||
coord{int16(x), int16(y)}.uintptr())
|
||||
}
|
||||
func (s *cScreen) setCursorPos(x, y int) {
|
||||
// Note that the string is Y first. Origin is 1,1.
|
||||
s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1))
|
||||
}
|
||||
|
||||
func (s *cScreen) setBufferSize(x, y int) {
|
||||
@@ -1206,52 +1142,30 @@ func (s *cScreen) resize() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cScreen) clearScreen(style Style, vtEnable bool) {
|
||||
if vtEnable {
|
||||
s.sendVtStyle(style)
|
||||
row := strings.Repeat(" ", s.w)
|
||||
for y := 0; y < s.h; y++ {
|
||||
s.setCursorPos(0, y, vtEnable)
|
||||
s.emitVtString(row)
|
||||
}
|
||||
s.setCursorPos(0, 0, vtEnable)
|
||||
|
||||
} else {
|
||||
pos := coord{0, 0}
|
||||
attr := s.mapStyle(style)
|
||||
x, y := s.w, s.h
|
||||
scratch := uint32(0)
|
||||
count := uint32(x * y)
|
||||
|
||||
_, _, _ = procFillConsoleOutputAttribute.Call(
|
||||
uintptr(s.out),
|
||||
uintptr(attr),
|
||||
uintptr(count),
|
||||
pos.uintptr(),
|
||||
uintptr(unsafe.Pointer(&scratch)))
|
||||
_, _, _ = procFillConsoleOutputCharacter.Call(
|
||||
uintptr(s.out),
|
||||
uintptr(' '),
|
||||
uintptr(count),
|
||||
pos.uintptr(),
|
||||
uintptr(unsafe.Pointer(&scratch)))
|
||||
func (s *cScreen) clearScreen(style Style) {
|
||||
s.sendVtStyle(style)
|
||||
row := strings.Repeat(" ", s.w)
|
||||
for y := 0; y < s.h; y++ {
|
||||
s.setCursorPos(0, y)
|
||||
s.emitVtString(row)
|
||||
}
|
||||
s.setCursorPos(0, 0)
|
||||
}
|
||||
|
||||
const (
|
||||
// Input modes
|
||||
modeExtendFlg uint32 = 0x0080
|
||||
modeMouseEn = 0x0010
|
||||
modeResizeEn = 0x0008
|
||||
// modeCooked = 0x0001
|
||||
// modeVtInput = 0x0200
|
||||
modeExtendFlg = uint32(0x0080)
|
||||
modeMouseEn = uint32(0x0010)
|
||||
modeResizeEn = uint32(0x0008)
|
||||
modeVtInput = uint32(0x0200)
|
||||
// modeCooked = uint32(0x0001)
|
||||
|
||||
// Output modes
|
||||
modeCookedOut uint32 = 0x0001
|
||||
modeVtOutput = 0x0004
|
||||
modeNoAutoNL = 0x0008
|
||||
modeUnderline = 0x0010 // ENABLE_LVB_GRID_WORLDWIDE, needed for underlines
|
||||
// modeWrapEOL = 0x0002
|
||||
modeCookedOut = uint32(0x0001)
|
||||
modeVtOutput = uint32(0x0004)
|
||||
modeNoAutoNL = uint32(0x0008)
|
||||
modeUnderline = uint32(0x0010) // ENABLE_LVB_GRID_WORLDWIDE, needed for underlines
|
||||
// modeWrapEOL = uint32(0x0002)
|
||||
)
|
||||
|
||||
func (s *cScreen) setInMode(mode uint32) {
|
||||
@@ -1287,9 +1201,7 @@ func (s *cScreen) SetStyle(style Style) {
|
||||
func (s *cScreen) SetTitle(title string) {
|
||||
s.Lock()
|
||||
s.title = title
|
||||
if s.vten {
|
||||
s.emitVtString(fmt.Sprintf(vtSetTitle, title))
|
||||
}
|
||||
s.emitVtString(fmt.Sprintf(vtSetTitle, title))
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
@@ -1320,43 +1232,8 @@ func (s *cScreen) GetClipboard() {
|
||||
|
||||
func (s *cScreen) Resize(int, int, int, int) {}
|
||||
|
||||
func (s *cScreen) HasKey(k Key) bool {
|
||||
// Microsoft has codes for some keys, but they are unusual,
|
||||
// so we don't include them. We include all the typical
|
||||
// 101, 105 key layout keys.
|
||||
valid := map[Key]bool{
|
||||
KeyBackspace: true,
|
||||
KeyTab: true,
|
||||
KeyEscape: true,
|
||||
KeyPause: true,
|
||||
KeyPrint: true,
|
||||
KeyPgUp: true,
|
||||
KeyPgDn: true,
|
||||
KeyEnter: true,
|
||||
KeyEnd: true,
|
||||
KeyHome: true,
|
||||
KeyLeft: true,
|
||||
KeyUp: true,
|
||||
KeyRight: true,
|
||||
KeyDown: true,
|
||||
KeyInsert: true,
|
||||
KeyDelete: true,
|
||||
KeyF1: true,
|
||||
KeyF2: true,
|
||||
KeyF3: true,
|
||||
KeyF4: true,
|
||||
KeyF5: true,
|
||||
KeyF6: true,
|
||||
KeyF7: true,
|
||||
KeyF8: true,
|
||||
KeyF9: true,
|
||||
KeyF10: true,
|
||||
KeyF11: true,
|
||||
KeyF12: true,
|
||||
KeyRune: true,
|
||||
}
|
||||
|
||||
return valid[k]
|
||||
func (s *cScreen) HasKey(_ Key) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *cScreen) Beep() error {
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright 2025 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
// You may obtain a copy of the license at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tcell
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if rw := strings.ToLower(os.Getenv("RUNEWIDTH_EASTASIAN")); rw == "1" || rw == "true" || rw == "yes" {
|
||||
uniseg.EastAsianAmbiguousWidth = 2
|
||||
} else {
|
||||
uniseg.EastAsianAmbiguousWidth = 1
|
||||
}
|
||||
}
|
||||
+944
@@ -0,0 +1,944 @@
|
||||
// Copyright 2025 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
// You may obtain a copy of the license at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file describes a generic VT input processor. It parses key sequences,
|
||||
// (input bytes) and loads them into events. It expects UTF-8 or UTF-16 as the input
|
||||
// feed, along with ECMA-48 sequences. The assumption here is that all potential
|
||||
// key sequences are unambiguous between terminal variants (analysis of extant terminfo
|
||||
// data appears to support this conjecture). This allows us to implement this once,
|
||||
// in the most efficient and terminal-agnostic way possible.
|
||||
//
|
||||
// There is unfortunately *one* conflict, with aixterm, for CSI-P - which is KeyDelete
|
||||
// in aixterm, but F1 in others.
|
||||
|
||||
package tcell
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type inpState int
|
||||
|
||||
const (
|
||||
inpStateInit = inpState(iota)
|
||||
inpStateUtf
|
||||
inpStateEsc
|
||||
inpStateCsi // control sequence introducer
|
||||
inpStateOsc // operating system command
|
||||
inpStateDcs // device control string
|
||||
inpStateSos // start of string (unused)
|
||||
inpStatePm // privacy message (unused)
|
||||
inpStateApc // application program command
|
||||
inpStateSt // string terminator
|
||||
inpStateSs2 // single shift 2
|
||||
inpStateSs3 // single shift 3
|
||||
inpStateLFK // linux F-key (not ECMA-48 compliant - bogus CSI)
|
||||
)
|
||||
|
||||
type InputProcessor interface {
|
||||
ScanUTF8([]byte)
|
||||
ScanUTF16([]uint16)
|
||||
SetSize(rows, cols int)
|
||||
}
|
||||
|
||||
func NewInputProcessor(eq chan<- Event) InputProcessor {
|
||||
return &inputProcessor{
|
||||
evch: eq,
|
||||
buf: make([]rune, 0, 128),
|
||||
}
|
||||
}
|
||||
|
||||
type inputProcessor struct {
|
||||
ut8 []byte
|
||||
ut16 []uint16
|
||||
buf []rune
|
||||
scratch []byte
|
||||
csiParams []byte
|
||||
csiInterm []byte
|
||||
escaped bool
|
||||
btnDown bool // mouse button tracking for broken terms
|
||||
state inpState
|
||||
strState inpState // saved str state (needed for ST)
|
||||
timer *time.Timer
|
||||
expire time.Time
|
||||
l sync.Mutex
|
||||
encBuf []rune
|
||||
evch chan<- Event
|
||||
rows int // used for clipping mouse coordinates
|
||||
cols int // used for clipping mouse coordinates
|
||||
surrogate rune
|
||||
nested *inputProcessor
|
||||
}
|
||||
|
||||
func (ip *inputProcessor) SetSize(w, h int) {
|
||||
if ip.nested != nil {
|
||||
ip.nested.SetSize(w, h)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
ip.l.Lock()
|
||||
ip.rows = h
|
||||
ip.cols = w
|
||||
ip.post(NewEventResize(w, h))
|
||||
ip.l.Unlock()
|
||||
}()
|
||||
}
|
||||
func (ip *inputProcessor) post(ev Event) {
|
||||
if ip.escaped {
|
||||
ip.escaped = false
|
||||
if ke, ok := ev.(*EventKey); ok {
|
||||
ev = NewEventKey(ke.Key(), ke.Rune(), ke.Modifiers()|ModAlt)
|
||||
}
|
||||
} else if ke, ok := ev.(*EventKey); ok {
|
||||
switch ke.Key() {
|
||||
case keyPasteStart:
|
||||
ev = NewEventPaste(true)
|
||||
case keyPasteEnd:
|
||||
ev = NewEventPaste(false)
|
||||
}
|
||||
}
|
||||
|
||||
ip.evch <- ev
|
||||
}
|
||||
|
||||
func (ip *inputProcessor) escTimeout() {
|
||||
ip.l.Lock()
|
||||
defer ip.l.Unlock()
|
||||
if ip.state == inpStateEsc && ip.expire.Before(time.Now()) {
|
||||
// post it
|
||||
ip.state = inpStateInit
|
||||
ip.escaped = false
|
||||
ip.post(NewEventKey(KeyEsc, 0, ModNone))
|
||||
}
|
||||
}
|
||||
|
||||
type csiParamMode struct {
|
||||
M rune // Mode
|
||||
P int // Parameter (first)
|
||||
}
|
||||
|
||||
type keyMap struct {
|
||||
Key Key
|
||||
Mod ModMask
|
||||
Rune rune
|
||||
}
|
||||
|
||||
var csiAllKeys = map[csiParamMode]keyMap{
|
||||
{M: 'A'}: {Key: KeyUp},
|
||||
{M: 'B'}: {Key: KeyDown},
|
||||
{M: 'C'}: {Key: KeyRight},
|
||||
{M: 'D'}: {Key: KeyLeft},
|
||||
{M: 'F'}: {Key: KeyEnd},
|
||||
{M: 'H'}: {Key: KeyHome},
|
||||
{M: 'L'}: {Key: KeyInsert},
|
||||
{M: 'P'}: {Key: KeyF1}, // except for aixterm, where this is Delete
|
||||
{M: 'Q'}: {Key: KeyF2},
|
||||
{M: 'S'}: {Key: KeyF4},
|
||||
{M: 'Z'}: {Key: KeyBacktab},
|
||||
{M: 'a'}: {Key: KeyUp, Mod: ModShift},
|
||||
{M: 'b'}: {Key: KeyDown, Mod: ModShift},
|
||||
{M: 'c'}: {Key: KeyRight, Mod: ModShift},
|
||||
{M: 'd'}: {Key: KeyLeft, Mod: ModShift},
|
||||
{M: 'q', P: 1}: {Key: KeyF1}, // all these 'q' are for aixterm
|
||||
{M: 'q', P: 2}: {Key: KeyF2},
|
||||
{M: 'q', P: 3}: {Key: KeyF3},
|
||||
{M: 'q', P: 4}: {Key: KeyF4},
|
||||
{M: 'q', P: 5}: {Key: KeyF5},
|
||||
{M: 'q', P: 6}: {Key: KeyF6},
|
||||
{M: 'q', P: 7}: {Key: KeyF7},
|
||||
{M: 'q', P: 8}: {Key: KeyF8},
|
||||
{M: 'q', P: 9}: {Key: KeyF9},
|
||||
{M: 'q', P: 10}: {Key: KeyF10},
|
||||
{M: 'q', P: 11}: {Key: KeyF11},
|
||||
{M: 'q', P: 12}: {Key: KeyF12},
|
||||
{M: 'q', P: 13}: {Key: KeyF13},
|
||||
{M: 'q', P: 14}: {Key: KeyF14},
|
||||
{M: 'q', P: 15}: {Key: KeyF15},
|
||||
{M: 'q', P: 16}: {Key: KeyF16},
|
||||
{M: 'q', P: 17}: {Key: KeyF17},
|
||||
{M: 'q', P: 18}: {Key: KeyF18},
|
||||
{M: 'q', P: 19}: {Key: KeyF19},
|
||||
{M: 'q', P: 20}: {Key: KeyF20},
|
||||
{M: 'q', P: 21}: {Key: KeyF21},
|
||||
{M: 'q', P: 22}: {Key: KeyF22},
|
||||
{M: 'q', P: 23}: {Key: KeyF23},
|
||||
{M: 'q', P: 24}: {Key: KeyF24},
|
||||
{M: 'q', P: 25}: {Key: KeyF25},
|
||||
{M: 'q', P: 26}: {Key: KeyF26},
|
||||
{M: 'q', P: 27}: {Key: KeyF27},
|
||||
{M: 'q', P: 28}: {Key: KeyF28},
|
||||
{M: 'q', P: 29}: {Key: KeyF29},
|
||||
{M: 'q', P: 30}: {Key: KeyF30},
|
||||
{M: 'q', P: 31}: {Key: KeyF31},
|
||||
{M: 'q', P: 32}: {Key: KeyF32},
|
||||
{M: 'q', P: 33}: {Key: KeyF33},
|
||||
{M: 'q', P: 34}: {Key: KeyF34},
|
||||
{M: 'q', P: 35}: {Key: KeyF35},
|
||||
{M: 'q', P: 36}: {Key: KeyF36},
|
||||
{M: 'q', P: 144}: {Key: KeyClear},
|
||||
{M: 'q', P: 146}: {Key: KeyEnd},
|
||||
{M: 'q', P: 150}: {Key: KeyPgUp},
|
||||
{M: 'q', P: 154}: {Key: KeyPgDn},
|
||||
{M: 'z', P: 214}: {Key: KeyHome},
|
||||
{M: 'z', P: 216}: {Key: KeyPgUp},
|
||||
{M: 'z', P: 220}: {Key: KeyEnd},
|
||||
{M: 'z', P: 222}: {Key: KeyPgDn},
|
||||
{M: 'z', P: 224}: {Key: KeyF1},
|
||||
{M: 'z', P: 225}: {Key: KeyF2},
|
||||
{M: 'z', P: 226}: {Key: KeyF3},
|
||||
{M: 'z', P: 227}: {Key: KeyF4},
|
||||
{M: 'z', P: 228}: {Key: KeyF5},
|
||||
{M: 'z', P: 229}: {Key: KeyF6},
|
||||
{M: 'z', P: 230}: {Key: KeyF7},
|
||||
{M: 'z', P: 231}: {Key: KeyF8},
|
||||
{M: 'z', P: 232}: {Key: KeyF9},
|
||||
{M: 'z', P: 233}: {Key: KeyF10},
|
||||
{M: 'z', P: 234}: {Key: KeyF11},
|
||||
{M: 'z', P: 235}: {Key: KeyF12},
|
||||
{M: 'z', P: 247}: {Key: KeyInsert},
|
||||
{M: '^', P: 7}: {Key: KeyHome, Mod: ModCtrl},
|
||||
{M: '^', P: 8}: {Key: KeyEnd, Mod: ModCtrl},
|
||||
{M: '^', P: 11}: {Key: KeyF23},
|
||||
{M: '^', P: 12}: {Key: KeyF24},
|
||||
{M: '^', P: 13}: {Key: KeyF25},
|
||||
{M: '^', P: 14}: {Key: KeyF26},
|
||||
{M: '^', P: 15}: {Key: KeyF27},
|
||||
{M: '^', P: 17}: {Key: KeyF28}, // 16 is a gap
|
||||
{M: '^', P: 18}: {Key: KeyF29},
|
||||
{M: '^', P: 19}: {Key: KeyF30},
|
||||
{M: '^', P: 20}: {Key: KeyF31},
|
||||
{M: '^', P: 21}: {Key: KeyF32},
|
||||
{M: '^', P: 23}: {Key: KeyF33}, // 22 is a gap
|
||||
{M: '^', P: 24}: {Key: KeyF34},
|
||||
{M: '^', P: 25}: {Key: KeyF35},
|
||||
{M: '^', P: 26}: {Key: KeyF36}, // 27 is a gap
|
||||
{M: '^', P: 28}: {Key: KeyF37},
|
||||
{M: '^', P: 29}: {Key: KeyF38}, // 30 is a gap
|
||||
{M: '^', P: 31}: {Key: KeyF39},
|
||||
{M: '^', P: 32}: {Key: KeyF40},
|
||||
{M: '^', P: 33}: {Key: KeyF41},
|
||||
{M: '^', P: 34}: {Key: KeyF42},
|
||||
{M: '@', P: 23}: {Key: KeyF43},
|
||||
{M: '@', P: 24}: {Key: KeyF44},
|
||||
{M: '$', P: 2}: {Key: KeyInsert, Mod: ModShift},
|
||||
{M: '$', P: 3}: {Key: KeyDelete, Mod: ModShift},
|
||||
{M: '$', P: 7}: {Key: KeyHome, Mod: ModShift},
|
||||
{M: '$', P: 8}: {Key: KeyEnd, Mod: ModShift},
|
||||
{M: '$', P: 23}: {Key: KeyF21},
|
||||
{M: '$', P: 24}: {Key: KeyF22},
|
||||
{M: '~', P: 1}: {Key: KeyHome},
|
||||
{M: '~', P: 2}: {Key: KeyInsert},
|
||||
{M: '~', P: 3}: {Key: KeyDelete},
|
||||
{M: '~', P: 4}: {Key: KeyEnd},
|
||||
{M: '~', P: 5}: {Key: KeyPgUp},
|
||||
{M: '~', P: 6}: {Key: KeyPgDn},
|
||||
{M: '~', P: 7}: {Key: KeyHome},
|
||||
{M: '~', P: 8}: {Key: KeyEnd},
|
||||
{M: '~', P: 11}: {Key: KeyF1},
|
||||
{M: '~', P: 12}: {Key: KeyF2},
|
||||
{M: '~', P: 13}: {Key: KeyF3},
|
||||
{M: '~', P: 14}: {Key: KeyF4},
|
||||
{M: '~', P: 15}: {Key: KeyF5},
|
||||
{M: '~', P: 17}: {Key: KeyF6},
|
||||
{M: '~', P: 18}: {Key: KeyF7},
|
||||
{M: '~', P: 19}: {Key: KeyF8},
|
||||
{M: '~', P: 20}: {Key: KeyF9},
|
||||
{M: '~', P: 21}: {Key: KeyF10},
|
||||
{M: '~', P: 23}: {Key: KeyF11},
|
||||
{M: '~', P: 24}: {Key: KeyF12},
|
||||
{M: '~', P: 25}: {Key: KeyF13},
|
||||
{M: '~', P: 26}: {Key: KeyF14},
|
||||
{M: '~', P: 28}: {Key: KeyF15}, // aka KeyHelp
|
||||
{M: '~', P: 29}: {Key: KeyF16},
|
||||
{M: '~', P: 31}: {Key: KeyF17},
|
||||
{M: '~', P: 32}: {Key: KeyF18},
|
||||
{M: '~', P: 33}: {Key: KeyF19},
|
||||
{M: '~', P: 34}: {Key: KeyF20},
|
||||
{M: '~', P: 200}: {Key: keyPasteStart},
|
||||
{M: '~', P: 201}: {Key: keyPasteEnd},
|
||||
}
|
||||
|
||||
// keys reported using Kitty csi-u protocol
|
||||
var csiUKeys = map[int]keyMap{
|
||||
27: {Key: KeyESC},
|
||||
9: {Key: KeyTAB},
|
||||
13: {Key: KeyEnter},
|
||||
127: {Key: KeyBS},
|
||||
57358: {Key: KeyCapsLock},
|
||||
57359: {Key: KeyScrollLock},
|
||||
57360: {Key: KeyNumLock},
|
||||
57361: {Key: KeyPrint},
|
||||
57362: {Key: KeyPause},
|
||||
57363: {Key: KeyMenu},
|
||||
57376: {Key: KeyF13},
|
||||
57377: {Key: KeyF14},
|
||||
57378: {Key: KeyF15},
|
||||
57379: {Key: KeyF16},
|
||||
57380: {Key: KeyF17},
|
||||
57381: {Key: KeyF18},
|
||||
57382: {Key: KeyF19},
|
||||
57383: {Key: KeyF20},
|
||||
57384: {Key: KeyF21},
|
||||
57385: {Key: KeyF22},
|
||||
57386: {Key: KeyF23},
|
||||
57387: {Key: KeyF24},
|
||||
57388: {Key: KeyF25},
|
||||
57389: {Key: KeyF26},
|
||||
57390: {Key: KeyF27},
|
||||
57391: {Key: KeyF28},
|
||||
57392: {Key: KeyF29},
|
||||
57393: {Key: KeyF30},
|
||||
57394: {Key: KeyF31},
|
||||
57395: {Key: KeyF32},
|
||||
57396: {Key: KeyF33},
|
||||
57397: {Key: KeyF34},
|
||||
57398: {Key: KeyF35},
|
||||
57399: {Key: KeyRune, Rune: '0'}, // KP 0
|
||||
57400: {Key: KeyRune, Rune: '1'}, // KP 1
|
||||
57401: {Key: KeyRune, Rune: '2'}, // KP 2
|
||||
57402: {Key: KeyRune, Rune: '3'}, // KP 3
|
||||
57403: {Key: KeyRune, Rune: '4'}, // KP 4
|
||||
57404: {Key: KeyRune, Rune: '5'}, // KP 5
|
||||
57405: {Key: KeyRune, Rune: '6'}, // KP 6
|
||||
57406: {Key: KeyRune, Rune: '7'}, // KP 7
|
||||
57407: {Key: KeyRune, Rune: '8'}, // KP 8
|
||||
57408: {Key: KeyRune, Rune: '9'}, // KP 9
|
||||
57409: {Key: KeyRune, Rune: '.'}, // KP_DECIMAL
|
||||
57410: {Key: KeyRune, Rune: '/'}, // KP_DIVIDE
|
||||
57411: {Key: KeyRune, Rune: '*'}, // KP_MULTIPLY
|
||||
57412: {Key: KeyRune, Rune: '-'}, // KP_SUBTRACT
|
||||
57413: {Key: KeyRune, Rune: '+'}, // KP_ADD
|
||||
57414: {Key: KeyEnter}, // KP_ENTER
|
||||
57415: {Key: KeyRune, Rune: '='}, // KP_EQUAL
|
||||
57416: {Key: KeyClear}, // KP_SEPARATOR
|
||||
57417: {Key: KeyLeft}, // KP_LEFT
|
||||
57418: {Key: KeyRight}, // KP_RIGHT
|
||||
57419: {Key: KeyUp}, // KP_UP
|
||||
57420: {Key: KeyDown}, // KP_DOWN
|
||||
57421: {Key: KeyPgUp}, // KP_PG_UP
|
||||
57422: {Key: KeyPgDn}, // KP_PG_DN
|
||||
57423: {Key: KeyHome}, // KP_HOME
|
||||
57424: {Key: KeyEnd}, // KP_END
|
||||
57425: {Key: KeyInsert}, // KP_INSERT
|
||||
57426: {Key: KeyDelete}, // KP_DELETE
|
||||
// 57427: {Key: KeyBegin}, // KP_BEGIN
|
||||
|
||||
// TODO: Media keys
|
||||
}
|
||||
|
||||
// windows virtual key codes per microsoft
|
||||
var winKeys = map[int]Key{
|
||||
0x03: KeyCancel, // vkCancel
|
||||
0x08: KeyBackspace, // vkBackspace
|
||||
0x09: KeyTab, // vkTab
|
||||
0x0c: KeyClear, // vClear
|
||||
0x0d: KeyEnter, // vkReturn
|
||||
0x13: KeyPause, // vkPause
|
||||
0x1b: KeyEscape, // vkEscape
|
||||
0x21: KeyPgUp, // vkPrior
|
||||
0x22: KeyPgDn, // vkNext
|
||||
0x23: KeyEnd, // vkEnd
|
||||
0x24: KeyHome, // vkHome
|
||||
0x25: KeyLeft, // vkLeft
|
||||
0x26: KeyUp, // vkUp
|
||||
0x27: KeyRight, // vkRight
|
||||
0x28: KeyDown, // vkDown
|
||||
0x2a: KeyPrint, // vkPrint
|
||||
0x2c: KeyPrint, // vkPrtScr
|
||||
0x2d: KeyInsert, // vkInsert
|
||||
0x2e: KeyDelete, // vkDelete
|
||||
0x2f: KeyHelp, // vkHelp
|
||||
0x70: KeyF1, // vkF1
|
||||
0x71: KeyF2, // vkF2
|
||||
0x72: KeyF3, // vkF3
|
||||
0x73: KeyF4, // vkF4
|
||||
0x74: KeyF5, // vkF5
|
||||
0x75: KeyF6, // vkF6
|
||||
0x76: KeyF7, // vkF7
|
||||
0x77: KeyF8, // vkF8
|
||||
0x78: KeyF9, // vkF9
|
||||
0x79: KeyF10, // vkF10
|
||||
0x7a: KeyF11, // vkF11
|
||||
0x7b: KeyF12, // vkF12
|
||||
0x7c: KeyF13, // vkF13
|
||||
0x7d: KeyF14, // vkF14
|
||||
0x7e: KeyF15, // vkF15
|
||||
0x7f: KeyF16, // vkF16
|
||||
0x80: KeyF17, // vkF17
|
||||
0x81: KeyF18, // vkF18
|
||||
0x82: KeyF19, // vkF19
|
||||
0x83: KeyF20, // vkF20
|
||||
0x84: KeyF21, // vkF21
|
||||
0x85: KeyF22, // vkF22
|
||||
0x86: KeyF23, // vkF23
|
||||
0x87: KeyF24, // vkF24
|
||||
}
|
||||
|
||||
// keys by their SS3 - used in application mode usually (legacy VT-style)
|
||||
var ss3Keys = map[rune]Key{
|
||||
'A': KeyUp,
|
||||
'B': KeyDown,
|
||||
'C': KeyRight,
|
||||
'D': KeyLeft,
|
||||
'F': KeyEnd,
|
||||
'H': KeyHome,
|
||||
'P': KeyF1,
|
||||
'Q': KeyF2,
|
||||
'R': KeyF3,
|
||||
'S': KeyF4,
|
||||
't': KeyF5,
|
||||
'u': KeyF6,
|
||||
'v': KeyF7,
|
||||
'l': KeyF8,
|
||||
'w': KeyF9,
|
||||
'x': KeyF10,
|
||||
}
|
||||
|
||||
// linux terminal uses these non ECMA keys prefixed by CSI-[
|
||||
var linuxFKeys = map[rune]Key{
|
||||
'A': KeyF1,
|
||||
'B': KeyF2,
|
||||
'C': KeyF3,
|
||||
'D': KeyF4,
|
||||
'E': KeyF5,
|
||||
}
|
||||
|
||||
func (ip *inputProcessor) scan() {
|
||||
for _, r := range ip.buf {
|
||||
ip.buf = ip.buf[1:]
|
||||
if r > 0x7F {
|
||||
// 8-bit extended Unicode we just treat as such - this will swallow anything else queued up
|
||||
ip.state = inpStateInit
|
||||
ip.post(NewEventKey(KeyRune, r, ModNone))
|
||||
continue
|
||||
}
|
||||
switch ip.state {
|
||||
case inpStateInit:
|
||||
switch r {
|
||||
case '\x1b':
|
||||
// escape.. pending
|
||||
ip.state = inpStateEsc
|
||||
if len(ip.buf) == 0 && ip.nested == nil {
|
||||
ip.expire = time.Now().Add(time.Millisecond * 50)
|
||||
ip.timer = time.AfterFunc(time.Millisecond*60, ip.escTimeout)
|
||||
}
|
||||
case '\t':
|
||||
ip.post(NewEventKey(KeyTab, 0, ModNone))
|
||||
case '\b', '\x7F':
|
||||
ip.post(NewEventKey(KeyBackspace, 0, ModNone))
|
||||
case '\r':
|
||||
ip.post(NewEventKey(KeyEnter, 0, ModNone))
|
||||
default:
|
||||
// Control keys - legacy handling
|
||||
if r < ' ' {
|
||||
ip.post(NewEventKey(KeyCtrlSpace+Key(r), 0, ModCtrl))
|
||||
} else {
|
||||
ip.post(NewEventKey(KeyRune, r, ModNone))
|
||||
}
|
||||
}
|
||||
case inpStateEsc:
|
||||
switch r {
|
||||
case '[':
|
||||
ip.state = inpStateCsi
|
||||
ip.csiInterm = nil
|
||||
ip.csiParams = nil
|
||||
case ']':
|
||||
ip.state = inpStateOsc
|
||||
ip.scratch = nil
|
||||
case 'N':
|
||||
ip.state = inpStateSs2 // no known uses
|
||||
ip.scratch = nil
|
||||
case 'O':
|
||||
ip.state = inpStateSs3
|
||||
ip.scratch = nil
|
||||
case 'X':
|
||||
ip.state = inpStateSos
|
||||
ip.scratch = nil
|
||||
case '^':
|
||||
ip.state = inpStatePm
|
||||
ip.scratch = nil
|
||||
case '_':
|
||||
ip.state = inpStateApc
|
||||
ip.scratch = nil
|
||||
case '\\':
|
||||
// string terminator reached, (orphaned?)
|
||||
ip.state = inpStateInit
|
||||
case '\t':
|
||||
// Linux console only, does not conform to ECMA
|
||||
ip.state = inpStateInit
|
||||
ip.post(NewEventKey(KeyBacktab, 0, ModNone))
|
||||
default:
|
||||
if r == '\x1b' {
|
||||
// leading ESC to capture alt
|
||||
ip.escaped = true
|
||||
} else {
|
||||
// treat as alt-key ... legacy emulators only (no CSI-u or other)
|
||||
ip.state = inpStateInit
|
||||
mod := ModAlt
|
||||
if r < ' ' {
|
||||
mod |= ModCtrl
|
||||
r += 0x60
|
||||
}
|
||||
ip.post(NewEventKey(KeyRune, r, mod))
|
||||
}
|
||||
}
|
||||
case inpStateCsi:
|
||||
// usual case for incoming keys
|
||||
if r == '\x1b' {
|
||||
// Per ECMA-48 §5.3.1, ESC restarts the escape
|
||||
// sequence machine from any intermediate state.
|
||||
ip.state = inpStateEsc
|
||||
if len(ip.buf) == 0 && ip.nested == nil {
|
||||
ip.expire = time.Now().Add(time.Millisecond * 50)
|
||||
ip.timer = time.AfterFunc(time.Millisecond*60, ip.escTimeout)
|
||||
}
|
||||
} else if r >= 0x30 && r <= 0x3F { // parameter bytes
|
||||
ip.csiParams = append(ip.csiParams, byte(r))
|
||||
} else if r >= 0x20 && r <= 0x2F { // intermediate bytes, rarely used
|
||||
ip.csiInterm = append(ip.csiInterm, byte(r))
|
||||
} else if r >= 0x40 && r <= 0x7F { // final byte
|
||||
ip.handleCsi(r, ip.csiParams, ip.csiInterm)
|
||||
} else {
|
||||
// bad parse, just swallow it all
|
||||
ip.state = inpStateInit
|
||||
}
|
||||
case inpStateSs2:
|
||||
// No known uses for SS2
|
||||
ip.state = inpStateInit
|
||||
|
||||
case inpStateSs3: // typically application mode keys or older terminals
|
||||
ip.state = inpStateInit
|
||||
if r == '\x1b' {
|
||||
// Per ECMA-48 §5.3.1, ESC restarts the escape
|
||||
// sequence machine from any intermediate state.
|
||||
ip.state = inpStateEsc
|
||||
if len(ip.buf) == 0 && ip.nested == nil {
|
||||
ip.expire = time.Now().Add(time.Millisecond * 50)
|
||||
ip.timer = time.AfterFunc(time.Millisecond*60, ip.escTimeout)
|
||||
}
|
||||
} else if k, ok := ss3Keys[r]; ok {
|
||||
ip.post(NewEventKey(k, 0, ModNone))
|
||||
}
|
||||
|
||||
case inpStatePm, inpStateApc, inpStateSos, inpStateDcs: // these we just eat
|
||||
switch r {
|
||||
case '\x1b':
|
||||
ip.strState = ip.state
|
||||
ip.state = inpStateSt
|
||||
case '\x07': // bell - some send this instead of ST
|
||||
ip.state = inpStateInit
|
||||
}
|
||||
|
||||
case inpStateOsc: // not sure if used
|
||||
switch r {
|
||||
case '\x1b':
|
||||
ip.strState = ip.state
|
||||
ip.state = inpStateSt
|
||||
case '\x07':
|
||||
ip.handleOsc(string(ip.scratch))
|
||||
default:
|
||||
ip.scratch = append(ip.scratch, byte(r&0x7f))
|
||||
}
|
||||
case inpStateSt:
|
||||
if r == '\\' || r == '\x07' {
|
||||
ip.state = inpStateInit
|
||||
switch ip.strState {
|
||||
case inpStateOsc:
|
||||
ip.handleOsc(string(ip.scratch))
|
||||
case inpStatePm, inpStateApc, inpStateSos, inpStateDcs:
|
||||
ip.state = inpStateInit
|
||||
}
|
||||
} else {
|
||||
ip.scratch = append(ip.scratch, '\x1b', byte(r))
|
||||
ip.state = ip.strState
|
||||
}
|
||||
case inpStateLFK:
|
||||
// linux console does not follow ECMA
|
||||
if k, ok := linuxFKeys[r]; ok {
|
||||
ip.post(NewEventKey(k, 0, ModNone))
|
||||
}
|
||||
ip.state = inpStateInit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ip *inputProcessor) handleOsc(str string) {
|
||||
ip.state = inpStateInit
|
||||
if content, ok := strings.CutPrefix(str, "52;c;"); ok {
|
||||
decoded := make([]byte, base64.StdEncoding.DecodedLen(len(content)))
|
||||
if count, err := base64.StdEncoding.Decode(decoded, []byte(content)); err == nil {
|
||||
ip.post(NewEventClipboard(decoded[:count]))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func calcModifier(n int) ModMask {
|
||||
n--
|
||||
m := ModNone
|
||||
if n&1 != 0 {
|
||||
m |= ModShift
|
||||
}
|
||||
if n&2 != 0 {
|
||||
m |= ModAlt
|
||||
}
|
||||
if n&4 != 0 {
|
||||
m |= ModCtrl
|
||||
}
|
||||
if n&8 != 0 {
|
||||
m |= ModMeta // kitty calls this Super
|
||||
}
|
||||
if n&16 != 0 {
|
||||
m |= ModHyper
|
||||
}
|
||||
if n&32 != 0 {
|
||||
m |= ModMeta // for now not separating from Super
|
||||
}
|
||||
// Not doing (kitty only):
|
||||
// caps_lock 0b1000000 (64)
|
||||
// num_lock 0b10000000 (128)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// func (ip *inputProcessor) handleMouse(x, y, btn int, down bool) *EventMouse {
|
||||
func (ip *inputProcessor) handleMouse(mode rune, params []int) {
|
||||
|
||||
// XTerm mouse events only report at most one button at a time,
|
||||
// which may include a wheel button. Wheel motion events are
|
||||
// reported as single impulses, while other button events are reported
|
||||
// as separate press & release events.
|
||||
if len(params) < 3 {
|
||||
return
|
||||
}
|
||||
btn := params[0]
|
||||
// Some terminals will report mouse coordinates outside the
|
||||
// screen, especially with click-drag events. Clip the coordinates
|
||||
// to the screen in that case.
|
||||
x := max(min(params[1]-1, ip.cols-1), 0)
|
||||
y := max(min(params[2]-1, ip.rows-1), 0)
|
||||
motion := (btn & 0x20) != 0
|
||||
scroll := (btn & 0x42) == 0x40
|
||||
btn &^= 0x20
|
||||
if mode == 'm' {
|
||||
// mouse release, clear all buttons
|
||||
btn |= 3
|
||||
btn &^= 0x40
|
||||
ip.btnDown = false
|
||||
} else if motion {
|
||||
/*
|
||||
* Some broken terminals appear to send
|
||||
* mouse button one motion events, instead of
|
||||
* encoding 35 (no buttons) into these events.
|
||||
* We resolve these by looking for a non-motion
|
||||
* event first.
|
||||
*/
|
||||
if !ip.btnDown {
|
||||
btn |= 3
|
||||
btn &^= 0x40
|
||||
}
|
||||
} else if !scroll {
|
||||
ip.btnDown = true
|
||||
}
|
||||
|
||||
button := ButtonNone
|
||||
mod := ModNone
|
||||
|
||||
// Mouse wheel has bit 6 set, no release events. It should be noted
|
||||
// that wheel events are sometimes misdelivered as mouse button events
|
||||
// during a click-drag, so we debounce these, considering them to be
|
||||
// button press events unless we see an intervening release event.
|
||||
switch btn & 0x43 {
|
||||
case 0:
|
||||
button = Button1
|
||||
case 1:
|
||||
button = Button3 // Note we prefer to treat right as button 2
|
||||
case 2:
|
||||
button = Button2 // And the middle button as button 3
|
||||
case 3:
|
||||
button = ButtonNone
|
||||
case 0x40:
|
||||
button = WheelUp
|
||||
case 0x41:
|
||||
button = WheelDown
|
||||
case 0x42:
|
||||
button = WheelLeft
|
||||
case 0x43:
|
||||
button = WheelRight
|
||||
}
|
||||
|
||||
if btn&0x4 != 0 {
|
||||
mod |= ModShift
|
||||
}
|
||||
if btn&0x8 != 0 {
|
||||
mod |= ModAlt
|
||||
}
|
||||
if btn&0x10 != 0 {
|
||||
mod |= ModCtrl
|
||||
}
|
||||
|
||||
ip.post(NewEventMouse(x, y, button, mod))
|
||||
}
|
||||
|
||||
func (ip *inputProcessor) handleWinKey(P []int) {
|
||||
// win32-input-mode
|
||||
// ^[ [ Vk ; Sc ; Uc ; Kd ; Cs ; Rc _
|
||||
// Vk: the value of wVirtualKeyCode - any number. If omitted, defaults to '0'.
|
||||
// Sc: the value of wVirtualScanCode - any number. If omitted, defaults to '0'.
|
||||
// Uc: the decimal value of UnicodeChar - for example, NUL is "0", LF is
|
||||
// "10", the character 'A' is "65". If omitted, defaults to '0'.
|
||||
// Kd: the value of bKeyDown - either a '0' or '1'. If omitted, defaults to '0'.
|
||||
// Cs: the value of dwControlKeyState - any number. If omitted, defaults to '0'.
|
||||
// Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'.
|
||||
//
|
||||
// Note that some 3rd party terminal emulators (not Terminal) suffer from a bug
|
||||
// where other events, such as mouse events, are doubly encoded, using Vk 0
|
||||
// for each character. (So a CSI-M sequence is encoded as a series of CSI-_
|
||||
// sequences.) We consider this a bug in those terminal emulators -- Windows 11
|
||||
// Terminal does not suffer this brain damage. (We've observed this with both Alacritty
|
||||
// and WezTerm.)
|
||||
for len(P) < 6 {
|
||||
P = append(P, 0) // ensure sufficient length
|
||||
}
|
||||
if P[3] == 0 {
|
||||
// key up event ignore ignore
|
||||
return
|
||||
}
|
||||
|
||||
if P[0] == 0 && P[1] == 0 && P[2] > 0 && P[2] < 0x80 { // only ASCII in win32-input-mode
|
||||
if ip.nested == nil {
|
||||
ip.nested = &inputProcessor{
|
||||
evch: ip.evch,
|
||||
rows: ip.rows,
|
||||
cols: ip.cols,
|
||||
}
|
||||
}
|
||||
|
||||
ip.nested.ScanUTF8([]byte{byte(P[2])})
|
||||
return
|
||||
}
|
||||
|
||||
key := KeyRune
|
||||
chr := rune(P[2])
|
||||
mod := ModNone
|
||||
rpt := max(1, P[5])
|
||||
if k1, ok := winKeys[P[0]]; ok {
|
||||
chr = 0
|
||||
key = k1
|
||||
} else if chr == 0 && P[0] >= 0x30 && P[0] <= 0x39 {
|
||||
chr = rune(P[0])
|
||||
} else if chr < ' ' && P[0] >= 0x41 && P[0] <= 0x5a {
|
||||
key = Key(P[0])
|
||||
chr = 0
|
||||
|
||||
} else if chr >= 0xD800 && chr <= 0xDBFF {
|
||||
// high surrogate pair
|
||||
ip.surrogate = chr
|
||||
return
|
||||
} else if chr >= 0xDC00 && chr <= 0xDFFF {
|
||||
// low surrogate pair
|
||||
chr = utf16.DecodeRune(ip.surrogate, chr)
|
||||
} else if P[0] == 0x10 || P[0] == 0x11 || P[0] == 0x12 || P[0] == 0x14 {
|
||||
// lone modifiers
|
||||
ip.surrogate = 0
|
||||
return
|
||||
}
|
||||
|
||||
ip.surrogate = 0
|
||||
|
||||
// Modifiers
|
||||
if P[4]&0x010 != 0 {
|
||||
mod |= ModShift
|
||||
}
|
||||
if P[4]&0x000c != 0 {
|
||||
mod |= ModCtrl
|
||||
}
|
||||
if P[4]&0x0003 != 0 {
|
||||
mod |= ModAlt
|
||||
}
|
||||
if key == KeyRune && chr > ' ' && mod == ModShift {
|
||||
// filter out lone shift for printable chars
|
||||
mod = ModNone
|
||||
}
|
||||
if chr != 0 && mod&(ModCtrl|ModAlt) == ModCtrl|ModAlt {
|
||||
// Filter out ctrl+alt (it means AltGr)
|
||||
mod = ModNone
|
||||
}
|
||||
|
||||
for range rpt {
|
||||
if key != KeyRune || chr != 0 {
|
||||
ip.post(NewEventKey(key, chr, mod))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ip *inputProcessor) handleCsi(mode rune, params []byte, intermediate []byte) {
|
||||
|
||||
// reset state
|
||||
ip.state = inpStateInit
|
||||
|
||||
if len(intermediate) != 0 {
|
||||
// we don't know what to do with these for now
|
||||
return
|
||||
}
|
||||
|
||||
var parts []string
|
||||
var P []int
|
||||
hasLT := false
|
||||
pstr := string(params)
|
||||
// extract numeric parameters
|
||||
if strings.HasPrefix(pstr, "<") {
|
||||
hasLT = true
|
||||
pstr = pstr[1:]
|
||||
}
|
||||
if pstr != "" && pstr[0] >= '0' && pstr[0] <= '9' {
|
||||
parts = strings.Split(pstr, ";")
|
||||
for i := range parts {
|
||||
if parts[i] != "" {
|
||||
if n, e := strconv.ParseInt(parts[i], 10, 32); e == nil {
|
||||
P = append(P, int(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var P0 int
|
||||
if len(P) > 0 {
|
||||
P0 = P[0]
|
||||
}
|
||||
|
||||
if hasLT {
|
||||
switch mode {
|
||||
case 'm', 'M': // mouse event, we only do SGR tracking
|
||||
ip.handleMouse(mode, P)
|
||||
}
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case 'I': // focus in
|
||||
ip.post(NewEventFocus(true))
|
||||
return
|
||||
case 'O': // focus out
|
||||
ip.post(NewEventFocus(false))
|
||||
return
|
||||
case '[':
|
||||
// linux console F-key - CSI-[ modifies next key
|
||||
ip.state = inpStateLFK
|
||||
return
|
||||
case 'u':
|
||||
// CSI-u kitty keyboard protocol
|
||||
if len(P) > 0 && !hasLT {
|
||||
mod := ModNone
|
||||
key := KeyRune
|
||||
chr := rune(0)
|
||||
if k1, ok := csiUKeys[P0]; ok {
|
||||
key = k1.Key
|
||||
chr = k1.Rune
|
||||
} else {
|
||||
chr = rune(P0)
|
||||
}
|
||||
if len(P) > 1 {
|
||||
mod = calcModifier(P[1])
|
||||
}
|
||||
ip.post(NewEventKey(key, chr, mod))
|
||||
}
|
||||
return
|
||||
case '_':
|
||||
if len(intermediate) == 0 && len(P) > 0 {
|
||||
ip.handleWinKey(P)
|
||||
return
|
||||
}
|
||||
case '~':
|
||||
if len(intermediate) == 0 && len(P) >= 2 {
|
||||
mod := calcModifier(P[1])
|
||||
if ks, ok := csiAllKeys[csiParamMode{M: mode, P: P0}]; ok {
|
||||
ip.post(NewEventKey(ks.Key, 0, mod))
|
||||
return
|
||||
}
|
||||
if P0 == 27 && len(P) > 2 && P[2] > 0 && P[2] <= 0xff {
|
||||
if P[2] < ' ' || P[2] == 0x7F {
|
||||
ip.post(NewEventKey(Key(P[2]), 0, mod))
|
||||
} else {
|
||||
ip.post(NewEventKey(KeyRune, rune(P[2]), mod))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ks, ok := csiAllKeys[csiParamMode{M: mode, P: P0}]; ok && !hasLT {
|
||||
if mode == '~' && len(P) > 1 && ks.Mod == ModNone {
|
||||
// apply modifiers if present
|
||||
ks.Mod = calcModifier(P[1])
|
||||
} else if mode == 'P' && os.Getenv("TERM") == "aixterm" {
|
||||
ks.Key = KeyDelete // aixterm hack - conflicts with kitty protocol
|
||||
}
|
||||
ip.post(NewEventKey(ks.Key, 0, ks.Mod))
|
||||
return
|
||||
}
|
||||
|
||||
// this might have been an SS3 style key with modifiers applied
|
||||
if k, ok := ss3Keys[mode]; ok && P0 == 1 && len(P) > 1 {
|
||||
ip.post(NewEventKey(k, 0, calcModifier(P[1])))
|
||||
return
|
||||
}
|
||||
// if we got here we just swallow the unknown sequence
|
||||
}
|
||||
|
||||
func (ip *inputProcessor) ScanUTF8(b []byte) {
|
||||
ip.l.Lock()
|
||||
defer ip.l.Unlock()
|
||||
|
||||
ip.ut8 = append(ip.ut8, b...)
|
||||
for len(ip.ut8) > 0 {
|
||||
// fast path, basic ascii
|
||||
if ip.ut8[0] < 0x7F {
|
||||
ip.buf = append(ip.buf, rune(ip.ut8[0]))
|
||||
ip.ut8 = ip.ut8[1:]
|
||||
} else {
|
||||
r, len := utf8.DecodeRune(ip.ut8)
|
||||
if r == utf8.RuneError {
|
||||
r = rune(ip.ut8[0])
|
||||
len = 1
|
||||
}
|
||||
ip.buf = append(ip.buf, r)
|
||||
ip.ut8 = ip.ut8[len:]
|
||||
}
|
||||
}
|
||||
|
||||
ip.scan()
|
||||
}
|
||||
|
||||
func (ip *inputProcessor) ScanUTF16(u []uint16) {
|
||||
ip.l.Lock()
|
||||
defer ip.l.Unlock()
|
||||
ip.ut16 = append(ip.ut16, u...)
|
||||
for len(ip.ut16) > 0 {
|
||||
if !utf16.IsSurrogate(rune(ip.ut16[0])) {
|
||||
ip.buf = append(ip.buf, rune(ip.ut16[0]))
|
||||
ip.ut16 = ip.ut16[1:]
|
||||
} else if len(ip.ut16) > 1 {
|
||||
ip.buf = append(ip.buf, utf16.DecodeRune(rune(ip.ut16[0]), rune(ip.ut16[1])))
|
||||
ip.ut16 = ip.ut16[2:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
-12
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 The TCell Authors
|
||||
// Copyright 2025 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
@@ -171,6 +171,11 @@ var KeyNames = map[Key]string{
|
||||
KeyF62: "F62",
|
||||
KeyF63: "F63",
|
||||
KeyF64: "F64",
|
||||
KeyMenu: "Menu",
|
||||
KeyCapsLock: "CapsLock",
|
||||
KeyScrollLock: "ScrollLock",
|
||||
KeyNumLock: "NumLock",
|
||||
KeyCtrlSpace: "Ctrl-Space",
|
||||
KeyCtrlA: "Ctrl-A",
|
||||
KeyCtrlB: "Ctrl-B",
|
||||
KeyCtrlC: "Ctrl-C",
|
||||
@@ -178,9 +183,12 @@ var KeyNames = map[Key]string{
|
||||
KeyCtrlE: "Ctrl-E",
|
||||
KeyCtrlF: "Ctrl-F",
|
||||
KeyCtrlG: "Ctrl-G",
|
||||
KeyCtrlH: "Ctrl-H",
|
||||
KeyCtrlI: "Ctrl-I",
|
||||
KeyCtrlJ: "Ctrl-J",
|
||||
KeyCtrlK: "Ctrl-K",
|
||||
KeyCtrlL: "Ctrl-L",
|
||||
KeyCtrlM: "Ctrl-M",
|
||||
KeyCtrlN: "Ctrl-N",
|
||||
KeyCtrlO: "Ctrl-O",
|
||||
KeyCtrlP: "Ctrl-P",
|
||||
@@ -194,11 +202,11 @@ var KeyNames = map[Key]string{
|
||||
KeyCtrlX: "Ctrl-X",
|
||||
KeyCtrlY: "Ctrl-Y",
|
||||
KeyCtrlZ: "Ctrl-Z",
|
||||
KeyCtrlSpace: "Ctrl-Space",
|
||||
KeyCtrlUnderscore: "Ctrl-_",
|
||||
KeyCtrlLeftSq: "Ctrl-[",
|
||||
KeyCtrlRightSq: "Ctrl-]",
|
||||
KeyCtrlBackslash: "Ctrl-\\",
|
||||
KeyCtrlCarat: "Ctrl-^",
|
||||
KeyCtrlUnderscore: "Ctrl-_",
|
||||
}
|
||||
|
||||
// Name returns a printable value or the key stroke. This can be used
|
||||
@@ -218,6 +226,9 @@ func (ev *EventKey) Name() string {
|
||||
if ev.mod&ModCtrl != 0 {
|
||||
m = append(m, "Ctrl")
|
||||
}
|
||||
if ev.mod&ModHyper != 0 {
|
||||
m = append(m, "Hyper")
|
||||
}
|
||||
|
||||
ok := false
|
||||
if s, ok = KeyNames[ev.key]; !ok {
|
||||
@@ -246,15 +257,52 @@ func NewEventKey(k Key, ch rune, mod ModMask) *EventKey {
|
||||
// control characters and the DEL.
|
||||
k = Key(ch)
|
||||
if mod == ModNone && ch < ' ' {
|
||||
switch Key(ch) {
|
||||
switch k {
|
||||
case KeyBackspace, KeyTab, KeyEsc, KeyEnter:
|
||||
// these keys are directly typeable without CTRL
|
||||
default:
|
||||
// most likely entered with a CTRL keypress
|
||||
mod = ModCtrl
|
||||
}
|
||||
ch = ch + '\x60'
|
||||
}
|
||||
}
|
||||
if k == KeyRune && ch >= 'A' && ch <= 'Z' && mod == ModCtrl {
|
||||
// We don't do Ctrl-[ or backslash or those specially.
|
||||
k = KeyCtrlA + Key(ch-'A')
|
||||
}
|
||||
|
||||
// Might be lower case
|
||||
if k == KeyRune && ch >= 'a' && ch <= 'z' && mod == ModCtrl {
|
||||
// We don't do Ctrl-[ or backslash or those specially.
|
||||
k = KeyCtrlA + Key(ch-'a')
|
||||
}
|
||||
|
||||
// Windows reports ModShift for shifted keys. This is inconsistent
|
||||
// with UNIX, lets harmonize this.
|
||||
if k == KeyRune && mod == ModShift && ch != 0 {
|
||||
mod = ModNone
|
||||
}
|
||||
|
||||
if k >= KeyCtrlA && k <= KeyCtrlZ {
|
||||
if mod&ModShift != 0 {
|
||||
ch = rune((k - KeyCtrlA) + 'A')
|
||||
} else {
|
||||
ch = rune((k - KeyCtrlA) + 'a')
|
||||
}
|
||||
}
|
||||
|
||||
// Backspace2 is just another name for backspace.
|
||||
if k == KeyBackspace2 {
|
||||
k = KeyBackspace
|
||||
}
|
||||
|
||||
// Shift-Tab should be Backtab.
|
||||
if k == KeyTab && (mod&ModShift) != 0 {
|
||||
k = KeyBacktab
|
||||
mod &^= ModShift
|
||||
}
|
||||
|
||||
return &EventKey{t: time.Now(), key: k, ch: ch, mod: mod}
|
||||
}
|
||||
|
||||
@@ -272,6 +320,7 @@ const (
|
||||
ModCtrl
|
||||
ModAlt
|
||||
ModMeta
|
||||
ModHyper
|
||||
ModNone ModMask = 0
|
||||
)
|
||||
|
||||
@@ -373,6 +422,10 @@ const (
|
||||
KeyF62
|
||||
KeyF63
|
||||
KeyF64
|
||||
KeyMenu
|
||||
KeyCapsLock
|
||||
KeyScrollLock
|
||||
KeyNumLock
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -381,10 +434,12 @@ const (
|
||||
keyPasteEnd
|
||||
)
|
||||
|
||||
// These are the control keys. Note that they overlap with other keys,
|
||||
// perhaps. For example, KeyCtrlH is the same as KeyBackspace.
|
||||
// These are the control keys, they will also be reported with the
|
||||
// rune (lower case) and control modifier. If the shift key
|
||||
// or other modifiers are present then these will *NOT* be reported,
|
||||
// but reported instead as KeyRune.
|
||||
const (
|
||||
KeyCtrlSpace Key = iota
|
||||
KeyCtrlSpace Key = iota + 64
|
||||
KeyCtrlA
|
||||
KeyCtrlB
|
||||
KeyCtrlC
|
||||
@@ -461,10 +516,12 @@ const (
|
||||
|
||||
// These keys are aliases for other names.
|
||||
const (
|
||||
KeyBackspace = KeyBS
|
||||
KeyTab = KeyTAB
|
||||
KeyEsc = KeyESC
|
||||
KeyEscape = KeyESC
|
||||
KeyEnter = KeyCR
|
||||
KeyBackspace = KeyBS
|
||||
KeyTab = KeyTAB
|
||||
KeyEsc = KeyESC
|
||||
KeyEscape = KeyESC
|
||||
KeyEnter = KeyCR
|
||||
|
||||
// NB: This key will be translated to KeyBackspace
|
||||
KeyBackspace2 = KeyDEL
|
||||
)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The TCell Authors
|
||||
// Copyright 2025 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
|
||||
+70
-20
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The TCell Authors
|
||||
// Copyright 2025 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
@@ -35,17 +35,37 @@ type Screen interface {
|
||||
// is called (or Sync).
|
||||
Fill(rune, Style)
|
||||
|
||||
// SetCell is an older API, and will be removed. Please use
|
||||
// SetContent instead; SetCell is implemented in terms of SetContent.
|
||||
// Put writes the first graphme of the given string with th
|
||||
// given style at the given coordinates. (Only the first grapheme
|
||||
// occupying either one or two cells is stored.) It returns the
|
||||
// remainder of the string, and the width displayed.
|
||||
Put(x int, y int, str string, style Style) (string, int)
|
||||
|
||||
// PutStr writes a string starting at the given position, using the
|
||||
// default style. The content is clipped to the screen dimensions.
|
||||
PutStr(x int, y int, str string)
|
||||
|
||||
// PutStrStyled writes a string starting at the given position, using
|
||||
// the given style. The cont4ent is clipped to the screen dimensions.
|
||||
PutStrStyled(x int, y int, str string, style Style)
|
||||
|
||||
// SetCell is an older API, and will be removed.
|
||||
//jj
|
||||
// Deprecated: Please use Put instead.
|
||||
SetCell(x int, y int, style Style, ch ...rune)
|
||||
|
||||
// GetContent returns the contents at the given location. If the
|
||||
// Get the contents at the given location. If the
|
||||
// coordinates are out of range, then the values will be 0, nil,
|
||||
// StyleDefault. Note that the contents returned are logical contents
|
||||
// and may not actually be what is displayed, but rather are what will
|
||||
// be displayed if Show() or Sync() is called. The width is the width
|
||||
// in screen cells; most often this will be 1, but some East Asian
|
||||
// characters and emoji require two cells.
|
||||
Get(x, y int) (str string, style Style, width int)
|
||||
|
||||
// GetContent is the old way to get cell contents.
|
||||
//
|
||||
// Deprecated: Use Get() instead.
|
||||
GetContent(x, y int) (primary rune, combining []rune, style Style, width int)
|
||||
|
||||
// SetContent sets the contents of the given cell location. If
|
||||
@@ -221,6 +241,9 @@ type Screen interface {
|
||||
// fallbacks are registered, this will return true. This will
|
||||
// also return true if the terminal can replace the glyph with
|
||||
// one that is visually indistinguishable from the one requested.
|
||||
//
|
||||
// Deprecated: This is not a particularly useful or reliable function,
|
||||
// due to limitations in fonts, etc. It will be removed in the future.
|
||||
CanDisplay(r rune, checkFallbacks bool) bool
|
||||
|
||||
// Resize does nothing, since it's generally not possible to
|
||||
@@ -228,14 +251,13 @@ type Screen interface {
|
||||
// the View interface.
|
||||
Resize(int, int, int, int)
|
||||
|
||||
// HasKey returns true if the keyboard is believed to have the
|
||||
// key. In some cases a keyboard may have keys with this name
|
||||
// but no support for them, while in others a key may be reported
|
||||
// as supported but not actually be usable (such as some emulators
|
||||
// that hijack certain keys). Its best not to depend to strictly
|
||||
// on this function, but it can be used for hinting when building
|
||||
// menus, displayed hot-keys, etc. Note that KeyRune (literal
|
||||
// runes) is always true.
|
||||
// HasKey always returns true.
|
||||
//
|
||||
// Deprecated: This function always returns true. Applications
|
||||
// cannot reliably detect whether a key is supported or not with
|
||||
// modern terminal emulators. (The intended use here was to help
|
||||
// applications determine whether a given key stroke was supported
|
||||
// by the terminal, but it was never reliable.)
|
||||
HasKey(Key) bool
|
||||
|
||||
// Suspend pauses input and output processing. It also restores the
|
||||
@@ -288,10 +310,9 @@ type Screen interface {
|
||||
// NewScreen returns a default Screen suitable for the user's terminal
|
||||
// environment.
|
||||
func NewScreen() (Screen, error) {
|
||||
// Windows is happier if we try for a console screen first.
|
||||
if s, _ := NewConsoleScreen(); s != nil {
|
||||
if s, e := NewTerminfoScreen(); s != nil {
|
||||
return s, nil
|
||||
} else if s, e := NewTerminfoScreen(); s != nil {
|
||||
} else if s, _ := NewConsoleScreen(); s != nil {
|
||||
return s, nil
|
||||
} else {
|
||||
return nil, e
|
||||
@@ -382,11 +403,37 @@ type baseScreen struct {
|
||||
screenImpl
|
||||
}
|
||||
|
||||
func (b *baseScreen) Put(x int, y int, str string, style Style) (remain string, width int) {
|
||||
cells := b.GetCells()
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
return cells.Put(x, y, str, style)
|
||||
}
|
||||
|
||||
func (b *baseScreen) PutStrStyled(x int, y int, str string, style Style) {
|
||||
cells := b.GetCells()
|
||||
b.Lock()
|
||||
cols, rows := cells.Size()
|
||||
width := 0
|
||||
for str != "" && x < cols && y < rows {
|
||||
str, width = cells.Put(x, y, str, style)
|
||||
if width == 0 {
|
||||
break
|
||||
}
|
||||
x += width
|
||||
}
|
||||
defer b.Unlock()
|
||||
}
|
||||
|
||||
func (b *baseScreen) PutStr(x, y int, str string) {
|
||||
b.PutStrStyled(x, y, str, StyleDefault)
|
||||
}
|
||||
|
||||
func (b *baseScreen) SetCell(x int, y int, style Style, ch ...rune) {
|
||||
if len(ch) > 0 {
|
||||
b.SetContent(x, y, ch[0], ch[1:], style)
|
||||
b.Put(x, y, string(ch), style)
|
||||
} else {
|
||||
b.SetContent(x, y, ' ', nil, style)
|
||||
b.Put(x, y, " ", style)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,12 +448,15 @@ func (b *baseScreen) Fill(r rune, style Style) {
|
||||
b.Unlock()
|
||||
}
|
||||
|
||||
func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, st Style) {
|
||||
func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
|
||||
b.Put(x, y, string(append([]rune{mainc}, combc...)), style)
|
||||
}
|
||||
|
||||
func (b *baseScreen) Get(x, y int) (string, Style, int) {
|
||||
cells := b.GetCells()
|
||||
b.Lock()
|
||||
cells.SetContent(x, y, mainc, combc, st)
|
||||
b.Unlock()
|
||||
defer b.Unlock()
|
||||
return cells.Get(x, y)
|
||||
}
|
||||
|
||||
func (b *baseScreen) GetContent(x, y int) (rune, []rune, Style, int) {
|
||||
|
||||
+14
-3
@@ -143,6 +143,10 @@ func (s *simscreen) Init() error {
|
||||
|
||||
func (s *simscreen) Fini() {
|
||||
s.Lock()
|
||||
if s.fini {
|
||||
s.Unlock()
|
||||
return
|
||||
}
|
||||
s.fini = true
|
||||
s.back.Resize(0, 0)
|
||||
s.Unlock()
|
||||
@@ -356,11 +360,18 @@ outer:
|
||||
}
|
||||
|
||||
if b[0] < 0x80 {
|
||||
mod := ModNone
|
||||
// No encodings start with low numbered values
|
||||
if Key(b[0]) >= KeyCtrlA && Key(b[0]) <= KeyCtrlZ {
|
||||
mod = ModCtrl
|
||||
if b[0] > 0 && b[0] < ' ' { // control keys
|
||||
switch Key(b[0]) {
|
||||
case KeyESC, KeyEnter, KeyTAB:
|
||||
s.postEvent(NewEventKey(Key(b[0]), 0, 0))
|
||||
continue;
|
||||
default:
|
||||
s.postEvent(NewEventKey(Key(b[0]), rune(b[0])+'\x60', ModCtrl))
|
||||
continue
|
||||
}
|
||||
}
|
||||
mod := ModNone
|
||||
ev := NewEventKey(Key(b[0]), 0, mod)
|
||||
s.postEvent(ev)
|
||||
b = b[1:]
|
||||
|
||||
+42
-2
@@ -14,6 +14,11 @@
|
||||
|
||||
package tcell
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Style represents a complete text style, including both foreground color,
|
||||
// background color, and additional attributes such as "bold" or "underline".
|
||||
//
|
||||
@@ -164,6 +169,16 @@ func (s Style) Underline(params ...interface{}) Style {
|
||||
return s2
|
||||
}
|
||||
|
||||
// GetUnderlineStyle returns the underline style for the style.
|
||||
func (s Style) GetUnderlineStyle() UnderlineStyle {
|
||||
return s.ulStyle
|
||||
}
|
||||
|
||||
// GetUnderlineColor returns the underline color for the style.
|
||||
func (s Style) GetUnderlineColor() Color {
|
||||
return s.ulColor
|
||||
}
|
||||
|
||||
// Attributes returns a new style based on s, with its attributes set as
|
||||
// specified.
|
||||
func (s Style) Attributes(attrs AttrMask) Style {
|
||||
@@ -177,7 +192,7 @@ func (s Style) Attributes(attrs AttrMask) Style {
|
||||
// link to that Url. If the Url is empty, then this mode is turned off.
|
||||
func (s Style) Url(url string) Style {
|
||||
s2 := s
|
||||
s2.url = url
|
||||
s2.url = stripOSCControls(url)
|
||||
return s2
|
||||
}
|
||||
|
||||
@@ -187,6 +202,31 @@ func (s Style) Url(url string) Style {
|
||||
// were one Url, even if it spans multiple lines.
|
||||
func (s Style) UrlId(id string) Style {
|
||||
s2 := s
|
||||
s2.urlId = "id=" + id
|
||||
s2.urlId = "id=" + stripOSCControls(id)
|
||||
return s2
|
||||
}
|
||||
|
||||
func stripOSCControls(s string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(s))
|
||||
for i := 0; i < len(s); {
|
||||
r, size := utf8.DecodeRuneInString(s[i:])
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
c := s[i]
|
||||
if c <= 0x1f || c == 0x7f || (c >= 0x80 && c <= 0x9f) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
_ = b.WriteByte(c)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if r <= 0x1f || r == 0x7f || (r >= 0x80 && r <= 0x9f) {
|
||||
i += size
|
||||
continue
|
||||
}
|
||||
b.WriteString(s[i : i+size])
|
||||
i += size
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
+19
-71
@@ -8,76 +8,24 @@ func init() {
|
||||
|
||||
// IBM Aixterm Terminal Emulator
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "aixterm",
|
||||
Columns: 80,
|
||||
Lines: 25,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
AttrOff: "\x1b[0;10m\x1b(B",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[32m\x1b[40m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "jjkkllmmnnqqttuuvvwwxx",
|
||||
EnterAcs: "\x1b(0",
|
||||
ExitAcs: "\x1b(B",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[139q",
|
||||
KeyDelete: "\x1b[P",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1b[H",
|
||||
KeyEnd: "\x1b[146q",
|
||||
KeyPgUp: "\x1b[150q",
|
||||
KeyPgDn: "\x1b[154q",
|
||||
KeyF1: "\x1b[001q",
|
||||
KeyF2: "\x1b[002q",
|
||||
KeyF3: "\x1b[003q",
|
||||
KeyF4: "\x1b[004q",
|
||||
KeyF5: "\x1b[005q",
|
||||
KeyF6: "\x1b[006q",
|
||||
KeyF7: "\x1b[007q",
|
||||
KeyF8: "\x1b[008q",
|
||||
KeyF9: "\x1b[009q",
|
||||
KeyF10: "\x1b[010q",
|
||||
KeyF11: "\x1b[011q",
|
||||
KeyF12: "\x1b[012q",
|
||||
KeyF13: "\x1b[013q",
|
||||
KeyF14: "\x1b[014q",
|
||||
KeyF15: "\x1b[015q",
|
||||
KeyF16: "\x1b[016q",
|
||||
KeyF17: "\x1b[017q",
|
||||
KeyF18: "\x1b[018q",
|
||||
KeyF19: "\x1b[019q",
|
||||
KeyF20: "\x1b[020q",
|
||||
KeyF21: "\x1b[021q",
|
||||
KeyF22: "\x1b[022q",
|
||||
KeyF23: "\x1b[023q",
|
||||
KeyF24: "\x1b[024q",
|
||||
KeyF25: "\x1b[025q",
|
||||
KeyF26: "\x1b[026q",
|
||||
KeyF27: "\x1b[027q",
|
||||
KeyF28: "\x1b[028q",
|
||||
KeyF29: "\x1b[029q",
|
||||
KeyF30: "\x1b[030q",
|
||||
KeyF31: "\x1b[031q",
|
||||
KeyF32: "\x1b[032q",
|
||||
KeyF33: "\x1b[033q",
|
||||
KeyF34: "\x1b[034q",
|
||||
KeyF35: "\x1b[035q",
|
||||
KeyF36: "\x1b[036q",
|
||||
KeyClear: "\x1b[144q",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
AutoMargin: true,
|
||||
Name: "aixterm",
|
||||
Columns: 80,
|
||||
Lines: 25,
|
||||
Colors: 8,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
AttrOff: "\x1b[0;10m\x1b(B",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[32m\x1b[40m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "jjkkllmmnnqqttuuvvwwxx",
|
||||
EnterAcs: "\x1b(0",
|
||||
ExitAcs: "\x1b(B",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-28
@@ -12,7 +12,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 16777216,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||
@@ -36,33 +35,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
TrueColor: true,
|
||||
AutoMargin: true,
|
||||
})
|
||||
|
||||
-32
@@ -12,7 +12,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||
@@ -39,38 +38,7 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[<",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
DoubleUnderline: "\x1b[4:2m",
|
||||
CurlyUnderline: "\x1b[4:3m",
|
||||
DottedUnderline: "\x1b[4:4m",
|
||||
DashedUnderline: "\x1b[4:5m",
|
||||
XTermLike: true,
|
||||
})
|
||||
}
|
||||
|
||||
+20
-31
@@ -8,36 +8,25 @@ func init() {
|
||||
|
||||
// ansi/pc-term compatible with color
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "ansi",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
AttrOff: "\x1b[0;10m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe",
|
||||
EnterAcs: "\x1b[11m",
|
||||
ExitAcs: "\x1b[10m",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\x1b[D",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[L",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1b[H",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
AutoMargin: true,
|
||||
Name: "ansi",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
AttrOff: "\x1b[0;10m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe",
|
||||
EnterAcs: "\x1b[11m",
|
||||
ExitAcs: "\x1b[10m",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
// Generated automatically. DO NOT HAND-EDIT.
|
||||
|
||||
package beterm
|
||||
|
||||
import "github.com/gdamore/tcell/v2/terminfo"
|
||||
|
||||
func init() {
|
||||
|
||||
// BeOS Terminal
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "beterm",
|
||||
Columns: 80,
|
||||
Lines: 25,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
AttrOff: "\x1b[0;10m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?4h",
|
||||
ExitKeypad: "\x1b[?4l",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[m",
|
||||
PadChar: "\x00",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[11~",
|
||||
KeyF2: "\x1b[12~",
|
||||
KeyF3: "\x1b[13~",
|
||||
KeyF4: "\x1b[14~",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[16~",
|
||||
KeyF7: "\x1b[17~",
|
||||
KeyF8: "\x1b[18~",
|
||||
KeyF9: "\x1b[19~",
|
||||
KeyF10: "\x1b[20~",
|
||||
KeyF11: "\x1b[21~",
|
||||
KeyF12: "\x1b[22~",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
})
|
||||
}
|
||||
+1
@@ -25,6 +25,7 @@ import (
|
||||
// The following imports just register themselves --
|
||||
// these are the terminal types we aggregate in this package.
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/a/ansi"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/t/tmux"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt100"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt102"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt220"
|
||||
|
||||
+20
-54
@@ -8,59 +8,25 @@ func init() {
|
||||
|
||||
// ANSI emulation for Cygwin
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "cygwin",
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
AttrOff: "\x1b[0;10m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe",
|
||||
EnterAcs: "\x1b[11m",
|
||||
ExitAcs: "\x1b[10m",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[[A",
|
||||
KeyF2: "\x1b[[B",
|
||||
KeyF3: "\x1b[[C",
|
||||
KeyF4: "\x1b[[D",
|
||||
KeyF5: "\x1b[[E",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
Name: "cygwin",
|
||||
Colors: 8,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
AttrOff: "\x1b[0;10m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe",
|
||||
EnterAcs: "\x1b[11m",
|
||||
ExitAcs: "\x1b[10m",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
})
|
||||
}
|
||||
|
||||
-33
@@ -12,7 +12,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
@@ -34,38 +33,6 @@ func init() {
|
||||
EnableAutoMargin: "\x1b[?7h",
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\b",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[11~",
|
||||
KeyF2: "\x1b[12~",
|
||||
KeyF3: "\x1b[13~",
|
||||
KeyF4: "\x1b[14~",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
KeyHelp: "\x1b[28~",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
+3
-160
@@ -24,6 +24,7 @@ package dynamic
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -126,7 +127,7 @@ func (tc *termcap) setupterm(name string) error {
|
||||
tc.nums = make(map[string]int)
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("couldn't open terminfo ($TERM) file for %s: %w", name, err)
|
||||
}
|
||||
|
||||
// Now parse the output.
|
||||
@@ -144,9 +145,7 @@ func (tc *termcap) setupterm(name string) error {
|
||||
lines = lines[:len(lines)-1]
|
||||
}
|
||||
header := lines[0]
|
||||
if strings.HasSuffix(header, ",") {
|
||||
header = header[:len(header)-1]
|
||||
}
|
||||
header = strings.TrimSuffix(header, ",")
|
||||
names := strings.Split(header, "|")
|
||||
tc.name = names[0]
|
||||
names = names[1:]
|
||||
@@ -193,7 +192,6 @@ func LoadTerminfo(name string) (*terminfo.Terminfo, string, error) {
|
||||
t.Colors = tc.getnum("colors")
|
||||
t.Columns = tc.getnum("cols")
|
||||
t.Lines = tc.getnum("lines")
|
||||
t.Bell = tc.getstr("bel")
|
||||
t.Clear = tc.getstr("clear")
|
||||
t.EnterCA = tc.getstr("smcup")
|
||||
t.ExitCA = tc.getstr("rmcup")
|
||||
@@ -211,166 +209,11 @@ func LoadTerminfo(name string) (*terminfo.Terminfo, string, error) {
|
||||
t.SetFg = tc.getstr("setaf")
|
||||
t.SetBg = tc.getstr("setab")
|
||||
t.SetCursor = tc.getstr("cup")
|
||||
t.CursorBack1 = tc.getstr("cub1")
|
||||
t.CursorUp1 = tc.getstr("cuu1")
|
||||
t.KeyF1 = tc.getstr("kf1")
|
||||
t.KeyF2 = tc.getstr("kf2")
|
||||
t.KeyF3 = tc.getstr("kf3")
|
||||
t.KeyF4 = tc.getstr("kf4")
|
||||
t.KeyF5 = tc.getstr("kf5")
|
||||
t.KeyF6 = tc.getstr("kf6")
|
||||
t.KeyF7 = tc.getstr("kf7")
|
||||
t.KeyF8 = tc.getstr("kf8")
|
||||
t.KeyF9 = tc.getstr("kf9")
|
||||
t.KeyF10 = tc.getstr("kf10")
|
||||
t.KeyF11 = tc.getstr("kf11")
|
||||
t.KeyF12 = tc.getstr("kf12")
|
||||
t.KeyF13 = tc.getstr("kf13")
|
||||
t.KeyF14 = tc.getstr("kf14")
|
||||
t.KeyF15 = tc.getstr("kf15")
|
||||
t.KeyF16 = tc.getstr("kf16")
|
||||
t.KeyF17 = tc.getstr("kf17")
|
||||
t.KeyF18 = tc.getstr("kf18")
|
||||
t.KeyF19 = tc.getstr("kf19")
|
||||
t.KeyF20 = tc.getstr("kf20")
|
||||
t.KeyF21 = tc.getstr("kf21")
|
||||
t.KeyF22 = tc.getstr("kf22")
|
||||
t.KeyF23 = tc.getstr("kf23")
|
||||
t.KeyF24 = tc.getstr("kf24")
|
||||
t.KeyF25 = tc.getstr("kf25")
|
||||
t.KeyF26 = tc.getstr("kf26")
|
||||
t.KeyF27 = tc.getstr("kf27")
|
||||
t.KeyF28 = tc.getstr("kf28")
|
||||
t.KeyF29 = tc.getstr("kf29")
|
||||
t.KeyF30 = tc.getstr("kf30")
|
||||
t.KeyF31 = tc.getstr("kf31")
|
||||
t.KeyF32 = tc.getstr("kf32")
|
||||
t.KeyF33 = tc.getstr("kf33")
|
||||
t.KeyF34 = tc.getstr("kf34")
|
||||
t.KeyF35 = tc.getstr("kf35")
|
||||
t.KeyF36 = tc.getstr("kf36")
|
||||
t.KeyF37 = tc.getstr("kf37")
|
||||
t.KeyF38 = tc.getstr("kf38")
|
||||
t.KeyF39 = tc.getstr("kf39")
|
||||
t.KeyF40 = tc.getstr("kf40")
|
||||
t.KeyF41 = tc.getstr("kf41")
|
||||
t.KeyF42 = tc.getstr("kf42")
|
||||
t.KeyF43 = tc.getstr("kf43")
|
||||
t.KeyF44 = tc.getstr("kf44")
|
||||
t.KeyF45 = tc.getstr("kf45")
|
||||
t.KeyF46 = tc.getstr("kf46")
|
||||
t.KeyF47 = tc.getstr("kf47")
|
||||
t.KeyF48 = tc.getstr("kf48")
|
||||
t.KeyF49 = tc.getstr("kf49")
|
||||
t.KeyF50 = tc.getstr("kf50")
|
||||
t.KeyF51 = tc.getstr("kf51")
|
||||
t.KeyF52 = tc.getstr("kf52")
|
||||
t.KeyF53 = tc.getstr("kf53")
|
||||
t.KeyF54 = tc.getstr("kf54")
|
||||
t.KeyF55 = tc.getstr("kf55")
|
||||
t.KeyF56 = tc.getstr("kf56")
|
||||
t.KeyF57 = tc.getstr("kf57")
|
||||
t.KeyF58 = tc.getstr("kf58")
|
||||
t.KeyF59 = tc.getstr("kf59")
|
||||
t.KeyF60 = tc.getstr("kf60")
|
||||
t.KeyF61 = tc.getstr("kf61")
|
||||
t.KeyF62 = tc.getstr("kf62")
|
||||
t.KeyF63 = tc.getstr("kf63")
|
||||
t.KeyF64 = tc.getstr("kf64")
|
||||
t.KeyInsert = tc.getstr("kich1")
|
||||
t.KeyDelete = tc.getstr("kdch1")
|
||||
t.KeyBackspace = tc.getstr("kbs")
|
||||
t.KeyHome = tc.getstr("khome")
|
||||
t.KeyEnd = tc.getstr("kend")
|
||||
t.KeyUp = tc.getstr("kcuu1")
|
||||
t.KeyDown = tc.getstr("kcud1")
|
||||
t.KeyRight = tc.getstr("kcuf1")
|
||||
t.KeyLeft = tc.getstr("kcub1")
|
||||
t.KeyPgDn = tc.getstr("knp")
|
||||
t.KeyPgUp = tc.getstr("kpp")
|
||||
t.KeyBacktab = tc.getstr("kcbt")
|
||||
t.KeyExit = tc.getstr("kext")
|
||||
t.KeyCancel = tc.getstr("kcan")
|
||||
t.KeyPrint = tc.getstr("kprt")
|
||||
t.KeyHelp = tc.getstr("khlp")
|
||||
t.KeyClear = tc.getstr("kclr")
|
||||
t.AltChars = tc.getstr("acsc")
|
||||
t.EnterAcs = tc.getstr("smacs")
|
||||
t.ExitAcs = tc.getstr("rmacs")
|
||||
t.EnableAcs = tc.getstr("enacs")
|
||||
t.Mouse = tc.getstr("kmous")
|
||||
t.KeyShfRight = tc.getstr("kRIT")
|
||||
t.KeyShfLeft = tc.getstr("kLFT")
|
||||
t.KeyShfHome = tc.getstr("kHOM")
|
||||
t.KeyShfEnd = tc.getstr("kEND")
|
||||
|
||||
// Terminfo lacks descriptions for a bunch of modified keys,
|
||||
// but modern XTerm and emulators often have them. Let's add them,
|
||||
// if the shifted right and left arrows are defined.
|
||||
if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" {
|
||||
t.Modifiers = terminfo.ModifiersXTerm
|
||||
|
||||
t.KeyShfUp = "\x1b[1;2A"
|
||||
t.KeyShfDown = "\x1b[1;2B"
|
||||
t.KeyMetaUp = "\x1b[1;9A"
|
||||
t.KeyMetaDown = "\x1b[1;9B"
|
||||
t.KeyMetaRight = "\x1b[1;9C"
|
||||
t.KeyMetaLeft = "\x1b[1;9D"
|
||||
t.KeyAltUp = "\x1b[1;3A"
|
||||
t.KeyAltDown = "\x1b[1;3B"
|
||||
t.KeyAltRight = "\x1b[1;3C"
|
||||
t.KeyAltLeft = "\x1b[1;3D"
|
||||
t.KeyCtrlUp = "\x1b[1;5A"
|
||||
t.KeyCtrlDown = "\x1b[1;5B"
|
||||
t.KeyCtrlRight = "\x1b[1;5C"
|
||||
t.KeyCtrlLeft = "\x1b[1;5D"
|
||||
t.KeyAltShfUp = "\x1b[1;4A"
|
||||
t.KeyAltShfDown = "\x1b[1;4B"
|
||||
t.KeyAltShfRight = "\x1b[1;4C"
|
||||
t.KeyAltShfLeft = "\x1b[1;4D"
|
||||
|
||||
t.KeyMetaShfUp = "\x1b[1;10A"
|
||||
t.KeyMetaShfDown = "\x1b[1;10B"
|
||||
t.KeyMetaShfRight = "\x1b[1;10C"
|
||||
t.KeyMetaShfLeft = "\x1b[1;10D"
|
||||
|
||||
t.KeyCtrlShfUp = "\x1b[1;6A"
|
||||
t.KeyCtrlShfDown = "\x1b[1;6B"
|
||||
t.KeyCtrlShfRight = "\x1b[1;6C"
|
||||
t.KeyCtrlShfLeft = "\x1b[1;6D"
|
||||
|
||||
t.KeyShfPgUp = "\x1b[5;2~"
|
||||
t.KeyShfPgDn = "\x1b[6;2~"
|
||||
}
|
||||
// And also for Home and End
|
||||
if t.KeyShfHome == "\x1b[1;2H" && t.KeyShfEnd == "\x1b[1;2F" {
|
||||
t.KeyCtrlHome = "\x1b[1;5H"
|
||||
t.KeyCtrlEnd = "\x1b[1;5F"
|
||||
t.KeyAltHome = "\x1b[1;9H"
|
||||
t.KeyAltEnd = "\x1b[1;9F"
|
||||
t.KeyCtrlShfHome = "\x1b[1;6H"
|
||||
t.KeyCtrlShfEnd = "\x1b[1;6F"
|
||||
t.KeyAltShfHome = "\x1b[1;4H"
|
||||
t.KeyAltShfEnd = "\x1b[1;4F"
|
||||
t.KeyMetaShfHome = "\x1b[1;10H"
|
||||
t.KeyMetaShfEnd = "\x1b[1;10F"
|
||||
}
|
||||
|
||||
// And the same thing for rxvt and workalikes (Eterm, aterm, etc.)
|
||||
// It seems that urxvt at least send escaped as ALT prefix for these,
|
||||
// although some places seem to indicate a separate ALT key sesquence.
|
||||
if t.KeyShfRight == "\x1b[c" && t.KeyShfLeft == "\x1b[d" {
|
||||
t.KeyShfUp = "\x1b[a"
|
||||
t.KeyShfDown = "\x1b[b"
|
||||
t.KeyCtrlUp = "\x1b[Oa"
|
||||
t.KeyCtrlDown = "\x1b[Ob"
|
||||
t.KeyCtrlRight = "\x1b[Oc"
|
||||
t.KeyCtrlLeft = "\x1b[Od"
|
||||
}
|
||||
if t.KeyShfHome == "\x1b[7$" && t.KeyShfEnd == "\x1b[8$" {
|
||||
t.KeyCtrlHome = "\x1b[7^"
|
||||
t.KeyCtrlEnd = "\x1b[8^"
|
||||
}
|
||||
|
||||
// Technically the RGB flag that is provided for xterm-direct is not
|
||||
// quite right. The problem is that the -direct flag that was introduced
|
||||
|
||||
+32
-49
@@ -8,58 +8,41 @@ func init() {
|
||||
|
||||
// GNU Emacs term.el terminal emulation
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "eterm",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
AttrOff: "\x1b[m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Reverse: "\x1b[7m",
|
||||
PadChar: "\x00",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
AutoMargin: true,
|
||||
Name: "eterm",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
AttrOff: "\x1b[m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Reverse: "\x1b[7m",
|
||||
PadChar: "\x00",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
})
|
||||
|
||||
// Emacs term.el terminal emulator term-protocol-version 0.96
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "eterm-color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
AttrOff: "\x1b[m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[%p1%{30}%+%dm",
|
||||
SetBg: "\x1b[%p1%'('%+%dm",
|
||||
SetFgBg: "\x1b[%p1%{30}%+%d;%p2%'('%+%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
AutoMargin: true,
|
||||
Name: "eterm-color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
AttrOff: "\x1b[m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[%p1%{30}%+%dm",
|
||||
SetBg: "\x1b[%p1%'('%+%dm",
|
||||
SetFgBg: "\x1b[%p1%{30}%+%d;%p2%'('%+%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-6
@@ -24,13 +24,11 @@ import (
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/a/aixterm"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/a/alacritty"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/a/ansi"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/b/beterm"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/c/cygwin"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/d/dtterm"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/e/emacs"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/f/foot"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/g/gnome"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/h/hpterm"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/k/konsole"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/k/kterm"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/l/linux"
|
||||
@@ -46,10 +44,6 @@ import (
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt320"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt400"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt420"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt52"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/w/wy50"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/w/wy60"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/x/xfce"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm"
|
||||
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty"
|
||||
|
||||
-28
@@ -13,7 +13,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||
@@ -38,33 +37,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\u007f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-56
@@ -12,7 +12,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
@@ -39,33 +38,6 @@ func init() {
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
@@ -76,7 +48,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
@@ -103,33 +74,6 @@ func init() {
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
// Generated automatically. DO NOT HAND-EDIT.
|
||||
|
||||
package hpterm
|
||||
|
||||
import "github.com/gdamore/tcell/v2/terminfo"
|
||||
|
||||
func init() {
|
||||
|
||||
// HP X11 terminal emulator (old)
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "hpterm",
|
||||
Aliases: []string{"X-hpterm"},
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b&a0y0C\x1bJ",
|
||||
AttrOff: "\x1b&d@\x0f",
|
||||
Underline: "\x1b&dD",
|
||||
Bold: "\x1b&dB",
|
||||
Dim: "\x1b&dH",
|
||||
Reverse: "\x1b&dB",
|
||||
EnterKeypad: "\x1b&s1A",
|
||||
ExitKeypad: "\x1b&s0A",
|
||||
PadChar: "\x00",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
SetCursor: "\x1b&a%p1%dy%p2%dC",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1bA",
|
||||
KeyUp: "\x1bA",
|
||||
KeyDown: "\x1bB",
|
||||
KeyRight: "\x1bC",
|
||||
KeyLeft: "\x1bD",
|
||||
KeyInsert: "\x1bQ",
|
||||
KeyDelete: "\x1bP",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1bh",
|
||||
KeyPgUp: "\x1bV",
|
||||
KeyPgDn: "\x1bU",
|
||||
KeyF1: "\x1bp",
|
||||
KeyF2: "\x1bq",
|
||||
KeyF3: "\x1br",
|
||||
KeyF4: "\x1bs",
|
||||
KeyF5: "\x1bt",
|
||||
KeyF6: "\x1bu",
|
||||
KeyF7: "\x1bv",
|
||||
KeyF8: "\x1bw",
|
||||
KeyClear: "\x1bJ",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
-56
@@ -12,7 +12,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
@@ -40,33 +39,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[<",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
@@ -77,7 +49,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
@@ -105,33 +76,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[<",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
-32
@@ -12,7 +12,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
@@ -34,37 +33,6 @@ func init() {
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[11~",
|
||||
KeyF2: "\x1b[12~",
|
||||
KeyF3: "\x1b[13~",
|
||||
KeyF4: "\x1b[14~",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
-35
@@ -10,7 +10,6 @@ func init() {
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "linux",
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
ShowCursor: "\x1b[?25h\x1b[?0c",
|
||||
HideCursor: "\x1b[?25l\x1b[?1c",
|
||||
@@ -33,40 +32,6 @@ func init() {
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[[A",
|
||||
KeyF2: "\x1b[[B",
|
||||
KeyF3: "\x1b[[C",
|
||||
KeyF4: "\x1b[[D",
|
||||
KeyF5: "\x1b[[E",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
KeyBacktab: "\x1b\t",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
})
|
||||
|
||||
-2
@@ -1,7 +1,6 @@
|
||||
aixterm
|
||||
alacritty
|
||||
ansi
|
||||
beterm
|
||||
cygwin
|
||||
dtterm
|
||||
eterm,eterm-color|emacs
|
||||
@@ -15,7 +14,6 @@ rxvt,rxvt-256color,rxvt-88color,rxvt-unicode,rxvt-unicode-256color
|
||||
screen,screen-256color
|
||||
st,st-256color|simpleterm
|
||||
tmux,tmux-256color
|
||||
vt52
|
||||
vt100
|
||||
vt102
|
||||
vt220
|
||||
|
||||
+20
-29
@@ -8,34 +8,25 @@ func init() {
|
||||
|
||||
// ibm-pc terminal programs claiming to be ANSI
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "pcansi",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
AttrOff: "\x1b[0;10m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[37;40m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe",
|
||||
EnterAcs: "\x1b[12m",
|
||||
ExitAcs: "\x1b[10m",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\x1b[D",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1b[H",
|
||||
AutoMargin: true,
|
||||
Name: "pcansi",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
AttrOff: "\x1b[0;10m",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[37;40m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe",
|
||||
EnterAcs: "\x1b[12m",
|
||||
ExitAcs: "\x1b[10m",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
+88
-405
@@ -8,321 +8,102 @@ func init() {
|
||||
|
||||
// rxvt terminal emulator (X Window System)
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "rxvt",
|
||||
Aliases: []string{"rxvt-color"},
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b=",
|
||||
ExitKeypad: "\x1b>",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[7~",
|
||||
KeyEnd: "\x1b[8~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[11~",
|
||||
KeyF2: "\x1b[12~",
|
||||
KeyF3: "\x1b[13~",
|
||||
KeyF4: "\x1b[14~",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
KeyF21: "\x1b[23$",
|
||||
KeyF22: "\x1b[24$",
|
||||
KeyF23: "\x1b[11^",
|
||||
KeyF24: "\x1b[12^",
|
||||
KeyF25: "\x1b[13^",
|
||||
KeyF26: "\x1b[14^",
|
||||
KeyF27: "\x1b[15^",
|
||||
KeyF28: "\x1b[17^",
|
||||
KeyF29: "\x1b[18^",
|
||||
KeyF30: "\x1b[19^",
|
||||
KeyF31: "\x1b[20^",
|
||||
KeyF32: "\x1b[21^",
|
||||
KeyF33: "\x1b[23^",
|
||||
KeyF34: "\x1b[24^",
|
||||
KeyF35: "\x1b[25^",
|
||||
KeyF36: "\x1b[26^",
|
||||
KeyF37: "\x1b[28^",
|
||||
KeyF38: "\x1b[29^",
|
||||
KeyF39: "\x1b[31^",
|
||||
KeyF40: "\x1b[32^",
|
||||
KeyF41: "\x1b[33^",
|
||||
KeyF42: "\x1b[34^",
|
||||
KeyF43: "\x1b[23@",
|
||||
KeyF44: "\x1b[24@",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
KeyShfLeft: "\x1b[d",
|
||||
KeyShfRight: "\x1b[c",
|
||||
KeyShfUp: "\x1b[a",
|
||||
KeyShfDown: "\x1b[b",
|
||||
KeyShfHome: "\x1b[7$",
|
||||
KeyShfEnd: "\x1b[8$",
|
||||
KeyShfInsert: "\x1b[2$",
|
||||
KeyShfDelete: "\x1b[3$",
|
||||
KeyCtrlUp: "\x1b[Oa",
|
||||
KeyCtrlDown: "\x1b[Ob",
|
||||
KeyCtrlRight: "\x1b[Oc",
|
||||
KeyCtrlLeft: "\x1b[Od",
|
||||
KeyCtrlHome: "\x1b[7^",
|
||||
KeyCtrlEnd: "\x1b[8^",
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
Name: "rxvt",
|
||||
Aliases: []string{"rxvt-color"},
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b=",
|
||||
ExitKeypad: "\x1b>",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
// rxvt 2.7.9 with xterm 256-colors
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "rxvt-256color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b=",
|
||||
ExitKeypad: "\x1b>",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[7~",
|
||||
KeyEnd: "\x1b[8~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[11~",
|
||||
KeyF2: "\x1b[12~",
|
||||
KeyF3: "\x1b[13~",
|
||||
KeyF4: "\x1b[14~",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
KeyF21: "\x1b[23$",
|
||||
KeyF22: "\x1b[24$",
|
||||
KeyF23: "\x1b[11^",
|
||||
KeyF24: "\x1b[12^",
|
||||
KeyF25: "\x1b[13^",
|
||||
KeyF26: "\x1b[14^",
|
||||
KeyF27: "\x1b[15^",
|
||||
KeyF28: "\x1b[17^",
|
||||
KeyF29: "\x1b[18^",
|
||||
KeyF30: "\x1b[19^",
|
||||
KeyF31: "\x1b[20^",
|
||||
KeyF32: "\x1b[21^",
|
||||
KeyF33: "\x1b[23^",
|
||||
KeyF34: "\x1b[24^",
|
||||
KeyF35: "\x1b[25^",
|
||||
KeyF36: "\x1b[26^",
|
||||
KeyF37: "\x1b[28^",
|
||||
KeyF38: "\x1b[29^",
|
||||
KeyF39: "\x1b[31^",
|
||||
KeyF40: "\x1b[32^",
|
||||
KeyF41: "\x1b[33^",
|
||||
KeyF42: "\x1b[34^",
|
||||
KeyF43: "\x1b[23@",
|
||||
KeyF44: "\x1b[24@",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
KeyShfLeft: "\x1b[d",
|
||||
KeyShfRight: "\x1b[c",
|
||||
KeyShfUp: "\x1b[a",
|
||||
KeyShfDown: "\x1b[b",
|
||||
KeyShfHome: "\x1b[7$",
|
||||
KeyShfEnd: "\x1b[8$",
|
||||
KeyShfInsert: "\x1b[2$",
|
||||
KeyShfDelete: "\x1b[3$",
|
||||
KeyCtrlUp: "\x1b[Oa",
|
||||
KeyCtrlDown: "\x1b[Ob",
|
||||
KeyCtrlRight: "\x1b[Oc",
|
||||
KeyCtrlLeft: "\x1b[Od",
|
||||
KeyCtrlHome: "\x1b[7^",
|
||||
KeyCtrlEnd: "\x1b[8^",
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
Name: "rxvt-256color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b=",
|
||||
ExitKeypad: "\x1b>",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
// rxvt 2.7.9 with xterm 88-colors
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "rxvt-88color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 88,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b=",
|
||||
ExitKeypad: "\x1b>",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[7~",
|
||||
KeyEnd: "\x1b[8~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[11~",
|
||||
KeyF2: "\x1b[12~",
|
||||
KeyF3: "\x1b[13~",
|
||||
KeyF4: "\x1b[14~",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
KeyF21: "\x1b[23$",
|
||||
KeyF22: "\x1b[24$",
|
||||
KeyF23: "\x1b[11^",
|
||||
KeyF24: "\x1b[12^",
|
||||
KeyF25: "\x1b[13^",
|
||||
KeyF26: "\x1b[14^",
|
||||
KeyF27: "\x1b[15^",
|
||||
KeyF28: "\x1b[17^",
|
||||
KeyF29: "\x1b[18^",
|
||||
KeyF30: "\x1b[19^",
|
||||
KeyF31: "\x1b[20^",
|
||||
KeyF32: "\x1b[21^",
|
||||
KeyF33: "\x1b[23^",
|
||||
KeyF34: "\x1b[24^",
|
||||
KeyF35: "\x1b[25^",
|
||||
KeyF36: "\x1b[26^",
|
||||
KeyF37: "\x1b[28^",
|
||||
KeyF38: "\x1b[29^",
|
||||
KeyF39: "\x1b[31^",
|
||||
KeyF40: "\x1b[32^",
|
||||
KeyF41: "\x1b[33^",
|
||||
KeyF42: "\x1b[34^",
|
||||
KeyF43: "\x1b[23@",
|
||||
KeyF44: "\x1b[24@",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
KeyShfLeft: "\x1b[d",
|
||||
KeyShfRight: "\x1b[c",
|
||||
KeyShfUp: "\x1b[a",
|
||||
KeyShfDown: "\x1b[b",
|
||||
KeyShfHome: "\x1b[7$",
|
||||
KeyShfEnd: "\x1b[8$",
|
||||
KeyShfInsert: "\x1b[2$",
|
||||
KeyShfDelete: "\x1b[3$",
|
||||
KeyCtrlUp: "\x1b[Oa",
|
||||
KeyCtrlDown: "\x1b[Ob",
|
||||
KeyCtrlRight: "\x1b[Oc",
|
||||
KeyCtrlLeft: "\x1b[Od",
|
||||
KeyCtrlHome: "\x1b[7^",
|
||||
KeyCtrlEnd: "\x1b[8^",
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
Name: "rxvt-88color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 88,
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b=",
|
||||
ExitKeypad: "\x1b>",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
// rxvt-unicode terminal (X Window System)
|
||||
@@ -331,7 +112,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 88,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[r\x1b[?1049l",
|
||||
@@ -356,54 +136,6 @@ func init() {
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[7~",
|
||||
KeyEnd: "\x1b[8~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[11~",
|
||||
KeyF2: "\x1b[12~",
|
||||
KeyF3: "\x1b[13~",
|
||||
KeyF4: "\x1b[14~",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
KeyShfLeft: "\x1b[d",
|
||||
KeyShfRight: "\x1b[c",
|
||||
KeyShfUp: "\x1b[a",
|
||||
KeyShfDown: "\x1b[b",
|
||||
KeyShfHome: "\x1b[7$",
|
||||
KeyShfEnd: "\x1b[8$",
|
||||
KeyShfInsert: "\x1b[2$",
|
||||
KeyShfDelete: "\x1b[3$",
|
||||
KeyCtrlUp: "\x1b[Oa",
|
||||
KeyCtrlDown: "\x1b[Ob",
|
||||
KeyCtrlRight: "\x1b[Oc",
|
||||
KeyCtrlLeft: "\x1b[Od",
|
||||
KeyCtrlHome: "\x1b[7^",
|
||||
KeyCtrlEnd: "\x1b[8^",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
})
|
||||
@@ -414,7 +146,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[r\x1b[?1049l",
|
||||
@@ -439,54 +170,6 @@ func init() {
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[7~",
|
||||
KeyEnd: "\x1b[8~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1b[11~",
|
||||
KeyF2: "\x1b[12~",
|
||||
KeyF3: "\x1b[13~",
|
||||
KeyF4: "\x1b[14~",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
KeyShfLeft: "\x1b[d",
|
||||
KeyShfRight: "\x1b[c",
|
||||
KeyShfUp: "\x1b[a",
|
||||
KeyShfDown: "\x1b[b",
|
||||
KeyShfHome: "\x1b[7$",
|
||||
KeyShfEnd: "\x1b[8$",
|
||||
KeyShfInsert: "\x1b[2$",
|
||||
KeyShfDelete: "\x1b[3$",
|
||||
KeyCtrlUp: "\x1b[Oa",
|
||||
KeyCtrlDown: "\x1b[Ob",
|
||||
KeyCtrlRight: "\x1b[Oc",
|
||||
KeyCtrlLeft: "\x1b[Od",
|
||||
KeyCtrlHome: "\x1b[7^",
|
||||
KeyCtrlEnd: "\x1b[8^",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
})
|
||||
|
||||
+58
-112
@@ -8,121 +8,67 @@ func init() {
|
||||
|
||||
// VT 100/ANSI X3.64 virtual terminal
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "screen",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h\x1b=",
|
||||
ExitKeypad: "\x1b[?1l\x1b>",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1bM",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
AutoMargin: true,
|
||||
Name: "screen",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h\x1b=",
|
||||
ExitKeypad: "\x1b[?1l\x1b>",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
})
|
||||
|
||||
// GNU Screen with 256 colors
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "screen-256color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h\x1b=",
|
||||
ExitKeypad: "\x1b[?1l\x1b>",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1bM",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
AutoMargin: true,
|
||||
Name: "screen-256color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h\x1b=",
|
||||
ExitKeypad: "\x1b[?1l\x1b>",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-56
@@ -13,7 +13,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
@@ -39,33 +38,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyClear: "\x1b[3;5~",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
@@ -77,7 +49,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
@@ -103,33 +74,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyClear: "\x1b[3;5~",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
+26
-78
@@ -26,87 +26,35 @@ func init() {
|
||||
|
||||
// Sun Microsystems Inc. workstation console
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "sun",
|
||||
Aliases: []string{"sun1", "sun2"},
|
||||
Columns: 80,
|
||||
Lines: 34,
|
||||
Bell: "\a",
|
||||
Clear: "\f",
|
||||
AttrOff: "\x1b[m",
|
||||
Reverse: "\x1b[7m",
|
||||
PadChar: "\x00",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[247z",
|
||||
KeyDelete: "\u007f",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1b[214z",
|
||||
KeyEnd: "\x1b[220z",
|
||||
KeyPgUp: "\x1b[216z",
|
||||
KeyPgDn: "\x1b[222z",
|
||||
KeyF1: "\x1b[224z",
|
||||
KeyF2: "\x1b[225z",
|
||||
KeyF3: "\x1b[226z",
|
||||
KeyF4: "\x1b[227z",
|
||||
KeyF5: "\x1b[228z",
|
||||
KeyF6: "\x1b[229z",
|
||||
KeyF7: "\x1b[230z",
|
||||
KeyF8: "\x1b[231z",
|
||||
KeyF9: "\x1b[232z",
|
||||
KeyF10: "\x1b[233z",
|
||||
KeyF11: "\x1b[234z",
|
||||
KeyF12: "\x1b[235z",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
Name: "sun",
|
||||
Aliases: []string{"sun1", "sun2"},
|
||||
Columns: 80,
|
||||
Lines: 34,
|
||||
Clear: "\f",
|
||||
AttrOff: "\x1b[m",
|
||||
Reverse: "\x1b[7m",
|
||||
PadChar: "\x00",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
})
|
||||
|
||||
// Sun Microsystems Workstation console with color support (IA systems)
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "sun-color",
|
||||
Columns: 80,
|
||||
Lines: 34,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\f",
|
||||
AttrOff: "\x1b[m",
|
||||
Bold: "\x1b[1m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[38;5;%p1%dm",
|
||||
SetBg: "\x1b[48;5;%p1%dm",
|
||||
ResetFgBg: "\x1b[0m",
|
||||
PadChar: "\x00",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[247z",
|
||||
KeyDelete: "\u007f",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1b[214z",
|
||||
KeyEnd: "\x1b[220z",
|
||||
KeyPgUp: "\x1b[216z",
|
||||
KeyPgDn: "\x1b[222z",
|
||||
KeyF1: "\x1b[224z",
|
||||
KeyF2: "\x1b[225z",
|
||||
KeyF3: "\x1b[226z",
|
||||
KeyF4: "\x1b[227z",
|
||||
KeyF5: "\x1b[228z",
|
||||
KeyF6: "\x1b[229z",
|
||||
KeyF7: "\x1b[230z",
|
||||
KeyF8: "\x1b[231z",
|
||||
KeyF9: "\x1b[232z",
|
||||
KeyF10: "\x1b[233z",
|
||||
KeyF11: "\x1b[234z",
|
||||
KeyF12: "\x1b[235z",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
Name: "sun-color",
|
||||
Columns: 80,
|
||||
Lines: 34,
|
||||
Colors: 256,
|
||||
Clear: "\f",
|
||||
AttrOff: "\x1b[m",
|
||||
Bold: "\x1b[1m",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[38;5;%p1%dm",
|
||||
SetBg: "\x1b[48;5;%p1%dm",
|
||||
ResetFgBg: "\x1b[0m",
|
||||
PadChar: "\x00",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
})
|
||||
}
|
||||
|
||||
+64
-126
@@ -8,135 +8,73 @@ func init() {
|
||||
|
||||
// tmux terminal multiplexer
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "tmux",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Italic: "\x1b[3m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h\x1b=",
|
||||
ExitKeypad: "\x1b[?1l\x1b>",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1bM",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
DoubleUnderline: "\x1b[4:2m",
|
||||
CurlyUnderline: "\x1b[4:3m",
|
||||
DottedUnderline: "\x1b[4:4m",
|
||||
DashedUnderline: "\x1b[4:5m",
|
||||
Name: "tmux",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Italic: "\x1b[3m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h\x1b=",
|
||||
ExitKeypad: "\x1b[?1l\x1b>",
|
||||
SetFg: "\x1b[3%p1%dm",
|
||||
SetBg: "\x1b[4%p1%dm",
|
||||
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
// tmux with 256 colors
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "tmux-256color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Italic: "\x1b[3m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h\x1b=",
|
||||
ExitKeypad: "\x1b[?1l\x1b>",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1bM",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyEnd: "\x1b[4~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
DoubleUnderline: "\x1b[4:2m",
|
||||
CurlyUnderline: "\x1b[4:3m",
|
||||
DottedUnderline: "\x1b[4:4m",
|
||||
DashedUnderline: "\x1b[4:5m",
|
||||
Name: "tmux-256color",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
EnterCA: "\x1b[?1049h",
|
||||
ExitCA: "\x1b[?1049l",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Italic: "\x1b[3m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h\x1b=",
|
||||
ExitKeypad: "\x1b[?1l\x1b>",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
|
||||
ResetFgBg: "\x1b[39;49m",
|
||||
PadChar: "\x00",
|
||||
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b(B\x1b)0",
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
}
|
||||
|
||||
+55
-208
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The TCell Authors
|
||||
// Copyright 2025 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
@@ -41,125 +41,35 @@ var (
|
||||
// in Go, but when we write out JSON, we use the same names as terminfo.
|
||||
// The name, aliases and smous, rmous fields do not come from terminfo directly.
|
||||
type Terminfo struct {
|
||||
Name string
|
||||
Aliases []string
|
||||
Columns int // cols
|
||||
Lines int // lines
|
||||
Colors int // colors
|
||||
Bell string // bell
|
||||
Clear string // clear
|
||||
EnterCA string // smcup
|
||||
ExitCA string // rmcup
|
||||
ShowCursor string // cnorm
|
||||
HideCursor string // civis
|
||||
AttrOff string // sgr0
|
||||
Underline string // smul
|
||||
Bold string // bold
|
||||
Blink string // blink
|
||||
Reverse string // rev
|
||||
Dim string // dim
|
||||
Italic string // sitm
|
||||
EnterKeypad string // smkx
|
||||
ExitKeypad string // rmkx
|
||||
SetFg string // setaf
|
||||
SetBg string // setab
|
||||
ResetFgBg string // op
|
||||
SetCursor string // cup
|
||||
CursorBack1 string // cub1
|
||||
CursorUp1 string // cuu1
|
||||
PadChar string // pad
|
||||
KeyBackspace string // kbs
|
||||
KeyF1 string // kf1
|
||||
KeyF2 string // kf2
|
||||
KeyF3 string // kf3
|
||||
KeyF4 string // kf4
|
||||
KeyF5 string // kf5
|
||||
KeyF6 string // kf6
|
||||
KeyF7 string // kf7
|
||||
KeyF8 string // kf8
|
||||
KeyF9 string // kf9
|
||||
KeyF10 string // kf10
|
||||
KeyF11 string // kf11
|
||||
KeyF12 string // kf12
|
||||
KeyF13 string // kf13
|
||||
KeyF14 string // kf14
|
||||
KeyF15 string // kf15
|
||||
KeyF16 string // kf16
|
||||
KeyF17 string // kf17
|
||||
KeyF18 string // kf18
|
||||
KeyF19 string // kf19
|
||||
KeyF20 string // kf20
|
||||
KeyF21 string // kf21
|
||||
KeyF22 string // kf22
|
||||
KeyF23 string // kf23
|
||||
KeyF24 string // kf24
|
||||
KeyF25 string // kf25
|
||||
KeyF26 string // kf26
|
||||
KeyF27 string // kf27
|
||||
KeyF28 string // kf28
|
||||
KeyF29 string // kf29
|
||||
KeyF30 string // kf30
|
||||
KeyF31 string // kf31
|
||||
KeyF32 string // kf32
|
||||
KeyF33 string // kf33
|
||||
KeyF34 string // kf34
|
||||
KeyF35 string // kf35
|
||||
KeyF36 string // kf36
|
||||
KeyF37 string // kf37
|
||||
KeyF38 string // kf38
|
||||
KeyF39 string // kf39
|
||||
KeyF40 string // kf40
|
||||
KeyF41 string // kf41
|
||||
KeyF42 string // kf42
|
||||
KeyF43 string // kf43
|
||||
KeyF44 string // kf44
|
||||
KeyF45 string // kf45
|
||||
KeyF46 string // kf46
|
||||
KeyF47 string // kf47
|
||||
KeyF48 string // kf48
|
||||
KeyF49 string // kf49
|
||||
KeyF50 string // kf50
|
||||
KeyF51 string // kf51
|
||||
KeyF52 string // kf52
|
||||
KeyF53 string // kf53
|
||||
KeyF54 string // kf54
|
||||
KeyF55 string // kf55
|
||||
KeyF56 string // kf56
|
||||
KeyF57 string // kf57
|
||||
KeyF58 string // kf58
|
||||
KeyF59 string // kf59
|
||||
KeyF60 string // kf60
|
||||
KeyF61 string // kf61
|
||||
KeyF62 string // kf62
|
||||
KeyF63 string // kf63
|
||||
KeyF64 string // kf64
|
||||
KeyInsert string // kich1
|
||||
KeyDelete string // kdch1
|
||||
KeyHome string // khome
|
||||
KeyEnd string // kend
|
||||
KeyHelp string // khlp
|
||||
KeyPgUp string // kpp
|
||||
KeyPgDn string // knp
|
||||
KeyUp string // kcuu1
|
||||
KeyDown string // kcud1
|
||||
KeyLeft string // kcub1
|
||||
KeyRight string // kcuf1
|
||||
KeyBacktab string // kcbt
|
||||
KeyExit string // kext
|
||||
KeyClear string // kclr
|
||||
KeyPrint string // kprt
|
||||
KeyCancel string // kcan
|
||||
Mouse string // kmous
|
||||
AltChars string // acsc
|
||||
EnterAcs string // smacs
|
||||
ExitAcs string // rmacs
|
||||
EnableAcs string // enacs
|
||||
KeyShfRight string // kRIT
|
||||
KeyShfLeft string // kLFT
|
||||
KeyShfHome string // kHOM
|
||||
KeyShfEnd string // kEND
|
||||
KeyShfInsert string // kIC
|
||||
KeyShfDelete string // kDC
|
||||
Name string
|
||||
Aliases []string
|
||||
Columns int // cols
|
||||
Lines int // lines
|
||||
Colors int // colors
|
||||
Clear string // clear
|
||||
EnterCA string // smcup
|
||||
ExitCA string // rmcup
|
||||
ShowCursor string // cnorm
|
||||
HideCursor string // civis
|
||||
AttrOff string // sgr0
|
||||
Underline string // smul
|
||||
Bold string // bold
|
||||
Blink string // blink
|
||||
Reverse string // rev
|
||||
Dim string // dim
|
||||
Italic string // sitm
|
||||
EnterKeypad string // smkx
|
||||
ExitKeypad string // rmkx
|
||||
SetFg string // setaf
|
||||
SetBg string // setab
|
||||
ResetFgBg string // op
|
||||
SetCursor string // cup
|
||||
PadChar string // pad
|
||||
Mouse string // kmous
|
||||
AltChars string // acsc
|
||||
EnterAcs string // smacs
|
||||
ExitAcs string // rmacs
|
||||
EnableAcs string // enacs
|
||||
|
||||
// These are non-standard extensions to terminfo. This includes
|
||||
// true color support, and some additional keys. Its kind of bizarre
|
||||
@@ -167,95 +77,22 @@ type Terminfo struct {
|
||||
// Terminal support for these are going to vary amongst XTerm
|
||||
// emulations, so don't depend too much on them in your application.
|
||||
|
||||
StrikeThrough string // smxx
|
||||
SetFgBg string // setfgbg
|
||||
SetFgBgRGB string // setfgbgrgb
|
||||
SetFgRGB string // setfrgb
|
||||
SetBgRGB string // setbrgb
|
||||
KeyShfUp string // shift-up
|
||||
KeyShfDown string // shift-down
|
||||
KeyShfPgUp string // shift-kpp
|
||||
KeyShfPgDn string // shift-knp
|
||||
KeyCtrlUp string // ctrl-up
|
||||
KeyCtrlDown string // ctrl-left
|
||||
KeyCtrlRight string // ctrl-right
|
||||
KeyCtrlLeft string // ctrl-left
|
||||
KeyMetaUp string // meta-up
|
||||
KeyMetaDown string // meta-left
|
||||
KeyMetaRight string // meta-right
|
||||
KeyMetaLeft string // meta-left
|
||||
KeyAltUp string // alt-up
|
||||
KeyAltDown string // alt-left
|
||||
KeyAltRight string // alt-right
|
||||
KeyAltLeft string // alt-left
|
||||
KeyCtrlHome string
|
||||
KeyCtrlEnd string
|
||||
KeyMetaHome string
|
||||
KeyMetaEnd string
|
||||
KeyAltHome string
|
||||
KeyAltEnd string
|
||||
KeyAltShfUp string
|
||||
KeyAltShfDown string
|
||||
KeyAltShfLeft string
|
||||
KeyAltShfRight string
|
||||
KeyMetaShfUp string
|
||||
KeyMetaShfDown string
|
||||
KeyMetaShfLeft string
|
||||
KeyMetaShfRight string
|
||||
KeyCtrlShfUp string
|
||||
KeyCtrlShfDown string
|
||||
KeyCtrlShfLeft string
|
||||
KeyCtrlShfRight string
|
||||
KeyCtrlShfHome string
|
||||
KeyCtrlShfEnd string
|
||||
KeyAltShfHome string
|
||||
KeyAltShfEnd string
|
||||
KeyMetaShfHome string
|
||||
KeyMetaShfEnd string
|
||||
EnablePaste string // bracketed paste mode
|
||||
DisablePaste string
|
||||
PasteStart string
|
||||
PasteEnd string
|
||||
Modifiers int
|
||||
InsertChar string // string to insert a character (ich1)
|
||||
AutoMargin bool // true if writing to last cell in line advances
|
||||
TrueColor bool // true if the terminal supports direct color
|
||||
CursorDefault string
|
||||
CursorBlinkingBlock string
|
||||
CursorSteadyBlock string
|
||||
CursorBlinkingUnderline string
|
||||
CursorSteadyUnderline string
|
||||
CursorBlinkingBar string
|
||||
CursorSteadyBar string
|
||||
CursorColor string // nothing uses it yet
|
||||
CursorColorRGB string // Cs (but not really because Cs uses X11 color string)
|
||||
CursorColorReset string // Cr
|
||||
EnterUrl string
|
||||
ExitUrl string
|
||||
SetWindowSize string
|
||||
SetWindowTitle string // no terminfo extension
|
||||
EnableFocusReporting string
|
||||
DisableFocusReporting string
|
||||
DisableAutoMargin string // smam
|
||||
EnableAutoMargin string // rmam
|
||||
DoubleUnderline string // Smulx with param 2
|
||||
CurlyUnderline string // Smulx with param 3
|
||||
DottedUnderline string // Smulx with param 4
|
||||
DashedUnderline string // Smulx with param 5
|
||||
UnderlineColor string // Setuc1
|
||||
UnderlineColorRGB string // Setulc
|
||||
UnderlineColorReset string // ol
|
||||
XTermLike bool // (XT) has XTerm extensions
|
||||
StrikeThrough string // smxx
|
||||
SetFgBg string // setfgbg
|
||||
SetFgBgRGB string // setfgbgrgb
|
||||
SetFgRGB string // setfrgb
|
||||
SetBgRGB string // setbrgb
|
||||
InsertChar string // string to insert a character (ich1)
|
||||
AutoMargin bool // true if writing to last cell in line advances
|
||||
TrueColor bool // true if the terminal supports direct color
|
||||
DisableAutoMargin string // smam
|
||||
EnableAutoMargin string // rmam
|
||||
XTermLike bool // (XT) has XTerm extensions
|
||||
}
|
||||
|
||||
const (
|
||||
ModifiersNone = 0
|
||||
ModifiersXTerm = 1
|
||||
)
|
||||
type stack []any
|
||||
|
||||
type stack []interface{}
|
||||
|
||||
func (st stack) Push(v interface{}) stack {
|
||||
func (st stack) Push(v any) stack {
|
||||
if b, ok := v.(bool); ok {
|
||||
if b {
|
||||
return append(st, 1)
|
||||
@@ -337,12 +174,12 @@ func (pb *paramsBuffer) PutString(s string) {
|
||||
// TParm takes a terminfo parameterized string, such as setaf or cup, and
|
||||
// evaluates the string, and returns the result with the parameter
|
||||
// applied.
|
||||
func (t *Terminfo) TParm(s string, p ...interface{}) string {
|
||||
func (t *Terminfo) TParm(s string, p ...any) string {
|
||||
var stk stack
|
||||
var a string
|
||||
var ai, bi int
|
||||
var dvars [26]string
|
||||
var params [9]interface{}
|
||||
var params [9]any
|
||||
var pb = ¶msBuffer{}
|
||||
|
||||
pb.Start(s)
|
||||
@@ -682,6 +519,7 @@ var (
|
||||
// AddTerminfo can be called to register a new Terminfo entry.
|
||||
func AddTerminfo(t *Terminfo) {
|
||||
dblock.Lock()
|
||||
|
||||
terminfos[t.Name] = t
|
||||
for _, x := range t.Aliases {
|
||||
terminfos[x] = t
|
||||
@@ -777,5 +615,14 @@ func LookupTerminfo(name string) (*Terminfo, error) {
|
||||
t.SetFgBg = "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m"
|
||||
t.ResetFgBg = "\x1b[39;49m"
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func TerminfoNames() []string {
|
||||
res := make([]string, 0, len(terminfos))
|
||||
for m := range terminfos {
|
||||
res = append(res, m)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
-18
@@ -12,7 +12,6 @@ func init() {
|
||||
Aliases: []string{"vt100-am"},
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J$<50>",
|
||||
AttrOff: "\x1b[m\x0f$<2>",
|
||||
Underline: "\x1b[4m$<2>",
|
||||
@@ -29,23 +28,6 @@ func init() {
|
||||
EnableAutoMargin: "\x1b[?7h",
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A$<2>",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyBackspace: "\b",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1bOt",
|
||||
KeyF6: "\x1bOu",
|
||||
KeyF7: "\x1bOv",
|
||||
KeyF8: "\x1bOl",
|
||||
KeyF9: "\x1bOw",
|
||||
KeyF10: "\x1bOx",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-18
@@ -11,7 +11,6 @@ func init() {
|
||||
Name: "vt102",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J$<50>",
|
||||
AttrOff: "\x1b[m\x0f$<2>",
|
||||
Underline: "\x1b[4m$<2>",
|
||||
@@ -28,23 +27,6 @@ func init() {
|
||||
EnableAutoMargin: "\x1b[?7h",
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A$<2>",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyBackspace: "\b",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1bOt",
|
||||
KeyF6: "\x1bOu",
|
||||
KeyF7: "\x1bOv",
|
||||
KeyF8: "\x1bOl",
|
||||
KeyF9: "\x1bOw",
|
||||
KeyF10: "\x1bOx",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-30
@@ -12,7 +12,6 @@ func init() {
|
||||
Aliases: []string{"vt200"},
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
@@ -29,35 +28,6 @@ func init() {
|
||||
EnableAutoMargin: "\x1b[?7h",
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\b",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
KeyHelp: "\x1b[28~",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-32
@@ -12,7 +12,6 @@ func init() {
|
||||
Aliases: []string{"vt300"},
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
@@ -30,37 +29,6 @@ func init() {
|
||||
EnableAutoMargin: "\x1b[?7h",
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1b[1~",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF13: "\x1b[25~",
|
||||
KeyF14: "\x1b[26~",
|
||||
KeyF15: "\x1b[28~",
|
||||
KeyF16: "\x1b[29~",
|
||||
KeyF17: "\x1b[31~",
|
||||
KeyF18: "\x1b[32~",
|
||||
KeyF19: "\x1b[33~",
|
||||
KeyF20: "\x1b[34~",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-15
@@ -29,21 +29,6 @@ func init() {
|
||||
EnableAutoMargin: "\x1b[?7h",
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyBackspace: "\b",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
AutoMargin: true,
|
||||
InsertChar: "\x1b[@",
|
||||
})
|
||||
|
||||
+4
-23
@@ -1,4 +1,7 @@
|
||||
// Generated automatically. DO NOT HAND-EDIT.
|
||||
// This file was originally generated automatically,
|
||||
// but it is edited to correct for errors in the VT420
|
||||
// terminfo data. Additionally we have added extended
|
||||
// information for the extended F-keys.
|
||||
|
||||
package vt420
|
||||
|
||||
@@ -11,7 +14,6 @@ func init() {
|
||||
Name: "vt420",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J$<50>",
|
||||
ShowCursor: "\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
@@ -30,27 +32,6 @@ func init() {
|
||||
EnableAutoMargin: "\x1b[?7h",
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH$<10>",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1b[A",
|
||||
KeyDown: "\x1b[B",
|
||||
KeyRight: "\x1b[C",
|
||||
KeyLeft: "\x1b[D",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\b",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[17~",
|
||||
KeyF6: "\x1b[18~",
|
||||
KeyF7: "\x1b[19~",
|
||||
KeyF8: "\x1b[20~",
|
||||
KeyF9: "\x1b[21~",
|
||||
KeyF10: "\x1b[29~",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
// Generated automatically. DO NOT HAND-EDIT.
|
||||
|
||||
package vt52
|
||||
|
||||
import "github.com/gdamore/tcell/v2/terminfo"
|
||||
|
||||
func init() {
|
||||
|
||||
// DEC VT52
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "vt52",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1bH\x1bJ",
|
||||
EnterKeypad: "\x1b=",
|
||||
ExitKeypad: "\x1b>",
|
||||
PadChar: "\x00",
|
||||
AltChars: "+h.k0affggolpnqprrss",
|
||||
EnterAcs: "\x1bF",
|
||||
ExitAcs: "\x1bG",
|
||||
SetCursor: "\x1bY%p1%' '%+%c%p2%' '%+%c",
|
||||
CursorBack1: "\x1bD",
|
||||
CursorUp1: "\x1bA",
|
||||
KeyUp: "\x1bA",
|
||||
KeyDown: "\x1bB",
|
||||
KeyRight: "\x1bC",
|
||||
KeyLeft: "\x1bD",
|
||||
KeyBackspace: "\b",
|
||||
KeyF1: "\x1bP",
|
||||
KeyF2: "\x1bQ",
|
||||
KeyF3: "\x1bR",
|
||||
KeyF5: "\x1b?t",
|
||||
KeyF6: "\x1b?u",
|
||||
KeyF7: "\x1b?v",
|
||||
KeyF8: "\x1b?w",
|
||||
KeyF9: "\x1b?x",
|
||||
})
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
// Generated automatically. DO NOT HAND-EDIT.
|
||||
|
||||
package wy50
|
||||
|
||||
import "github.com/gdamore/tcell/v2/terminfo"
|
||||
|
||||
func init() {
|
||||
|
||||
// Wyse 50
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "wy50",
|
||||
Aliases: []string{"wyse50"},
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b+$<20>",
|
||||
ShowCursor: "\x1b`1",
|
||||
HideCursor: "\x1b`0",
|
||||
AttrOff: "\x1b(\x1bH\x03",
|
||||
Dim: "\x1b`7\x1b)",
|
||||
Reverse: "\x1b`6\x1b)",
|
||||
PadChar: "\x00",
|
||||
AltChars: "a;j5k3l2m1n8q:t4u9v=w0x6",
|
||||
EnterAcs: "\x1bH\x02",
|
||||
ExitAcs: "\x1bH\x03",
|
||||
SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\v",
|
||||
KeyUp: "\v",
|
||||
KeyDown: "\n",
|
||||
KeyRight: "\f",
|
||||
KeyLeft: "\b",
|
||||
KeyInsert: "\x1bQ",
|
||||
KeyDelete: "\x1bW",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1e",
|
||||
KeyPgUp: "\x1bJ",
|
||||
KeyPgDn: "\x1bK",
|
||||
KeyF1: "\x01@\r",
|
||||
KeyF2: "\x01A\r",
|
||||
KeyF3: "\x01B\r",
|
||||
KeyF4: "\x01C\r",
|
||||
KeyF5: "\x01D\r",
|
||||
KeyF6: "\x01E\r",
|
||||
KeyF7: "\x01F\r",
|
||||
KeyF8: "\x01G\r",
|
||||
KeyF9: "\x01H\r",
|
||||
KeyF10: "\x01I\r",
|
||||
KeyF11: "\x01J\r",
|
||||
KeyF12: "\x01K\r",
|
||||
KeyF13: "\x01L\r",
|
||||
KeyF14: "\x01M\r",
|
||||
KeyF15: "\x01N\r",
|
||||
KeyF16: "\x01O\r",
|
||||
KeyPrint: "\x1bP",
|
||||
KeyBacktab: "\x1bI",
|
||||
KeyShfHome: "\x1b{",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
// Generated automatically. DO NOT HAND-EDIT.
|
||||
|
||||
package wy60
|
||||
|
||||
import "github.com/gdamore/tcell/v2/terminfo"
|
||||
|
||||
func init() {
|
||||
|
||||
// Wyse 60
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "wy60",
|
||||
Aliases: []string{"wyse60"},
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b+$<100>",
|
||||
EnterCA: "\x1bw0",
|
||||
ExitCA: "\x1bw1",
|
||||
ShowCursor: "\x1b`1",
|
||||
HideCursor: "\x1b`0",
|
||||
AttrOff: "\x1b(\x1bH\x03\x1bG0\x1bcD",
|
||||
Underline: "\x1bG8",
|
||||
Dim: "\x1bGp",
|
||||
Blink: "\x1bG2",
|
||||
Reverse: "\x1bG4",
|
||||
PadChar: "\x00",
|
||||
AltChars: "+/,.0[a2fxgqh1ihjYk?lZm@nEqDtCu4vAwBx3yszr{c~~",
|
||||
EnterAcs: "\x1bcE",
|
||||
ExitAcs: "\x1bcD",
|
||||
EnableAutoMargin: "\x1bd/",
|
||||
DisableAutoMargin: "\x1bd.",
|
||||
SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\v",
|
||||
KeyUp: "\v",
|
||||
KeyDown: "\n",
|
||||
KeyRight: "\f",
|
||||
KeyLeft: "\b",
|
||||
KeyInsert: "\x1bQ",
|
||||
KeyDelete: "\x1bW",
|
||||
KeyBackspace: "\b",
|
||||
KeyHome: "\x1e",
|
||||
KeyPgUp: "\x1bJ",
|
||||
KeyPgDn: "\x1bK",
|
||||
KeyF1: "\x01@\r",
|
||||
KeyF2: "\x01A\r",
|
||||
KeyF3: "\x01B\r",
|
||||
KeyF4: "\x01C\r",
|
||||
KeyF5: "\x01D\r",
|
||||
KeyF6: "\x01E\r",
|
||||
KeyF7: "\x01F\r",
|
||||
KeyF8: "\x01G\r",
|
||||
KeyF9: "\x01H\r",
|
||||
KeyF10: "\x01I\r",
|
||||
KeyF11: "\x01J\r",
|
||||
KeyF12: "\x01K\r",
|
||||
KeyF13: "\x01L\r",
|
||||
KeyF14: "\x01M\r",
|
||||
KeyF15: "\x01N\r",
|
||||
KeyF16: "\x01O\r",
|
||||
KeyPrint: "\x1bP",
|
||||
KeyBacktab: "\x1bI",
|
||||
KeyShfHome: "\x1b{",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
// Generated automatically. DO NOT HAND-EDIT.
|
||||
|
||||
package wy99_ansi
|
||||
|
||||
import "github.com/gdamore/tcell/v2/terminfo"
|
||||
|
||||
func init() {
|
||||
|
||||
// Wyse WY-99GT in ANSI mode (int'l PC keyboard)
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "wy99-ansi",
|
||||
Columns: 80,
|
||||
Lines: 25,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J$<200>",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f\x1b[\"q",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h",
|
||||
ExitKeypad: "\x1b[?1l",
|
||||
PadChar: "\x00",
|
||||
AltChars: "``aaffggjjkkllmmnnooqqssttuuvvwwxx{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b)0",
|
||||
EnableAutoMargin: "\x1b[?7h",
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b$<1>",
|
||||
CursorUp1: "\x1bM",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyBackspace: "\b",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[M",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF17: "\x1b[K",
|
||||
KeyF18: "\x1b[31~",
|
||||
KeyF19: "\x1b[32~",
|
||||
KeyF20: "\x1b[33~",
|
||||
KeyF21: "\x1b[34~",
|
||||
KeyF22: "\x1b[35~",
|
||||
KeyF23: "\x1b[1~",
|
||||
KeyF24: "\x1b[2~",
|
||||
KeyBacktab: "\x1b[z",
|
||||
AutoMargin: true,
|
||||
})
|
||||
|
||||
// Wyse WY-99GT in ANSI mode (US PC keyboard)
|
||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||
Name: "wy99a-ansi",
|
||||
Columns: 80,
|
||||
Lines: 25,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[J$<200>",
|
||||
ShowCursor: "\x1b[34h\x1b[?25h",
|
||||
HideCursor: "\x1b[?25l",
|
||||
AttrOff: "\x1b[m\x0f\x1b[\"q",
|
||||
Underline: "\x1b[4m",
|
||||
Bold: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
EnterKeypad: "\x1b[?1h",
|
||||
ExitKeypad: "\x1b[?1l",
|
||||
PadChar: "\x00",
|
||||
AltChars: "``aaffggjjkkllmmnnooqqssttuuvvwwxx{{||}}~~",
|
||||
EnterAcs: "\x0e",
|
||||
ExitAcs: "\x0f",
|
||||
EnableAcs: "\x1b)0",
|
||||
EnableAutoMargin: "\x1b[?7h",
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b$<1>",
|
||||
CursorUp1: "\x1bM",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyBackspace: "\b",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[M",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyF17: "\x1b[K",
|
||||
KeyF18: "\x1b[31~",
|
||||
KeyF19: "\x1b[32~",
|
||||
KeyF20: "\x1b[33~",
|
||||
KeyF21: "\x1b[34~",
|
||||
KeyF22: "\x1b[35~",
|
||||
KeyF23: "\x1b[1~",
|
||||
KeyF24: "\x1b[2~",
|
||||
KeyBacktab: "\x1b[z",
|
||||
AutoMargin: true,
|
||||
})
|
||||
}
|
||||
-28
@@ -12,7 +12,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b7\x1b[?47h",
|
||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||
@@ -37,33 +36,6 @@ func init() {
|
||||
DisableAutoMargin: "\x1b[?7l",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
-28
@@ -31,7 +31,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||
@@ -59,33 +58,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[M",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\u007f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
TrueColor: true,
|
||||
})
|
||||
|
||||
-84
@@ -13,7 +13,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 8,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||
@@ -40,33 +39,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[<",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
@@ -77,7 +49,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 88,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||
@@ -104,33 +75,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[<",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
@@ -141,7 +85,6 @@ func init() {
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Clear: "\x1b[H\x1b[2J",
|
||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||
@@ -168,33 +111,6 @@ func init() {
|
||||
StrikeThrough: "\x1b[9m",
|
||||
Mouse: "\x1b[<",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
CursorBack1: "\b",
|
||||
CursorUp1: "\x1b[A",
|
||||
KeyUp: "\x1bOA",
|
||||
KeyDown: "\x1bOB",
|
||||
KeyRight: "\x1bOC",
|
||||
KeyLeft: "\x1bOD",
|
||||
KeyInsert: "\x1b[2~",
|
||||
KeyDelete: "\x1b[3~",
|
||||
KeyBackspace: "\x7f",
|
||||
KeyHome: "\x1bOH",
|
||||
KeyEnd: "\x1bOF",
|
||||
KeyPgUp: "\x1b[5~",
|
||||
KeyPgDn: "\x1b[6~",
|
||||
KeyF1: "\x1bOP",
|
||||
KeyF2: "\x1bOQ",
|
||||
KeyF3: "\x1bOR",
|
||||
KeyF4: "\x1bOS",
|
||||
KeyF5: "\x1b[15~",
|
||||
KeyF6: "\x1b[17~",
|
||||
KeyF7: "\x1b[18~",
|
||||
KeyF8: "\x1b[19~",
|
||||
KeyF9: "\x1b[20~",
|
||||
KeyF10: "\x1b[21~",
|
||||
KeyF11: "\x1b[23~",
|
||||
KeyF12: "\x1b[24~",
|
||||
KeyBacktab: "\x1b[Z",
|
||||
Modifiers: 1,
|
||||
AutoMargin: true,
|
||||
XTermLike: true,
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user