Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1cd9cd8803 | |||
| ab735d1f3a | |||
| e29c7e31c1 | |||
| 43f4680176 | |||
| d9f27c1775 |
@@ -177,7 +177,7 @@ func init() {
|
|||||||
convertCmd.Flags().StringVar(&convertPackageName, "package", "", "Package name (for code generation formats like gorm/bun)")
|
convertCmd.Flags().StringVar(&convertPackageName, "package", "", "Package name (for code generation formats like gorm/bun)")
|
||||||
convertCmd.Flags().StringVar(&convertSchemaFilter, "schema", "", "Filter to a specific schema by name (required for formats like dctx that only support single schemas)")
|
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().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().StringVar(&convertNullableTypes, "types", "", "Nullable type package for code-gen writers (bun/gorm): 'baselib' (default, Go pointer types), 'stdlib' (database/sql), or 'resolvespec'")
|
||||||
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)")
|
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")
|
err := convertCmd.MarkFlagRequired("from")
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func init() {
|
|||||||
splitCmd.Flags().StringVar(&splitTables, "tables", "", "Comma-separated list of table names to include (case-insensitive)")
|
splitCmd.Flags().StringVar(&splitTables, "tables", "", "Comma-separated list of table names to include (case-insensitive)")
|
||||||
splitCmd.Flags().StringVar(&splitExcludeSchema, "exclude-schema", "", "Comma-separated list of schema names to exclude")
|
splitCmd.Flags().StringVar(&splitExcludeSchema, "exclude-schema", "", "Comma-separated list of schema names to exclude")
|
||||||
splitCmd.Flags().StringVar(&splitExcludeTables, "exclude-tables", "", "Comma-separated list of table names to exclude (case-insensitive)")
|
splitCmd.Flags().StringVar(&splitExcludeTables, "exclude-tables", "", "Comma-separated list of table names to exclude (case-insensitive)")
|
||||||
splitCmd.Flags().StringVar(&splitNullableTypes, "types", "", "Nullable type package for code-gen writers (bun/gorm): 'resolvespec' (default) or 'stdlib' (database/sql)")
|
splitCmd.Flags().StringVar(&splitNullableTypes, "types", "", "Nullable type package for code-gen writers (bun/gorm): 'baselib' (default, Go pointer types), 'stdlib' (database/sql), or 'resolvespec'")
|
||||||
|
|
||||||
err := splitCmd.MarkFlagRequired("from")
|
err := splitCmd.MarkFlagRequired("from")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
module git.warky.dev/wdevs/relspecgo
|
module git.warky.dev/wdevs/relspecgo
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.7
|
||||||
|
|
||||||
require (
|
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/google/uuid v1.6.0
|
||||||
github.com/jackc/pgx/v5 v5.7.6
|
github.com/jackc/pgx/v5 v5.9.2
|
||||||
github.com/microsoft/go-mssqldb v1.9.6
|
github.com/microsoft/go-mssqldb v1.10.0
|
||||||
github.com/rivo/tview v0.42.0
|
github.com/rivo/tview v0.42.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/uptrace/bun v1.2.16
|
github.com/uptrace/bun v1.2.18
|
||||||
golang.org/x/text v0.31.0
|
golang.org/x/text v0.37.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
modernc.org/sqlite v1.44.3
|
modernc.org/sqlite v1.50.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -27,9 +27,8 @@ require (
|
|||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // 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/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.51.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/sys v0.44.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/term v0.43.0 // indirect
|
||||||
golang.org/x/term v0.37.0 // indirect
|
golang.org/x/tools v0.45.0 // indirect
|
||||||
modernc.org/libc v1.67.6 // indirect
|
modernc.org/libc v1.72.3 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // 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.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=
|
||||||
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/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk=
|
||||||
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.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
|
||||||
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/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||||
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.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
|
||||||
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/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
|
||||||
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.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=
|
||||||
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/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=
|
||||||
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.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
|
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.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
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/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/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=
|
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/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 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
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.13.9 h1:uI5l3DYPcFvHINKlGft+en23evOKL+dwtD21QR8ejVA=
|
||||||
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
github.com/gdamore/tcell/v2 v2.13.9/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
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 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
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 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
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 h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
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=
|
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/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 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
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.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
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 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
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/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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
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.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.4.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.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/microsoft/go-mssqldb v1.10.0/go.mod h1:mnG7lGa9iYJbzJqGCXyuQCegStKMr3kogDLD6+bmggg=
|
||||||
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/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
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/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
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/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 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c=
|
||||||
github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY=
|
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 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
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=
|
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/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 h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
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.18 h1:3HnRcMfS6OBPMG1eSOzlbFJ/X/AyMEJb7rMxE6VQvDU=
|
||||||
github.com/uptrace/bun v1.2.16/go.mod h1:jMoNg2n56ckaawi/O/J92BHaECmrz6IRjuMWqlMaMTM=
|
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 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
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=
|
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=
|
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-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.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.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||||
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||||
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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-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.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.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.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||||
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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.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.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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-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-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-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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
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.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.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.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||||
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.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.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.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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
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.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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||||
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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
||||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
|
||||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
|
||||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
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 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
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.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
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 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
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.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
|
||||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
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 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
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 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
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 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
|
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
|
||||||
modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
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 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: Hein (Warky Devs) <hein@warky.dev>
|
# Maintainer: Hein (Warky Devs) <hein@warky.dev>
|
||||||
pkgname=relspec
|
pkgname=relspec
|
||||||
pkgver=1.0.58
|
pkgver=1.0.60
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="RelSpec is a comprehensive database relations management tool that reads, transforms, and writes database table specifications across multiple formats and ORMs."
|
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')
|
arch=('x86_64' 'aarch64')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: relspec
|
Name: relspec
|
||||||
Version: 1.0.58
|
Version: 1.0.60
|
||||||
Release: 1%{?dist}
|
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.
|
Summary: RelSpec is a comprehensive database relations management tool that reads, transforms, and writes database table specifications across multiple formats and ORMs.
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type TypeMapper struct {
|
|||||||
// an empty string defaults to resolvespec.
|
// an empty string defaults to resolvespec.
|
||||||
func NewTypeMapper(typeStyle string) *TypeMapper {
|
func NewTypeMapper(typeStyle string) *TypeMapper {
|
||||||
if typeStyle == "" {
|
if typeStyle == "" {
|
||||||
typeStyle = writers.NullableTypeResolveSpec
|
typeStyle = writers.NullableTypeBaselib
|
||||||
}
|
}
|
||||||
return &TypeMapper{
|
return &TypeMapper{
|
||||||
sqlTypesAlias: "resolvespec_common",
|
sqlTypesAlias: "resolvespec_common",
|
||||||
@@ -37,11 +37,17 @@ func (tm *TypeMapper) SQLTypeToGoType(sqlType string, notNull bool) string {
|
|||||||
|
|
||||||
baseType := tm.extractBaseType(sqlType)
|
baseType := tm.extractBaseType(sqlType)
|
||||||
|
|
||||||
if tm.typeStyle == writers.NullableTypeStdlib {
|
switch tm.typeStyle {
|
||||||
|
case writers.NullableTypeStdlib:
|
||||||
if notNull {
|
if notNull {
|
||||||
return tm.rawGoType(baseType)
|
return tm.rawGoType(baseType)
|
||||||
}
|
}
|
||||||
return tm.stdlibNullableGoType(baseType)
|
return tm.stdlibNullableGoType(baseType)
|
||||||
|
case writers.NullableTypeBaselib:
|
||||||
|
if notNull {
|
||||||
|
return tm.rawGoType(baseType)
|
||||||
|
}
|
||||||
|
return tm.baselibNullableGoType(baseType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolvespec (default): use base Go types only for simple NOT NULL fields.
|
// resolvespec (default): use base Go types only for simple NOT NULL fields.
|
||||||
@@ -183,7 +189,7 @@ func (tm *TypeMapper) bunGoType(sqlType string) string {
|
|||||||
// arrayGoType returns the Go type for a PostgreSQL array column.
|
// arrayGoType returns the Go type for a PostgreSQL array column.
|
||||||
// The baseElemType is the canonical base type (e.g. "text", "integer").
|
// The baseElemType is the canonical base type (e.g. "text", "integer").
|
||||||
func (tm *TypeMapper) arrayGoType(baseElemType string) string {
|
func (tm *TypeMapper) arrayGoType(baseElemType string) string {
|
||||||
if tm.typeStyle == writers.NullableTypeStdlib {
|
if tm.typeStyle == writers.NullableTypeStdlib || tm.typeStyle == writers.NullableTypeBaselib {
|
||||||
return tm.stdlibArrayGoType(baseElemType)
|
return tm.stdlibArrayGoType(baseElemType)
|
||||||
}
|
}
|
||||||
typeMap := map[string]string{
|
typeMap := map[string]string{
|
||||||
@@ -276,6 +282,38 @@ func (tm *TypeMapper) stdlibNullableGoType(sqlType string) string {
|
|||||||
return "sql.NullString"
|
return "sql.NullString"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// baselibNullableGoType returns plain Go pointer types for nullable columns.
|
||||||
|
func (tm *TypeMapper) baselibNullableGoType(sqlType string) string {
|
||||||
|
typeMap := map[string]string{
|
||||||
|
"integer": "*int32", "int": "*int32", "int4": "*int32", "serial": "*int32",
|
||||||
|
"smallint": "*int16", "int2": "*int16", "smallserial": "*int16",
|
||||||
|
"bigint": "*int64", "int8": "*int64", "bigserial": "*int64",
|
||||||
|
"boolean": "*bool", "bool": "*bool",
|
||||||
|
"real": "*float32", "float4": "*float32",
|
||||||
|
"double precision": "*float64", "float8": "*float64",
|
||||||
|
"numeric": "*float64", "decimal": "*float64", "money": "*float64",
|
||||||
|
"text": "*string", "varchar": "*string", "char": "*string",
|
||||||
|
"character": "*string", "citext": "*string", "bpchar": "*string",
|
||||||
|
"inet": "*string", "cidr": "*string", "macaddr": "*string",
|
||||||
|
"uuid": "*string", "json": "*string", "jsonb": "*string",
|
||||||
|
"timestamp": "*time.Time",
|
||||||
|
"timestamp without time zone": "*time.Time",
|
||||||
|
"timestamp with time zone": "*time.Time",
|
||||||
|
"timestamptz": "*time.Time",
|
||||||
|
"date": "*time.Time",
|
||||||
|
"time": "*time.Time",
|
||||||
|
"time without time zone": "*time.Time",
|
||||||
|
"time with time zone": "*time.Time",
|
||||||
|
"timetz": "*time.Time",
|
||||||
|
"bytea": "[]byte",
|
||||||
|
"vector": "[]float32",
|
||||||
|
}
|
||||||
|
if goType, ok := typeMap[sqlType]; ok {
|
||||||
|
return goType
|
||||||
|
}
|
||||||
|
return "*string"
|
||||||
|
}
|
||||||
|
|
||||||
// stdlibArrayGoType returns a plain Go slice type for array columns in stdlib mode.
|
// stdlibArrayGoType returns a plain Go slice type for array columns in stdlib mode.
|
||||||
func (tm *TypeMapper) stdlibArrayGoType(baseElemType string) string {
|
func (tm *TypeMapper) stdlibArrayGoType(baseElemType string) string {
|
||||||
typeMap := map[string]string{
|
typeMap := map[string]string{
|
||||||
@@ -431,8 +469,11 @@ func (tm *TypeMapper) GetSQLTypesImport() string {
|
|||||||
// GetNullableTypeImportLine returns the full Go import line for the nullable type
|
// GetNullableTypeImportLine returns the full Go import line for the nullable type
|
||||||
// package (ready to pass to AddImport). Returns empty string when no import is needed.
|
// package (ready to pass to AddImport). Returns empty string when no import is needed.
|
||||||
func (tm *TypeMapper) GetNullableTypeImportLine() string {
|
func (tm *TypeMapper) GetNullableTypeImportLine() string {
|
||||||
if tm.typeStyle == writers.NullableTypeStdlib {
|
switch tm.typeStyle {
|
||||||
|
case writers.NullableTypeStdlib:
|
||||||
return "\"database/sql\""
|
return "\"database/sql\""
|
||||||
|
case writers.NullableTypeBaselib:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s \"%s\"", tm.sqlTypesAlias, tm.GetSQLTypesImport())
|
return fmt.Sprintf("%s \"%s\"", tm.sqlTypesAlias, tm.GetSQLTypesImport())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,9 +73,9 @@ func TestWriter_WriteTable(t *testing.T) {
|
|||||||
"ID",
|
"ID",
|
||||||
"int64",
|
"int64",
|
||||||
"Email",
|
"Email",
|
||||||
"resolvespec_common.SqlString",
|
"*string",
|
||||||
"CreatedAt",
|
"CreatedAt",
|
||||||
"resolvespec_common.SqlTime",
|
"time.Time",
|
||||||
"bun:\"id",
|
"bun:\"id",
|
||||||
"bun:\"email",
|
"bun:\"email",
|
||||||
"func (m ModelPublicUsers) TableName() string",
|
"func (m ModelPublicUsers) TableName() string",
|
||||||
@@ -564,20 +564,20 @@ func TestTypeMapper_SQLTypeToGoType_Bun(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{"bigint", true, "int64"},
|
{"bigint", true, "int64"},
|
||||||
{"bigint", false, "resolvespec_common.SqlInt64"},
|
{"bigint", false, "*int64"},
|
||||||
{"varchar", true, "resolvespec_common.SqlString"}, // Bun uses sql types even for NOT NULL strings
|
{"varchar", true, "string"},
|
||||||
{"varchar", false, "resolvespec_common.SqlString"},
|
{"varchar", false, "*string"},
|
||||||
{"timestamp", true, "resolvespec_common.SqlTimeStamp"},
|
{"timestamp", true, "time.Time"},
|
||||||
{"timestamp", false, "resolvespec_common.SqlTimeStamp"},
|
{"timestamp", false, "*time.Time"},
|
||||||
{"date", false, "resolvespec_common.SqlDate"},
|
{"date", false, "*time.Time"},
|
||||||
{"boolean", true, "bool"},
|
{"boolean", true, "bool"},
|
||||||
{"boolean", false, "resolvespec_common.SqlBool"},
|
{"boolean", false, "*bool"},
|
||||||
{"uuid", false, "resolvespec_common.SqlUUID"},
|
{"uuid", false, "*string"},
|
||||||
{"jsonb", false, "resolvespec_common.SqlJSONB"},
|
{"jsonb", false, "*string"},
|
||||||
{"text[]", true, "resolvespec_common.SqlStringArray"},
|
{"text[]", true, "[]string"},
|
||||||
{"text[]", false, "resolvespec_common.SqlStringArray"},
|
{"text[]", false, "[]string"},
|
||||||
{"integer[]", true, "resolvespec_common.SqlInt32Array"},
|
{"integer[]", true, "[]int32"},
|
||||||
{"bigint[]", false, "resolvespec_common.SqlInt64Array"},
|
{"bigint[]", false, "[]int64"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -599,7 +599,7 @@ func TestWriter_UpdateIDTypeSafety_Bun(t *testing.T) {
|
|||||||
forbidInt32 bool
|
forbidInt32 bool
|
||||||
}{
|
}{
|
||||||
{"int32_pk", "int", "int32", "m.ID = int32(newid)", false},
|
{"int32_pk", "int", "int32", "m.ID = int32(newid)", false},
|
||||||
{"sql_int16_pk", "smallint", "resolvespec_common.SqlInt16", "m.ID.FromString(fmt.Sprintf(\"%d\", newid))", true},
|
{"sql_int16_pk", "smallint", "int16", "m.ID = int16(newid)", true},
|
||||||
{"int64_pk", "bigint", "int64", "m.ID = int64(newid)", true},
|
{"int64_pk", "bigint", "int64", "m.ID = int64(newid)", true},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,13 +680,13 @@ func TestWriter_StringPrimaryKeyHelpers_Bun(t *testing.T) {
|
|||||||
generated := string(content)
|
generated := string(content)
|
||||||
|
|
||||||
expectations := []string{
|
expectations := []string{
|
||||||
"resolvespec_common.SqlUUID",
|
"ID string",
|
||||||
"func (m ModelPublicAccounts) GetID() string",
|
"func (m ModelPublicAccounts) GetID() string",
|
||||||
"return m.ID.String()",
|
"return m.ID",
|
||||||
"func (m ModelPublicAccounts) GetIDStr() string",
|
"func (m ModelPublicAccounts) GetIDStr() string",
|
||||||
"func (m ModelPublicAccounts) SetID(newid string)",
|
"func (m ModelPublicAccounts) SetID(newid string)",
|
||||||
"func (m *ModelPublicAccounts) UpdateID(newid string)",
|
"func (m *ModelPublicAccounts) UpdateID(newid string)",
|
||||||
"m.ID.FromString(newid)",
|
"m.ID = newid",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, expected := range expectations {
|
for _, expected := range expectations {
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ func formatComment(description, comment string) string {
|
|||||||
|
|
||||||
func isStringLikePrimaryKeyType(goType string) bool {
|
func isStringLikePrimaryKeyType(goType string) bool {
|
||||||
switch goType {
|
switch goType {
|
||||||
case "string", "sql.NullString", "sql_types.SqlString", "sql_types.SqlUUID":
|
case "string", "*string", "sql.NullString", "sql_types.SqlString", "sql_types.SqlUUID":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type TypeMapper struct {
|
|||||||
// an empty string defaults to resolvespec.
|
// an empty string defaults to resolvespec.
|
||||||
func NewTypeMapper(typeStyle string) *TypeMapper {
|
func NewTypeMapper(typeStyle string) *TypeMapper {
|
||||||
if typeStyle == "" {
|
if typeStyle == "" {
|
||||||
typeStyle = writers.NullableTypeResolveSpec
|
typeStyle = writers.NullableTypeBaselib
|
||||||
}
|
}
|
||||||
return &TypeMapper{
|
return &TypeMapper{
|
||||||
sqlTypesAlias: "sql_types",
|
sqlTypesAlias: "sql_types",
|
||||||
@@ -37,11 +37,17 @@ func (tm *TypeMapper) SQLTypeToGoType(sqlType string, notNull bool) string {
|
|||||||
|
|
||||||
baseType := tm.extractBaseType(sqlType)
|
baseType := tm.extractBaseType(sqlType)
|
||||||
|
|
||||||
if tm.typeStyle == writers.NullableTypeStdlib {
|
switch tm.typeStyle {
|
||||||
|
case writers.NullableTypeStdlib:
|
||||||
if notNull {
|
if notNull {
|
||||||
return tm.rawGoType(baseType)
|
return tm.rawGoType(baseType)
|
||||||
}
|
}
|
||||||
return tm.stdlibNullableGoType(baseType)
|
return tm.stdlibNullableGoType(baseType)
|
||||||
|
case writers.NullableTypeBaselib:
|
||||||
|
if notNull {
|
||||||
|
return tm.rawGoType(baseType)
|
||||||
|
}
|
||||||
|
return tm.baselibNullableGoType(baseType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolvespec (default)
|
// resolvespec (default)
|
||||||
@@ -212,7 +218,7 @@ func (tm *TypeMapper) nullableGoType(sqlType string) string {
|
|||||||
// arrayGoType returns the Go type for a PostgreSQL array column.
|
// arrayGoType returns the Go type for a PostgreSQL array column.
|
||||||
// The baseElemType is the canonical base type (e.g. "text", "integer").
|
// The baseElemType is the canonical base type (e.g. "text", "integer").
|
||||||
func (tm *TypeMapper) arrayGoType(baseElemType string) string {
|
func (tm *TypeMapper) arrayGoType(baseElemType string) string {
|
||||||
if tm.typeStyle == writers.NullableTypeStdlib {
|
if tm.typeStyle == writers.NullableTypeStdlib || tm.typeStyle == writers.NullableTypeBaselib {
|
||||||
return tm.stdlibArrayGoType(baseElemType)
|
return tm.stdlibArrayGoType(baseElemType)
|
||||||
}
|
}
|
||||||
typeMap := map[string]string{
|
typeMap := map[string]string{
|
||||||
@@ -305,6 +311,38 @@ func (tm *TypeMapper) stdlibNullableGoType(sqlType string) string {
|
|||||||
return "sql.NullString"
|
return "sql.NullString"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// baselibNullableGoType returns plain Go pointer types for nullable columns.
|
||||||
|
func (tm *TypeMapper) baselibNullableGoType(sqlType string) string {
|
||||||
|
typeMap := map[string]string{
|
||||||
|
"integer": "*int32", "int": "*int32", "int4": "*int32", "serial": "*int32",
|
||||||
|
"smallint": "*int16", "int2": "*int16", "smallserial": "*int16",
|
||||||
|
"bigint": "*int64", "int8": "*int64", "bigserial": "*int64",
|
||||||
|
"boolean": "*bool", "bool": "*bool",
|
||||||
|
"real": "*float32", "float4": "*float32",
|
||||||
|
"double precision": "*float64", "float8": "*float64",
|
||||||
|
"numeric": "*float64", "decimal": "*float64", "money": "*float64",
|
||||||
|
"text": "*string", "varchar": "*string", "char": "*string",
|
||||||
|
"character": "*string", "citext": "*string", "bpchar": "*string",
|
||||||
|
"inet": "*string", "cidr": "*string", "macaddr": "*string",
|
||||||
|
"uuid": "*string", "json": "*string", "jsonb": "*string",
|
||||||
|
"timestamp": "*time.Time",
|
||||||
|
"timestamp without time zone": "*time.Time",
|
||||||
|
"timestamp with time zone": "*time.Time",
|
||||||
|
"timestamptz": "*time.Time",
|
||||||
|
"date": "*time.Time",
|
||||||
|
"time": "*time.Time",
|
||||||
|
"time without time zone": "*time.Time",
|
||||||
|
"time with time zone": "*time.Time",
|
||||||
|
"timetz": "*time.Time",
|
||||||
|
"bytea": "[]byte",
|
||||||
|
"vector": "[]float32",
|
||||||
|
}
|
||||||
|
if goType, ok := typeMap[sqlType]; ok {
|
||||||
|
return goType
|
||||||
|
}
|
||||||
|
return "*string"
|
||||||
|
}
|
||||||
|
|
||||||
// stdlibArrayGoType returns a plain Go slice type for array columns in stdlib mode.
|
// stdlibArrayGoType returns a plain Go slice type for array columns in stdlib mode.
|
||||||
func (tm *TypeMapper) stdlibArrayGoType(baseElemType string) string {
|
func (tm *TypeMapper) stdlibArrayGoType(baseElemType string) string {
|
||||||
typeMap := map[string]string{
|
typeMap := map[string]string{
|
||||||
@@ -475,8 +513,11 @@ func (tm *TypeMapper) GetSQLTypesImport() string {
|
|||||||
// GetNullableTypeImportLine returns the full Go import line for the nullable type
|
// GetNullableTypeImportLine returns the full Go import line for the nullable type
|
||||||
// package (ready to pass to AddImport). Returns empty string when no import is needed.
|
// package (ready to pass to AddImport). Returns empty string when no import is needed.
|
||||||
func (tm *TypeMapper) GetNullableTypeImportLine() string {
|
func (tm *TypeMapper) GetNullableTypeImportLine() string {
|
||||||
if tm.typeStyle == writers.NullableTypeStdlib {
|
switch tm.typeStyle {
|
||||||
|
case writers.NullableTypeStdlib:
|
||||||
return "\"database/sql\""
|
return "\"database/sql\""
|
||||||
|
case writers.NullableTypeBaselib:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s \"%s\"", tm.sqlTypesAlias, tm.GetSQLTypesImport())
|
return fmt.Sprintf("%s \"%s\"", tm.sqlTypesAlias, tm.GetSQLTypesImport())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func TestWriter_WriteTable(t *testing.T) {
|
|||||||
"ID",
|
"ID",
|
||||||
"int64",
|
"int64",
|
||||||
"Email",
|
"Email",
|
||||||
"sql_types.SqlString",
|
"*string",
|
||||||
"CreatedAt",
|
"CreatedAt",
|
||||||
"time.Time",
|
"time.Time",
|
||||||
"gorm:\"column:id",
|
"gorm:\"column:id",
|
||||||
@@ -700,17 +700,17 @@ func TestTypeMapper_SQLTypeToGoType(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{"bigint", true, "int64"},
|
{"bigint", true, "int64"},
|
||||||
{"bigint", false, "sql_types.SqlInt64"},
|
{"bigint", false, "*int64"},
|
||||||
{"varchar", true, "string"},
|
{"varchar", true, "string"},
|
||||||
{"varchar", false, "sql_types.SqlString"},
|
{"varchar", false, "*string"},
|
||||||
{"timestamp", true, "time.Time"},
|
{"timestamp", true, "time.Time"},
|
||||||
{"timestamp", false, "sql_types.SqlTimeStamp"},
|
{"timestamp", false, "*time.Time"},
|
||||||
{"boolean", true, "bool"},
|
{"boolean", true, "bool"},
|
||||||
{"boolean", false, "sql_types.SqlBool"},
|
{"boolean", false, "*bool"},
|
||||||
{"text[]", true, "sql_types.SqlStringArray"},
|
{"text[]", true, "[]string"},
|
||||||
{"text[]", false, "sql_types.SqlStringArray"},
|
{"text[]", false, "[]string"},
|
||||||
{"integer[]", true, "sql_types.SqlInt32Array"},
|
{"integer[]", true, "[]int32"},
|
||||||
{"bigint[]", false, "sql_types.SqlInt64Array"},
|
{"bigint[]", false, "[]int64"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -1593,7 +1593,12 @@ func (w *Writer) executeDatabaseSQL(db *models.Database, connString string) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
stmtType := detectStatementType(stmtTrimmed)
|
stmtType := detectStatementType(stmtTrimmed)
|
||||||
|
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)
|
fmt.Fprintf(os.Stderr, "Executing statement %d/%d [%s]...\n", i+1, len(statements), stmtType)
|
||||||
|
}
|
||||||
|
|
||||||
_, execErr := conn.Exec(ctx, stmt)
|
_, execErr := conn.Exec(ctx, stmt)
|
||||||
if execErr != nil {
|
if execErr != nil {
|
||||||
@@ -1728,6 +1733,170 @@ func getCurrentTimestamp() string {
|
|||||||
return time.Now().Format("2006-01-02 15:04:05")
|
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
|
// detectStatementType detects the type of SQL statement for logging
|
||||||
func detectStatementType(stmt string) string {
|
func detectStatementType(stmt string) string {
|
||||||
upperStmt := strings.ToUpper(stmt)
|
upperStmt := strings.ToUpper(stmt)
|
||||||
|
|||||||
@@ -24,12 +24,17 @@ type Writer interface {
|
|||||||
// in code-generation writers (Bun, GORM).
|
// in code-generation writers (Bun, GORM).
|
||||||
const (
|
const (
|
||||||
// NullableTypeResolveSpec uses github.com/bitechdev/ResolveSpec/pkg/spectypes
|
// NullableTypeResolveSpec uses github.com/bitechdev/ResolveSpec/pkg/spectypes
|
||||||
// (SqlString, SqlInt32, SqlVector, SqlStringArray, …). This is the default.
|
// (SqlString, SqlInt32, SqlVector, SqlStringArray, …).
|
||||||
NullableTypeResolveSpec = "resolvespec"
|
NullableTypeResolveSpec = "resolvespec"
|
||||||
|
|
||||||
// NullableTypeStdlib uses the standard library database/sql nullable types
|
// NullableTypeStdlib uses the standard library database/sql nullable types
|
||||||
// (sql.NullString, sql.NullInt32, …) and plain Go slices for arrays.
|
// (sql.NullString, sql.NullInt32, …) and plain Go slices for arrays.
|
||||||
NullableTypeStdlib = "stdlib"
|
NullableTypeStdlib = "stdlib"
|
||||||
|
|
||||||
|
// NullableTypeBaselib uses plain Go pointer types for nullable columns
|
||||||
|
// (*string, *int32, *time.Time, …) and plain Go slices for arrays.
|
||||||
|
// No external imports are required beyond the standard library. This is the default.
|
||||||
|
NullableTypeBaselib = "baselib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriterOptions contains common options for writers
|
// WriterOptions contains common options for writers
|
||||||
@@ -49,6 +54,7 @@ type WriterOptions struct {
|
|||||||
// code-generation writers (bun, gorm). Accepted values:
|
// code-generation writers (bun, gorm). Accepted values:
|
||||||
// "resolvespec" (default) — github.com/bitechdev/ResolveSpec/pkg/spectypes
|
// "resolvespec" (default) — github.com/bitechdev/ResolveSpec/pkg/spectypes
|
||||||
// "stdlib" — database/sql (sql.NullString, sql.NullInt32, …)
|
// "stdlib" — database/sql (sql.NullString, sql.NullInt32, …)
|
||||||
|
// "baselib" — plain Go pointer types (*string, *int32, …)
|
||||||
NullableTypes string
|
NullableTypes string
|
||||||
|
|
||||||
// Prisma7 enables Prisma 7-specific output for Prisma writers.
|
// Prisma7 enables Prisma 7-specific output for Prisma writers.
|
||||||
|
|||||||
-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
|
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)
|
|
||||||
+1
-1
@@ -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
|
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
|
```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
|
In `tcell.js`, you also need to change the constant
|
||||||
|
|||||||
+25
-134
@@ -7,14 +7,14 @@ It was inspired by _termbox_, but includes many additional improvements.
|
|||||||
|
|
||||||
[](https://stand-with-ukraine.pp.ua)
|
[](https://stand-with-ukraine.pp.ua)
|
||||||
[](https://github.com/gdamore/tcell/actions/workflows/linux.yml)
|
[](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://github.com/gdamore/tcell/blob/master/LICENSE)
|
||||||
[](https://pkg.go.dev/github.com/gdamore/tcell/v2)
|
[](https://pkg.go.dev/github.com/gdamore/tcell/v2)
|
||||||
[](https://discord.gg/urTTxDN)
|
[](https://discord.gg/urTTxDN)
|
||||||
[](https://codecov.io/gh/gdamore/tcell)
|
[](https://codecov.io/gh/gdamore/tcell)
|
||||||
[](https://goreportcard.com/report/github.com/gdamore/tcell/v2)
|
[](https://goreportcard.com/report/github.com/gdamore/tcell/v2)
|
||||||
|
[](https://github.com/gdamore/tcell/releases)
|
||||||
Please see [here](UKRAINE.md) for an important message for the people of Russia.
|
|
||||||
|
|
||||||
NOTE: This is version 2 of _Tcell_. There are breaking changes relative to version 1.
|
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`.
|
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
|
## Examples
|
||||||
|
|
||||||
- [proxima5](https://github.com/gdamore/proxima5) - space shooter ([video](https://youtu.be/jNxKTCmY_bQ))
|
A number of example are posted up on our [Gallery](https://github.com/gdamore/tcell/wikis/Gallery/).
|
||||||
- [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
|
|
||||||
|
|
||||||
## Pure Go Terminfo Database
|
Let us know if you want to add your masterpiece to the list!
|
||||||
|
|
||||||
_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.
|
|
||||||
|
|
||||||
## More Portable
|
## More Portable
|
||||||
|
|
||||||
@@ -85,13 +35,10 @@ _Tcell_ is portable to a wide variety of systems, and is pure Go, without
|
|||||||
any need for CGO.
|
any need for CGO.
|
||||||
_Tcell_ is believed to work with mainstream systems officially supported by golang.
|
_Tcell_ is believed to work with mainstream systems officially supported by golang.
|
||||||
|
|
||||||
## No Async IO
|
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
|
||||||
_Tcell_ is able to operate without requiring `SIGIO` signals (unlike _termbox_),
|
update dependencies to pick up security fixes and new features, and it allows us to adopt changes
|
||||||
or asynchronous I/O, and can instead use standard Go file objects and Go routines.
|
(such as library and language features) that are only supported in newer versions of Go.
|
||||||
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.
|
|
||||||
|
|
||||||
## Rich Unicode & non-Unicode support
|
## 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
|
_Tcell_ also has richer support for a larger number of special keys that some
|
||||||
terminals can send.
|
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
|
## Better Mouse Support
|
||||||
|
|
||||||
_Tcell_ supports enhanced mouse tracking mode, so your application can receive
|
_Tcell_ supports enhanced mouse tracking mode, so your application can receive
|
||||||
regular mouse motion events, and wheel events, if your terminal supports it.
|
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
|
## _Termbox_ Compatibility
|
||||||
|
|
||||||
A compatibility layer for _termbox_ is provided in the `compat` directory.
|
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
|
## Wide & Combining Characters
|
||||||
|
|
||||||
The `SetContent()` API takes a primary rune, and an optional list of combining runes.
|
The `Put()` API takes a string, which should be legal UTF-8, and displays
|
||||||
If any of the runes is a wide (East Asian) rune occupying two cells,
|
the first grapheme (which may composed of multiple runes). It returns the
|
||||||
then the library will skip output from the following cell. Care must be
|
actual width displayed, which can be used to advance the column positiion
|
||||||
taken in the application to avoid explicitly attempting to set content in the
|
for the next display grapheme. Alternatively, `PutStr()` or `PutStrStyled()`
|
||||||
next cell, otherwise the results are undefined. (Normally the wide character
|
can be used to display a single line of text (which will be clipped at the
|
||||||
is displayed, and the other character is not; do not depend on that behavior.)
|
edge of the screen).
|
||||||
|
|
||||||
Older terminal applications (especially on systems like Windows 8) lack support
|
If a second character is displayed immediately in the cell adjacent to a
|
||||||
for advanced Unicode, and thus may not fare well.
|
wide character (offset by one instead of by two), then the results are undefined.
|
||||||
|
|
||||||
## Colors
|
## Colors
|
||||||
|
|
||||||
@@ -175,11 +104,7 @@ a ticket.
|
|||||||
|
|
||||||
_Tcell_ _supports 24-bit color!_ (That is, if your terminal can support it.)
|
_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
|
There are a few ways you can enable (or disable) 24-bit color.
|
||||||
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.
|
|
||||||
|
|
||||||
- For many terminals, we can detect it automatically if your terminal
|
- For many terminals, we can detect it automatically if your terminal
|
||||||
includes the `RGB` or `Tc` capabilities (or rather it did when the database
|
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
|
- You can disable 24-bit color by setting `TCELL_TRUECOLOR=disable` in your
|
||||||
environment.
|
environment.
|
||||||
|
|
||||||
When using TrueColor, programs will display the colors that the programmer
|
When using 24-bit color, programs will display the colors that the programmer
|
||||||
intended, overriding any "`themes`" you may have set in your terminal
|
intended, overriding any "`themes`" the user may have set in their terminal
|
||||||
emulator. (For some cases, accurate color fidelity is more important
|
emulator. (For some cases, accurate color fidelity is more important
|
||||||
than respecting themes. For other cases, such as typical text apps that
|
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
|
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,
|
Reasonable attempts have been made to minimize sending data to terminals,
|
||||||
avoiding repeated sequences or drawing the same cell on refresh updates.
|
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
|
||||||
|
|
||||||
Mouse support is detected via the `kmous` terminfo variable, however,
|
Mouse tracking, buttons, and even wheel mice works fine on most terminal
|
||||||
enablement/disablement and decoding mouse events is done using hard coded
|
emulators, as well as Windows.
|
||||||
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.
|
|
||||||
|
|
||||||
## Bracketed Paste
|
## 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.
|
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.)
|
support all the good features (resize, mouse tracking, etc.)
|
||||||
|
|
||||||
### WASM
|
### WASM
|
||||||
|
|
||||||
WASM is supported, but needs additional setup detailed in [README-wasm](README-wasm.md).
|
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
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
### Commercial Support
|
### Commercial Support
|
||||||
|
|
||||||
|
|||||||
+36
-21
@@ -107,23 +107,30 @@ s.SetStyle(defStyle)
|
|||||||
s.Clear()
|
s.Clear()
|
||||||
```
|
```
|
||||||
|
|
||||||
Text may be drawn on the screen using `SetContent`.
|
Text may be drawn on the screen using `Put`, `PutStr`, or `PutStrStyled`.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
s.SetContent(0, 0, 'H', nil, defStyle)
|
s.Put(0, 0, 'H', defStyle)
|
||||||
s.SetContent(1, 0, 'i', nil, defStyle)
|
s.Put(1, 0, 'i', defStyle)
|
||||||
s.SetContent(2, 0, '!', nil, 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
|
```go
|
||||||
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
|
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
|
||||||
row := y1
|
row := y1
|
||||||
col := x1
|
col := x1
|
||||||
for _, r := range []rune(text) {
|
var width int
|
||||||
s.SetContent(col, row, r, nil, style)
|
for text != "" {
|
||||||
col++
|
text, width = s.Put(col, row, text, style)
|
||||||
|
col += width
|
||||||
if col >= x2 {
|
if col >= x2 {
|
||||||
row++
|
row++
|
||||||
col = x1
|
col = x1
|
||||||
@@ -131,6 +138,10 @@ func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string
|
|||||||
if row > y2 {
|
if row > y2 {
|
||||||
break
|
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) {
|
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
|
||||||
row := y1
|
row := y1
|
||||||
col := x1
|
col := x1
|
||||||
for _, r := range []rune(text) {
|
var width int
|
||||||
s.SetContent(col, row, r, nil, style)
|
for text != "" {
|
||||||
col++
|
text, width = s.Put(col, row, text, style)
|
||||||
|
col += width
|
||||||
if col >= x2 {
|
if col >= x2 {
|
||||||
row++
|
row++
|
||||||
col = x1
|
col = x1
|
||||||
@@ -188,6 +200,10 @@ func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string
|
|||||||
if row > y2 {
|
if row > y2 {
|
||||||
break
|
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
|
// Fill background
|
||||||
for row := y1; row <= y2; row++ {
|
for row := y1; row <= y2; row++ {
|
||||||
for col := x1; col <= x2; col++ {
|
for col := x1; col <= x2; col++ {
|
||||||
s.SetContent(col, row, ' ', nil, style)
|
s.Put(col, row, " ", style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw borders
|
// Draw borders
|
||||||
for col := x1; col <= x2; col++ {
|
for col := x1; col <= x2; col++ {
|
||||||
s.SetContent(col, y1, tcell.RuneHLine, nil, style)
|
s.Put(col, y1, string(tcell.RuneHLine), style)
|
||||||
s.SetContent(col, y2, tcell.RuneHLine, nil, style)
|
s.Put(col, y2, string(tcell.RuneHLine), style)
|
||||||
}
|
}
|
||||||
for row := y1 + 1; row < y2; row++ {
|
for row := y1 + 1; row < y2; row++ {
|
||||||
s.SetContent(x1, row, tcell.RuneVLine, nil, style)
|
s.Put(x1, row, string(tcell.RuneVLine), style)
|
||||||
s.SetContent(x2, row, tcell.RuneVLine, nil, style)
|
s.Put(x2, row, string(tcell.RuneVLine), style)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only draw corners if necessary
|
// Only draw corners if necessary
|
||||||
if y1 != y2 && x1 != x2 {
|
if y1 != y2 && x1 != x2 {
|
||||||
s.SetContent(x1, y1, tcell.RuneULCorner, nil, style)
|
s.Put(x1, y1, string(tcell.RuneULCorner), style)
|
||||||
s.SetContent(x2, y1, tcell.RuneURCorner, nil, style)
|
s.Put(x2, y1, string(tcell.RuneURCorner), style)
|
||||||
s.SetContent(x1, y2, tcell.RuneLLCorner, nil, style)
|
s.Put(x1, y2, string(tcell.RuneLLCorner), style)
|
||||||
s.SetContent(x2, y2, tcell.RuneLRCorner, nil, style)
|
s.Put(x2, y2, string(tcell.RuneLRCorner), style)
|
||||||
}
|
}
|
||||||
|
|
||||||
drawText(s, x1+1, y1+1, x2-1, y2-1, style, text)
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use file except in compliance with the License.
|
// you may not use file except in compliance with the License.
|
||||||
@@ -15,23 +15,30 @@
|
|||||||
package tcell
|
package tcell
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"github.com/rivo/uniseg"
|
||||||
"reflect"
|
|
||||||
|
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type cell struct {
|
type cell struct {
|
||||||
currMain rune
|
currStr string
|
||||||
currComb []rune
|
lastStr string
|
||||||
currStyle Style
|
currStyle Style
|
||||||
lastMain rune
|
|
||||||
lastStyle Style
|
lastStyle Style
|
||||||
lastComb []rune
|
|
||||||
width int
|
width int
|
||||||
lock bool
|
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.
|
// CellBuffer represents a two-dimensional array of character cells.
|
||||||
// This is primarily intended for use by Screen implementors; it
|
// This is primarily intended for use by Screen implementors; it
|
||||||
// contains much of the common code they need. To create one, just
|
// 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
|
// and style) for a cell at a given location. If the background or
|
||||||
// foreground of the style is set to ColorNone, then the respective
|
// foreground of the style is set to ColorNone, then the respective
|
||||||
// color is left un changed.
|
// 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 {
|
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
|
||||||
|
var cl string
|
||||||
c := &cb.cells[(y*cb.w)+x]
|
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
|
// Wide characters: we want to mark the "wide" cells
|
||||||
// dirty as well as the base cell, to make sure we consider
|
// dirty as well as the base cell, to make sure we consider
|
||||||
// both cells as dirty together. We only need to do this
|
// both cells as dirty together. We only need to do this
|
||||||
// if we're changing content
|
// 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))) {
|
if width > 0 && cl != c.currStr {
|
||||||
for i := 0; i < c.width; i++ {
|
// 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)
|
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 {
|
if style.fg == ColorNone {
|
||||||
style.fg = c.currStyle.fg
|
style.fg = c.currStyle.fg
|
||||||
}
|
}
|
||||||
@@ -78,23 +104,45 @@ func (cb *CellBuffer) SetContent(x int, y int,
|
|||||||
}
|
}
|
||||||
c.currStyle = style
|
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
|
// GetContent returns the contents of a character cell, including the
|
||||||
// primary rune, any combining character runes (which will usually be
|
// primary rune, any combining character runes (which will usually be
|
||||||
// nil), the style, and the display width in cells. (The width can 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.)
|
// 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) {
|
func (cb *CellBuffer) GetContent(x, y int) (rune, []rune, Style, int) {
|
||||||
var mainc rune
|
|
||||||
var combc []rune
|
|
||||||
var style Style
|
var style Style
|
||||||
var width int
|
var width int
|
||||||
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
|
var mainc rune
|
||||||
c := &cb.cells[(y*cb.w)+x]
|
var combc []rune
|
||||||
mainc, combc, style = c.currMain, c.currComb, c.currStyle
|
str, style, width := cb.Get(x, y)
|
||||||
if width = c.width; width == 0 || mainc < ' ' {
|
for i, r := range str {
|
||||||
width = 1
|
if i == 0 {
|
||||||
mainc = ' '
|
mainc = r
|
||||||
|
} else {
|
||||||
|
combc = append(combc, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mainc, combc, style, width
|
return mainc, combc, style, width
|
||||||
@@ -108,7 +156,7 @@ func (cb *CellBuffer) Size() (int, int) {
|
|||||||
// Invalidate marks all characters within the buffer as dirty.
|
// Invalidate marks all characters within the buffer as dirty.
|
||||||
func (cb *CellBuffer) Invalidate() {
|
func (cb *CellBuffer) Invalidate() {
|
||||||
for i := range cb.cells {
|
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 {
|
if c.lock {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if c.lastMain == rune(0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if c.lastMain != c.currMain {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if c.lastStyle != c.currStyle {
|
if c.lastStyle != c.currStyle {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if len(c.lastComb) != len(c.currComb) {
|
if c.lastStr != c.currStr {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for i := range c.lastComb {
|
|
||||||
if c.lastComb[i] != c.currComb[i] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -148,16 +185,7 @@ func (cb *CellBuffer) Dirty(x, y int) bool {
|
|||||||
func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
|
func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
|
||||||
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
|
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
|
||||||
c := &cb.cells[(y*cb.w)+x]
|
c := &cb.cells[(y*cb.w)+x]
|
||||||
if dirty {
|
c.setDirty(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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,11 +231,10 @@ func (cb *CellBuffer) Resize(w, h int) {
|
|||||||
for x := 0; x < w && x < cb.w; x++ {
|
for x := 0; x < w && x < cb.w; x++ {
|
||||||
oc := &cb.cells[(y*cb.w)+x]
|
oc := &cb.cells[(y*cb.w)+x]
|
||||||
nc := &newc[(y*w)+x]
|
nc := &newc[(y*w)+x]
|
||||||
nc.currMain = oc.currMain
|
nc.currStr = oc.currStr
|
||||||
nc.currComb = oc.currComb
|
|
||||||
nc.currStyle = oc.currStyle
|
nc.currStyle = oc.currStyle
|
||||||
nc.width = oc.width
|
nc.width = oc.width
|
||||||
nc.lastMain = rune(0)
|
nc.lastStr = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb.cells = newc
|
cb.cells = newc
|
||||||
@@ -223,8 +250,7 @@ func (cb *CellBuffer) Resize(w, h int) {
|
|||||||
func (cb *CellBuffer) Fill(r rune, style Style) {
|
func (cb *CellBuffer) Fill(r rune, style Style) {
|
||||||
for i := range cb.cells {
|
for i := range cb.cells {
|
||||||
c := &cb.cells[i]
|
c := &cb.cells[i]
|
||||||
c.currMain = r
|
c.currStr = string(r)
|
||||||
c.currComb = nil
|
|
||||||
cs := style
|
cs := style
|
||||||
if cs.fg == ColorNone {
|
if cs.fg == ColorNone {
|
||||||
cs.fg = c.currStyle.fg
|
cs.fg = c.currStyle.fg
|
||||||
@@ -236,14 +262,3 @@ func (cb *CellBuffer) Fill(r rune, style Style) {
|
|||||||
c.width = 1
|
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
|
//go:build nacl
|
||||||
// +build plan9 nacl
|
// +build nacl
|
||||||
|
|
||||||
// Copyright 2015 The TCell Authors
|
// Copyright 2015 The TCell Authors
|
||||||
//
|
//
|
||||||
|
|||||||
+1
-1
@@ -18,5 +18,5 @@
|
|||||||
package tcell
|
package tcell
|
||||||
|
|
||||||
func getCharset() string {
|
func getCharset() string {
|
||||||
return "UTF-16"
|
return "UTF-8"
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-181
@@ -1,7 +1,7 @@
|
|||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
// Copyright 2024 The TCell Authors
|
// Copyright 2025 The TCell Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use file except in compliance with the License.
|
// you may not use file except in compliance with the License.
|
||||||
@@ -38,7 +38,6 @@ type cScreen struct {
|
|||||||
cury int
|
cury int
|
||||||
style Style
|
style Style
|
||||||
fini bool
|
fini bool
|
||||||
vten bool
|
|
||||||
truecolor bool
|
truecolor bool
|
||||||
running bool
|
running bool
|
||||||
disableAlt bool // disable the alternate screen
|
disableAlt bool // disable the alternate screen
|
||||||
@@ -106,7 +105,6 @@ var winColors = map[Color]Color{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
k32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
u32 = syscall.NewLazyDLL("user32.dll")
|
u32 = syscall.NewLazyDLL("user32.dll")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -117,18 +115,8 @@ var (
|
|||||||
// characters (Unicode) are in use. The documentation refers to them
|
// characters (Unicode) are in use. The documentation refers to them
|
||||||
// without this suffix, as the resolution is made via preprocessor.
|
// without this suffix, as the resolution is made via preprocessor.
|
||||||
var (
|
var (
|
||||||
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
|
|
||||||
procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects")
|
|
||||||
procCreateEvent = k32.NewProc("CreateEventW")
|
|
||||||
procSetEvent = k32.NewProc("SetEvent")
|
|
||||||
procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
|
procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
|
||||||
procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo")
|
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")
|
procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo")
|
||||||
procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
|
procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
|
||||||
procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute")
|
procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute")
|
||||||
@@ -195,6 +183,10 @@ var vtCursorStyles = map[CursorStyle]string{
|
|||||||
// NewConsoleScreen returns a Screen for the Windows console associated
|
// NewConsoleScreen returns a Screen for the Windows console associated
|
||||||
// with the current process. The Screen makes use of the Windows Console
|
// with the current process. The Screen makes use of the Windows Console
|
||||||
// API to display content and read events.
|
// 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) {
|
func NewConsoleScreen() (Screen, error) {
|
||||||
return &baseScreen{screenImpl: &cScreen{}}, nil
|
return &baseScreen{screenImpl: &cScreen{}}, nil
|
||||||
}
|
}
|
||||||
@@ -217,22 +209,11 @@ func (s *cScreen) Init() error {
|
|||||||
|
|
||||||
s.truecolor = true
|
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") {
|
switch os.Getenv("TCELL_TRUECOLOR") {
|
||||||
case "disable":
|
case "disable":
|
||||||
s.truecolor = false
|
s.truecolor = false
|
||||||
case "enable":
|
case "enable":
|
||||||
s.truecolor = true
|
s.truecolor = true
|
||||||
tryVt = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Lock()
|
s.Lock()
|
||||||
@@ -249,33 +230,17 @@ func (s *cScreen) Init() error {
|
|||||||
s.fini = false
|
s.fini = false
|
||||||
s.setInMode(modeResizeEn | modeExtendFlg)
|
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") {
|
switch os.Getenv("TCELL_ALTSCREEN") {
|
||||||
case "enable":
|
case "enable":
|
||||||
s.disableAlt = false // also the default
|
s.disableAlt = false // also the default
|
||||||
case "disable":
|
case "disable":
|
||||||
s.disableAlt = true
|
s.disableAlt = true
|
||||||
}
|
}
|
||||||
if tryVt {
|
|
||||||
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
|
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
|
||||||
var om uint32
|
var om uint32
|
||||||
s.getOutMode(&om)
|
s.getOutMode(&om)
|
||||||
if om&modeVtOutput == modeVtOutput {
|
if om&modeVtOutput != modeVtOutput {
|
||||||
s.vten = true
|
return errors.New("failed to initialize: VT output not supported?")
|
||||||
} else {
|
|
||||||
s.truecolor = false
|
|
||||||
s.setOutMode(0)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s.setOutMode(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
@@ -349,7 +314,6 @@ func (s *cScreen) disengage() {
|
|||||||
|
|
||||||
s.wg.Wait()
|
s.wg.Wait()
|
||||||
|
|
||||||
if s.vten {
|
|
||||||
s.emitVtString(vtCursorStyles[CursorStyleDefault])
|
s.emitVtString(vtCursorStyles[CursorStyleDefault])
|
||||||
s.emitVtString(vtCursorColorReset)
|
s.emitVtString(vtCursorColorReset)
|
||||||
s.emitVtString(vtEnableAm)
|
s.emitVtString(vtEnableAm)
|
||||||
@@ -357,10 +321,6 @@ func (s *cScreen) disengage() {
|
|||||||
s.emitVtString(vtRestoreTitle)
|
s.emitVtString(vtRestoreTitle)
|
||||||
s.emitVtString(vtExitCA)
|
s.emitVtString(vtExitCA)
|
||||||
}
|
}
|
||||||
} else if !s.disableAlt {
|
|
||||||
s.clearScreen(StyleDefault, s.vten)
|
|
||||||
s.setCursorPos(0, 0, false)
|
|
||||||
}
|
|
||||||
s.setCursorInfo(&s.ocursor)
|
s.setCursorInfo(&s.ocursor)
|
||||||
s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
|
s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
|
||||||
s.setInMode(s.oimode)
|
s.setInMode(s.oimode)
|
||||||
@@ -388,8 +348,7 @@ func (s *cScreen) engage() error {
|
|||||||
s.running = true
|
s.running = true
|
||||||
s.cancelflag = syscall.Handle(cf)
|
s.cancelflag = syscall.Handle(cf)
|
||||||
s.enableMouse(s.mouseEnabled)
|
s.enableMouse(s.mouseEnabled)
|
||||||
|
s.setInMode(modeVtInput | modeResizeEn | modeExtendFlg)
|
||||||
if s.vten {
|
|
||||||
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
|
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
|
||||||
if !s.disableAlt {
|
if !s.disableAlt {
|
||||||
s.emitVtString(vtSaveTitle)
|
s.emitVtString(vtSaveTitle)
|
||||||
@@ -399,11 +358,8 @@ func (s *cScreen) engage() error {
|
|||||||
if s.title != "" {
|
if s.title != "" {
|
||||||
s.emitVtString(fmt.Sprintf(vtSetTitle, s.title))
|
s.emitVtString(fmt.Sprintf(vtSetTitle, s.title))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
s.setOutMode(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.clearScreen(s.style, s.vten)
|
s.clearScreen(s.style)
|
||||||
s.hideCursor()
|
s.hideCursor()
|
||||||
|
|
||||||
s.cells.Invalidate()
|
s.cells.Invalidate()
|
||||||
@@ -445,7 +401,6 @@ func (s *cScreen) emitVtString(vs string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *cScreen) showCursor() {
|
func (s *cScreen) showCursor() {
|
||||||
if s.vten {
|
|
||||||
s.emitVtString(vtShowCursor)
|
s.emitVtString(vtShowCursor)
|
||||||
s.emitVtString(vtCursorStyles[s.cursorStyle])
|
s.emitVtString(vtCursorStyles[s.cursorStyle])
|
||||||
if s.cursorColor == ColorReset {
|
if s.cursorColor == ColorReset {
|
||||||
@@ -454,17 +409,10 @@ func (s *cScreen) showCursor() {
|
|||||||
r, g, b := s.cursorColor.RGB()
|
r, g, b := s.cursorColor.RGB()
|
||||||
s.emitVtString(fmt.Sprintf(vtCursorColorRGB, r, g, b))
|
s.emitVtString(fmt.Sprintf(vtCursorColorRGB, r, g, b))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cScreen) hideCursor() {
|
func (s *cScreen) hideCursor() {
|
||||||
if s.vten {
|
|
||||||
s.emitVtString(vtHideCursor)
|
s.emitVtString(vtHideCursor)
|
||||||
} else {
|
|
||||||
s.setCursorInfo(&cursorInfo{size: 1, visible: 0})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cScreen) ShowCursor(x, y int) {
|
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 {
|
if x < 0 || y < 0 || x >= s.w || y >= s.h {
|
||||||
s.hideCursor()
|
s.hideCursor()
|
||||||
} else {
|
} else {
|
||||||
s.setCursorPos(x, y, s.vten)
|
s.setCursorPos(x, y)
|
||||||
s.showCursor()
|
s.showCursor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,20 +452,6 @@ func (s *cScreen) HideCursor() {
|
|||||||
s.ShowCursor(-1, -1)
|
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 {
|
type mouseRecord struct {
|
||||||
x int16
|
x int16
|
||||||
y int16
|
y int16
|
||||||
@@ -655,25 +589,28 @@ var vkKeys = map[uint16]Key{
|
|||||||
func getu32(v []byte) uint32 {
|
func getu32(v []byte) uint32 {
|
||||||
return uint32(v[0]) + (uint32(v[1]) << 8) + (uint32(v[2]) << 16) + (uint32(v[3]) << 24)
|
return uint32(v[0]) + (uint32(v[1]) << 8) + (uint32(v[2]) << 16) + (uint32(v[3]) << 24)
|
||||||
}
|
}
|
||||||
|
|
||||||
func geti32(v []byte) int32 {
|
func geti32(v []byte) int32 {
|
||||||
return int32(getu32(v))
|
return int32(getu32(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getu16(v []byte) uint16 {
|
func getu16(v []byte) uint16 {
|
||||||
return uint16(v[0]) + (uint16(v[1]) << 8)
|
return uint16(v[0]) + (uint16(v[1]) << 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
func geti16(v []byte) int16 {
|
func geti16(v []byte) int16 {
|
||||||
return int16(getu16(v))
|
return int16(getu16(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert windows dwControlKeyState to modifier mask
|
// Convert windows dwControlKeyState to modifier mask
|
||||||
func mod2mask(cks uint32) ModMask {
|
func mod2mask(cks uint32, filter_ctrl_alt bool) ModMask {
|
||||||
mm := ModNone
|
mm := ModNone
|
||||||
// Left or right control
|
// Left or right control
|
||||||
ctrl := (cks & (0x0008 | 0x0004)) != 0
|
ctrl := (cks & (0x0008 | 0x0004)) != 0
|
||||||
// Left or right alt
|
// Left or right alt
|
||||||
alt := (cks & (0x0002 | 0x0001)) != 0
|
alt := (cks & (0x0002 | 0x0001)) != 0
|
||||||
// Filter out ctrl+alt (it means AltGr)
|
// Filter out ctrl+alt (it means AltGr)
|
||||||
if !(ctrl && alt) {
|
if !filter_ctrl_alt || !(ctrl && alt) {
|
||||||
if ctrl {
|
if ctrl {
|
||||||
mm |= ModCtrl
|
mm |= ModCtrl
|
||||||
}
|
}
|
||||||
@@ -787,11 +724,15 @@ func (s *cScreen) getConsoleInput() error {
|
|||||||
if krec.ch != 0 {
|
if krec.ch != 0 {
|
||||||
// synthesized key code
|
// synthesized key code
|
||||||
for krec.repeat > 0 {
|
for krec.repeat > 0 {
|
||||||
|
if krec.ch < ' ' && mod2mask(krec.mod, false) == ModCtrl {
|
||||||
|
krec.ch += '\x60'
|
||||||
|
}
|
||||||
|
|
||||||
// convert shift+tab to backtab
|
// 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))
|
s.postEvent(NewEventKey(KeyBacktab, 0, ModNone))
|
||||||
} else {
|
} else {
|
||||||
s.postEvent(NewEventKey(KeyRune, rune(krec.ch), mod2mask(krec.mod)))
|
s.postEvent(NewEventKey(KeyRune, rune(krec.ch), mod2mask(krec.mod, true)))
|
||||||
}
|
}
|
||||||
krec.repeat--
|
krec.repeat--
|
||||||
}
|
}
|
||||||
@@ -803,7 +744,7 @@ func (s *cScreen) getConsoleInput() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for krec.repeat > 0 {
|
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--
|
krec.repeat--
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,7 +757,7 @@ func (s *cScreen) getConsoleInput() error {
|
|||||||
mrec.flags = getu32(rec.data[12:])
|
mrec.flags = getu32(rec.data[12:])
|
||||||
btns := mrec2btns(mrec.btns, mrec.flags)
|
btns := mrec2btns(mrec.btns, mrec.flags)
|
||||||
// we ignore double click, events are delivered normally
|
// 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:
|
case resizeEvent:
|
||||||
var rrec resizeRecord
|
var rrec resizeRecord
|
||||||
@@ -858,12 +799,11 @@ func (s *cScreen) scanInput(stopQ chan struct{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *cScreen) Colors() int {
|
func (s *cScreen) Colors() int {
|
||||||
if s.vten {
|
if !s.truecolor {
|
||||||
return 1 << 24
|
|
||||||
}
|
|
||||||
// Windows console can display 8 colors, in either low or high intensity
|
|
||||||
return 16
|
return 16
|
||||||
}
|
}
|
||||||
|
return 1 << 24
|
||||||
|
}
|
||||||
|
|
||||||
var vgaColors = map[Color]uint16{
|
var vgaColors = map[Color]uint16{
|
||||||
ColorBlack: 0,
|
ColorBlack: 0,
|
||||||
@@ -938,7 +878,7 @@ func (s *cScreen) mapStyle(style Style) uint16 {
|
|||||||
return attr
|
return attr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cScreen) sendVtStyle(style Style) {
|
func (s *cScreen) makeVtStyle(style Style) string {
|
||||||
esc := &strings.Builder{}
|
esc := &strings.Builder{}
|
||||||
|
|
||||||
fg, bg, attrs := style.fg, style.bg, style.attrs
|
fg, bg, attrs := style.fg, style.bg, style.attrs
|
||||||
@@ -998,30 +938,32 @@ func (s *cScreen) sendVtStyle(style Style) {
|
|||||||
esc.WriteString(vtExitUrl)
|
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
|
// we assume the caller has hidden the cursor
|
||||||
if len(ch) == 0 {
|
if len(ch) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.setCursorPos(x, y, s.vten)
|
|
||||||
|
|
||||||
if s.vten {
|
vtBuf = append(vtBuf, utf16.Encode([]rune(fmt.Sprintf(vtCursorPos, y+1, x+1)))...)
|
||||||
s.sendVtStyle(style)
|
styleStr := s.makeVtStyle(style)
|
||||||
} else {
|
vtBuf = append(vtBuf, utf16.Encode([]rune(styleStr))...)
|
||||||
_, _, _ = procSetConsoleTextAttribute.Call(
|
vtBuf = append(vtBuf, ch...)
|
||||||
uintptr(s.out),
|
_ = syscall.WriteConsole(s.out, &vtBuf[0], uint32(len(vtBuf)), nil, nil)
|
||||||
uintptr(s.mapStyle(style)))
|
vtBuf = vtBuf[:0]
|
||||||
}
|
|
||||||
_ = syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cScreen) draw() {
|
func (s *cScreen) draw() {
|
||||||
// allocate a scratch line bit enough for no combining chars.
|
// allocate a scratch line bit enough for no combining chars.
|
||||||
// if you have combining characters, you may pay for extra allocations.
|
// if you have combining characters, you may pay for extra allocations.
|
||||||
buf := make([]uint16, 0, s.w)
|
buf := make([]uint16, 0, s.w)
|
||||||
|
var vtBuf []uint16
|
||||||
wcs := buf[:]
|
wcs := buf[:]
|
||||||
lstyle := styleInvalid
|
lstyle := styleInvalid
|
||||||
|
|
||||||
@@ -1040,7 +982,7 @@ func (s *cScreen) draw() {
|
|||||||
// write out any data queued thus far
|
// write out any data queued thus far
|
||||||
// because we are going to skip over some
|
// because we are going to skip over some
|
||||||
// cells, or because we need to change styles
|
// 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]
|
wcs = buf[0:0]
|
||||||
lstyle = StyleDefault
|
lstyle = StyleDefault
|
||||||
if !dirty {
|
if !dirty {
|
||||||
@@ -1067,7 +1009,7 @@ func (s *cScreen) draw() {
|
|||||||
}
|
}
|
||||||
x += width - 1
|
x += width - 1
|
||||||
}
|
}
|
||||||
s.writeString(lx, ly, lstyle, wcs)
|
s.writeString(lx, ly, lstyle, vtBuf, wcs)
|
||||||
wcs = buf[0:0]
|
wcs = buf[0:0]
|
||||||
lstyle = styleInvalid
|
lstyle = styleInvalid
|
||||||
}
|
}
|
||||||
@@ -1122,15 +1064,9 @@ func (s *cScreen) setCursorInfo(info *cursorInfo) {
|
|||||||
uintptr(unsafe.Pointer(info)))
|
uintptr(unsafe.Pointer(info)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cScreen) setCursorPos(x, y int, vtEnable bool) {
|
func (s *cScreen) setCursorPos(x, y int) {
|
||||||
if vtEnable {
|
|
||||||
// Note that the string is Y first. Origin is 1,1.
|
// Note that the string is Y first. Origin is 1,1.
|
||||||
s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+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) setBufferSize(x, y int) {
|
func (s *cScreen) setBufferSize(x, y int) {
|
||||||
@@ -1206,52 +1142,30 @@ func (s *cScreen) resize() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cScreen) clearScreen(style Style, vtEnable bool) {
|
func (s *cScreen) clearScreen(style Style) {
|
||||||
if vtEnable {
|
|
||||||
s.sendVtStyle(style)
|
s.sendVtStyle(style)
|
||||||
row := strings.Repeat(" ", s.w)
|
row := strings.Repeat(" ", s.w)
|
||||||
for y := 0; y < s.h; y++ {
|
for y := 0; y < s.h; y++ {
|
||||||
s.setCursorPos(0, y, vtEnable)
|
s.setCursorPos(0, y)
|
||||||
s.emitVtString(row)
|
s.emitVtString(row)
|
||||||
}
|
}
|
||||||
s.setCursorPos(0, 0, vtEnable)
|
s.setCursorPos(0, 0)
|
||||||
|
|
||||||
} 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)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Input modes
|
// Input modes
|
||||||
modeExtendFlg uint32 = 0x0080
|
modeExtendFlg = uint32(0x0080)
|
||||||
modeMouseEn = 0x0010
|
modeMouseEn = uint32(0x0010)
|
||||||
modeResizeEn = 0x0008
|
modeResizeEn = uint32(0x0008)
|
||||||
// modeCooked = 0x0001
|
modeVtInput = uint32(0x0200)
|
||||||
// modeVtInput = 0x0200
|
// modeCooked = uint32(0x0001)
|
||||||
|
|
||||||
// Output modes
|
// Output modes
|
||||||
modeCookedOut uint32 = 0x0001
|
modeCookedOut = uint32(0x0001)
|
||||||
modeVtOutput = 0x0004
|
modeVtOutput = uint32(0x0004)
|
||||||
modeNoAutoNL = 0x0008
|
modeNoAutoNL = uint32(0x0008)
|
||||||
modeUnderline = 0x0010 // ENABLE_LVB_GRID_WORLDWIDE, needed for underlines
|
modeUnderline = uint32(0x0010) // ENABLE_LVB_GRID_WORLDWIDE, needed for underlines
|
||||||
// modeWrapEOL = 0x0002
|
// modeWrapEOL = uint32(0x0002)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *cScreen) setInMode(mode uint32) {
|
func (s *cScreen) setInMode(mode uint32) {
|
||||||
@@ -1287,9 +1201,7 @@ func (s *cScreen) SetStyle(style Style) {
|
|||||||
func (s *cScreen) SetTitle(title string) {
|
func (s *cScreen) SetTitle(title string) {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
s.title = title
|
s.title = title
|
||||||
if s.vten {
|
|
||||||
s.emitVtString(fmt.Sprintf(vtSetTitle, title))
|
s.emitVtString(fmt.Sprintf(vtSetTitle, title))
|
||||||
}
|
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1320,43 +1232,8 @@ func (s *cScreen) GetClipboard() {
|
|||||||
|
|
||||||
func (s *cScreen) Resize(int, int, int, int) {}
|
func (s *cScreen) Resize(int, int, int, int) {}
|
||||||
|
|
||||||
func (s *cScreen) HasKey(k Key) bool {
|
func (s *cScreen) HasKey(_ Key) bool {
|
||||||
// Microsoft has codes for some keys, but they are unusual,
|
return true
|
||||||
// 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) Beep() error {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+64
-7
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2016 The TCell Authors
|
// Copyright 2025 The TCell Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use file except in compliance with the License.
|
// you may not use file except in compliance with the License.
|
||||||
@@ -171,6 +171,11 @@ var KeyNames = map[Key]string{
|
|||||||
KeyF62: "F62",
|
KeyF62: "F62",
|
||||||
KeyF63: "F63",
|
KeyF63: "F63",
|
||||||
KeyF64: "F64",
|
KeyF64: "F64",
|
||||||
|
KeyMenu: "Menu",
|
||||||
|
KeyCapsLock: "CapsLock",
|
||||||
|
KeyScrollLock: "ScrollLock",
|
||||||
|
KeyNumLock: "NumLock",
|
||||||
|
KeyCtrlSpace: "Ctrl-Space",
|
||||||
KeyCtrlA: "Ctrl-A",
|
KeyCtrlA: "Ctrl-A",
|
||||||
KeyCtrlB: "Ctrl-B",
|
KeyCtrlB: "Ctrl-B",
|
||||||
KeyCtrlC: "Ctrl-C",
|
KeyCtrlC: "Ctrl-C",
|
||||||
@@ -178,9 +183,12 @@ var KeyNames = map[Key]string{
|
|||||||
KeyCtrlE: "Ctrl-E",
|
KeyCtrlE: "Ctrl-E",
|
||||||
KeyCtrlF: "Ctrl-F",
|
KeyCtrlF: "Ctrl-F",
|
||||||
KeyCtrlG: "Ctrl-G",
|
KeyCtrlG: "Ctrl-G",
|
||||||
|
KeyCtrlH: "Ctrl-H",
|
||||||
|
KeyCtrlI: "Ctrl-I",
|
||||||
KeyCtrlJ: "Ctrl-J",
|
KeyCtrlJ: "Ctrl-J",
|
||||||
KeyCtrlK: "Ctrl-K",
|
KeyCtrlK: "Ctrl-K",
|
||||||
KeyCtrlL: "Ctrl-L",
|
KeyCtrlL: "Ctrl-L",
|
||||||
|
KeyCtrlM: "Ctrl-M",
|
||||||
KeyCtrlN: "Ctrl-N",
|
KeyCtrlN: "Ctrl-N",
|
||||||
KeyCtrlO: "Ctrl-O",
|
KeyCtrlO: "Ctrl-O",
|
||||||
KeyCtrlP: "Ctrl-P",
|
KeyCtrlP: "Ctrl-P",
|
||||||
@@ -194,11 +202,11 @@ var KeyNames = map[Key]string{
|
|||||||
KeyCtrlX: "Ctrl-X",
|
KeyCtrlX: "Ctrl-X",
|
||||||
KeyCtrlY: "Ctrl-Y",
|
KeyCtrlY: "Ctrl-Y",
|
||||||
KeyCtrlZ: "Ctrl-Z",
|
KeyCtrlZ: "Ctrl-Z",
|
||||||
KeyCtrlSpace: "Ctrl-Space",
|
KeyCtrlLeftSq: "Ctrl-[",
|
||||||
KeyCtrlUnderscore: "Ctrl-_",
|
|
||||||
KeyCtrlRightSq: "Ctrl-]",
|
KeyCtrlRightSq: "Ctrl-]",
|
||||||
KeyCtrlBackslash: "Ctrl-\\",
|
KeyCtrlBackslash: "Ctrl-\\",
|
||||||
KeyCtrlCarat: "Ctrl-^",
|
KeyCtrlCarat: "Ctrl-^",
|
||||||
|
KeyCtrlUnderscore: "Ctrl-_",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns a printable value or the key stroke. This can be used
|
// 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 {
|
if ev.mod&ModCtrl != 0 {
|
||||||
m = append(m, "Ctrl")
|
m = append(m, "Ctrl")
|
||||||
}
|
}
|
||||||
|
if ev.mod&ModHyper != 0 {
|
||||||
|
m = append(m, "Hyper")
|
||||||
|
}
|
||||||
|
|
||||||
ok := false
|
ok := false
|
||||||
if s, ok = KeyNames[ev.key]; !ok {
|
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.
|
// control characters and the DEL.
|
||||||
k = Key(ch)
|
k = Key(ch)
|
||||||
if mod == ModNone && ch < ' ' {
|
if mod == ModNone && ch < ' ' {
|
||||||
switch Key(ch) {
|
switch k {
|
||||||
case KeyBackspace, KeyTab, KeyEsc, KeyEnter:
|
case KeyBackspace, KeyTab, KeyEsc, KeyEnter:
|
||||||
// these keys are directly typeable without CTRL
|
// these keys are directly typeable without CTRL
|
||||||
default:
|
default:
|
||||||
// most likely entered with a CTRL keypress
|
// most likely entered with a CTRL keypress
|
||||||
mod = ModCtrl
|
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}
|
return &EventKey{t: time.Now(), key: k, ch: ch, mod: mod}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,6 +320,7 @@ const (
|
|||||||
ModCtrl
|
ModCtrl
|
||||||
ModAlt
|
ModAlt
|
||||||
ModMeta
|
ModMeta
|
||||||
|
ModHyper
|
||||||
ModNone ModMask = 0
|
ModNone ModMask = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -373,6 +422,10 @@ const (
|
|||||||
KeyF62
|
KeyF62
|
||||||
KeyF63
|
KeyF63
|
||||||
KeyF64
|
KeyF64
|
||||||
|
KeyMenu
|
||||||
|
KeyCapsLock
|
||||||
|
KeyScrollLock
|
||||||
|
KeyNumLock
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -381,10 +434,12 @@ const (
|
|||||||
keyPasteEnd
|
keyPasteEnd
|
||||||
)
|
)
|
||||||
|
|
||||||
// These are the control keys. Note that they overlap with other keys,
|
// These are the control keys, they will also be reported with the
|
||||||
// perhaps. For example, KeyCtrlH is the same as KeyBackspace.
|
// 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 (
|
const (
|
||||||
KeyCtrlSpace Key = iota
|
KeyCtrlSpace Key = iota + 64
|
||||||
KeyCtrlA
|
KeyCtrlA
|
||||||
KeyCtrlB
|
KeyCtrlB
|
||||||
KeyCtrlC
|
KeyCtrlC
|
||||||
@@ -466,5 +521,7 @@ const (
|
|||||||
KeyEsc = KeyESC
|
KeyEsc = KeyESC
|
||||||
KeyEscape = KeyESC
|
KeyEscape = KeyESC
|
||||||
KeyEnter = KeyCR
|
KeyEnter = KeyCR
|
||||||
|
|
||||||
|
// NB: This key will be translated to KeyBackspace
|
||||||
KeyBackspace2 = KeyDEL
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use file except in compliance with 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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use file except in compliance with the License.
|
// you may not use file except in compliance with the License.
|
||||||
@@ -35,17 +35,37 @@ type Screen interface {
|
|||||||
// is called (or Sync).
|
// is called (or Sync).
|
||||||
Fill(rune, Style)
|
Fill(rune, Style)
|
||||||
|
|
||||||
// SetCell is an older API, and will be removed. Please use
|
// Put writes the first graphme of the given string with th
|
||||||
// SetContent instead; SetCell is implemented in terms of SetContent.
|
// 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)
|
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,
|
// coordinates are out of range, then the values will be 0, nil,
|
||||||
// StyleDefault. Note that the contents returned are logical contents
|
// StyleDefault. Note that the contents returned are logical contents
|
||||||
// and may not actually be what is displayed, but rather are what will
|
// 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
|
// 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
|
// in screen cells; most often this will be 1, but some East Asian
|
||||||
// characters and emoji require two cells.
|
// 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)
|
GetContent(x, y int) (primary rune, combining []rune, style Style, width int)
|
||||||
|
|
||||||
// SetContent sets the contents of the given cell location. If
|
// 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
|
// fallbacks are registered, this will return true. This will
|
||||||
// also return true if the terminal can replace the glyph with
|
// also return true if the terminal can replace the glyph with
|
||||||
// one that is visually indistinguishable from the one requested.
|
// 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
|
CanDisplay(r rune, checkFallbacks bool) bool
|
||||||
|
|
||||||
// Resize does nothing, since it's generally not possible to
|
// Resize does nothing, since it's generally not possible to
|
||||||
@@ -228,14 +251,13 @@ type Screen interface {
|
|||||||
// the View interface.
|
// the View interface.
|
||||||
Resize(int, int, int, int)
|
Resize(int, int, int, int)
|
||||||
|
|
||||||
// HasKey returns true if the keyboard is believed to have the
|
// HasKey always returns true.
|
||||||
// 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
|
// Deprecated: This function always returns true. Applications
|
||||||
// as supported but not actually be usable (such as some emulators
|
// cannot reliably detect whether a key is supported or not with
|
||||||
// that hijack certain keys). Its best not to depend to strictly
|
// modern terminal emulators. (The intended use here was to help
|
||||||
// on this function, but it can be used for hinting when building
|
// applications determine whether a given key stroke was supported
|
||||||
// menus, displayed hot-keys, etc. Note that KeyRune (literal
|
// by the terminal, but it was never reliable.)
|
||||||
// runes) is always true.
|
|
||||||
HasKey(Key) bool
|
HasKey(Key) bool
|
||||||
|
|
||||||
// Suspend pauses input and output processing. It also restores the
|
// 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
|
// NewScreen returns a default Screen suitable for the user's terminal
|
||||||
// environment.
|
// environment.
|
||||||
func NewScreen() (Screen, error) {
|
func NewScreen() (Screen, error) {
|
||||||
// Windows is happier if we try for a console screen first.
|
if s, e := NewTerminfoScreen(); s != nil {
|
||||||
if s, _ := NewConsoleScreen(); s != nil {
|
|
||||||
return s, nil
|
return s, nil
|
||||||
} else if s, e := NewTerminfoScreen(); s != nil {
|
} else if s, _ := NewConsoleScreen(); s != nil {
|
||||||
return s, nil
|
return s, nil
|
||||||
} else {
|
} else {
|
||||||
return nil, e
|
return nil, e
|
||||||
@@ -382,11 +403,37 @@ type baseScreen struct {
|
|||||||
screenImpl
|
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) {
|
func (b *baseScreen) SetCell(x int, y int, style Style, ch ...rune) {
|
||||||
if len(ch) > 0 {
|
if len(ch) > 0 {
|
||||||
b.SetContent(x, y, ch[0], ch[1:], style)
|
b.Put(x, y, string(ch), style)
|
||||||
} else {
|
} 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()
|
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()
|
cells := b.GetCells()
|
||||||
b.Lock()
|
b.Lock()
|
||||||
cells.SetContent(x, y, mainc, combc, st)
|
defer b.Unlock()
|
||||||
b.Unlock()
|
return cells.Get(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *baseScreen) GetContent(x, y int) (rune, []rune, Style, int) {
|
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() {
|
func (s *simscreen) Fini() {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
|
if s.fini {
|
||||||
|
s.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
s.fini = true
|
s.fini = true
|
||||||
s.back.Resize(0, 0)
|
s.back.Resize(0, 0)
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
@@ -356,11 +360,18 @@ outer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b[0] < 0x80 {
|
if b[0] < 0x80 {
|
||||||
mod := ModNone
|
|
||||||
// No encodings start with low numbered values
|
// No encodings start with low numbered values
|
||||||
if Key(b[0]) >= KeyCtrlA && Key(b[0]) <= KeyCtrlZ {
|
if b[0] > 0 && b[0] < ' ' { // control keys
|
||||||
mod = ModCtrl
|
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)
|
ev := NewEventKey(Key(b[0]), 0, mod)
|
||||||
s.postEvent(ev)
|
s.postEvent(ev)
|
||||||
b = b[1:]
|
b = b[1:]
|
||||||
|
|||||||
+42
-2
@@ -14,6 +14,11 @@
|
|||||||
|
|
||||||
package tcell
|
package tcell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
// Style represents a complete text style, including both foreground color,
|
// Style represents a complete text style, including both foreground color,
|
||||||
// background color, and additional attributes such as "bold" or "underline".
|
// background color, and additional attributes such as "bold" or "underline".
|
||||||
//
|
//
|
||||||
@@ -164,6 +169,16 @@ func (s Style) Underline(params ...interface{}) Style {
|
|||||||
return s2
|
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
|
// Attributes returns a new style based on s, with its attributes set as
|
||||||
// specified.
|
// specified.
|
||||||
func (s Style) Attributes(attrs AttrMask) Style {
|
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.
|
// link to that Url. If the Url is empty, then this mode is turned off.
|
||||||
func (s Style) Url(url string) Style {
|
func (s Style) Url(url string) Style {
|
||||||
s2 := s
|
s2 := s
|
||||||
s2.url = url
|
s2.url = stripOSCControls(url)
|
||||||
return s2
|
return s2
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +202,31 @@ func (s Style) Url(url string) Style {
|
|||||||
// were one Url, even if it spans multiple lines.
|
// were one Url, even if it spans multiple lines.
|
||||||
func (s Style) UrlId(id string) Style {
|
func (s Style) UrlId(id string) Style {
|
||||||
s2 := s
|
s2 := s
|
||||||
s2.urlId = "id=" + id
|
s2.urlId = "id=" + stripOSCControls(id)
|
||||||
return s2
|
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()
|
||||||
|
}
|
||||||
|
|||||||
-52
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 25,
|
Lines: 25,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
AttrOff: "\x1b[0;10m\x1b(B",
|
AttrOff: "\x1b[0;10m\x1b(B",
|
||||||
Underline: "\x1b[4m",
|
Underline: "\x1b[4m",
|
||||||
@@ -27,57 +26,6 @@ func init() {
|
|||||||
EnterAcs: "\x1b(0",
|
EnterAcs: "\x1b(0",
|
||||||
ExitAcs: "\x1b(B",
|
ExitAcs: "\x1b(B",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-28
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 16777216,
|
Colors: 16777216,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||||
@@ -36,33 +35,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
TrueColor: true,
|
||||||
AutoMargin: true,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
|
|||||||
-32
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||||
@@ -39,38 +38,7 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[<",
|
Mouse: "\x1b[<",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
DoubleUnderline: "\x1b[4:2m",
|
|
||||||
CurlyUnderline: "\x1b[4:3m",
|
|
||||||
DottedUnderline: "\x1b[4:4m",
|
|
||||||
DashedUnderline: "\x1b[4:5m",
|
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-11
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
AttrOff: "\x1b[0;10m",
|
AttrOff: "\x1b[0;10m",
|
||||||
Underline: "\x1b[4m",
|
Underline: "\x1b[4m",
|
||||||
@@ -28,16 +27,6 @@ func init() {
|
|||||||
EnterAcs: "\x1b[11m",
|
EnterAcs: "\x1b[11m",
|
||||||
ExitAcs: "\x1b[10m",
|
ExitAcs: "\x1b[10m",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
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 --
|
// The following imports just register themselves --
|
||||||
// these are the terminal types we aggregate in this package.
|
// these are the terminal types we aggregate in this package.
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/a/ansi"
|
_ "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/vt100"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt102"
|
_ "github.com/gdamore/tcell/v2/terminfo/v/vt102"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt220"
|
_ "github.com/gdamore/tcell/v2/terminfo/v/vt220"
|
||||||
|
|||||||
-34
@@ -10,7 +10,6 @@ func init() {
|
|||||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||||
Name: "cygwin",
|
Name: "cygwin",
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -27,39 +26,6 @@ func init() {
|
|||||||
EnterAcs: "\x1b[11m",
|
EnterAcs: "\x1b[11m",
|
||||||
ExitAcs: "\x1b[10m",
|
ExitAcs: "\x1b[10m",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
InsertChar: "\x1b[@",
|
InsertChar: "\x1b[@",
|
||||||
})
|
})
|
||||||
|
|||||||
-33
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
ShowCursor: "\x1b[?25h",
|
ShowCursor: "\x1b[?25h",
|
||||||
HideCursor: "\x1b[?25l",
|
HideCursor: "\x1b[?25l",
|
||||||
@@ -34,38 +33,6 @@ func init() {
|
|||||||
EnableAutoMargin: "\x1b[?7h",
|
EnableAutoMargin: "\x1b[?7h",
|
||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-160
@@ -24,6 +24,7 @@ package dynamic
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -126,7 +127,7 @@ func (tc *termcap) setupterm(name string) error {
|
|||||||
tc.nums = make(map[string]int)
|
tc.nums = make(map[string]int)
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
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.
|
// Now parse the output.
|
||||||
@@ -144,9 +145,7 @@ func (tc *termcap) setupterm(name string) error {
|
|||||||
lines = lines[:len(lines)-1]
|
lines = lines[:len(lines)-1]
|
||||||
}
|
}
|
||||||
header := lines[0]
|
header := lines[0]
|
||||||
if strings.HasSuffix(header, ",") {
|
header = strings.TrimSuffix(header, ",")
|
||||||
header = header[:len(header)-1]
|
|
||||||
}
|
|
||||||
names := strings.Split(header, "|")
|
names := strings.Split(header, "|")
|
||||||
tc.name = names[0]
|
tc.name = names[0]
|
||||||
names = names[1:]
|
names = names[1:]
|
||||||
@@ -193,7 +192,6 @@ func LoadTerminfo(name string) (*terminfo.Terminfo, string, error) {
|
|||||||
t.Colors = tc.getnum("colors")
|
t.Colors = tc.getnum("colors")
|
||||||
t.Columns = tc.getnum("cols")
|
t.Columns = tc.getnum("cols")
|
||||||
t.Lines = tc.getnum("lines")
|
t.Lines = tc.getnum("lines")
|
||||||
t.Bell = tc.getstr("bel")
|
|
||||||
t.Clear = tc.getstr("clear")
|
t.Clear = tc.getstr("clear")
|
||||||
t.EnterCA = tc.getstr("smcup")
|
t.EnterCA = tc.getstr("smcup")
|
||||||
t.ExitCA = tc.getstr("rmcup")
|
t.ExitCA = tc.getstr("rmcup")
|
||||||
@@ -211,166 +209,11 @@ func LoadTerminfo(name string) (*terminfo.Terminfo, string, error) {
|
|||||||
t.SetFg = tc.getstr("setaf")
|
t.SetFg = tc.getstr("setaf")
|
||||||
t.SetBg = tc.getstr("setab")
|
t.SetBg = tc.getstr("setab")
|
||||||
t.SetCursor = tc.getstr("cup")
|
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.AltChars = tc.getstr("acsc")
|
||||||
t.EnterAcs = tc.getstr("smacs")
|
t.EnterAcs = tc.getstr("smacs")
|
||||||
t.ExitAcs = tc.getstr("rmacs")
|
t.ExitAcs = tc.getstr("rmacs")
|
||||||
t.EnableAcs = tc.getstr("enacs")
|
t.EnableAcs = tc.getstr("enacs")
|
||||||
t.Mouse = tc.getstr("kmous")
|
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
|
// Technically the RGB flag that is provided for xterm-direct is not
|
||||||
// quite right. The problem is that the -direct flag that was introduced
|
// quite right. The problem is that the -direct flag that was introduced
|
||||||
|
|||||||
-17
@@ -11,7 +11,6 @@ func init() {
|
|||||||
Name: "eterm",
|
Name: "eterm",
|
||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -21,8 +20,6 @@ func init() {
|
|||||||
Reverse: "\x1b[7m",
|
Reverse: "\x1b[7m",
|
||||||
PadChar: "\x00",
|
PadChar: "\x00",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||||
CursorBack1: "\b",
|
|
||||||
CursorUp1: "\x1b[A",
|
|
||||||
AutoMargin: true,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,7 +29,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -47,19 +43,6 @@ func init() {
|
|||||||
ResetFgBg: "\x1b[39;49m",
|
ResetFgBg: "\x1b[39;49m",
|
||||||
PadChar: "\x00",
|
PadChar: "\x00",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-6
@@ -24,13 +24,11 @@ import (
|
|||||||
_ "github.com/gdamore/tcell/v2/terminfo/a/aixterm"
|
_ "github.com/gdamore/tcell/v2/terminfo/a/aixterm"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/a/alacritty"
|
_ "github.com/gdamore/tcell/v2/terminfo/a/alacritty"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/a/ansi"
|
_ "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/c/cygwin"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/d/dtterm"
|
_ "github.com/gdamore/tcell/v2/terminfo/d/dtterm"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/e/emacs"
|
_ "github.com/gdamore/tcell/v2/terminfo/e/emacs"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/f/foot"
|
_ "github.com/gdamore/tcell/v2/terminfo/f/foot"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/g/gnome"
|
_ "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/konsole"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/k/kterm"
|
_ "github.com/gdamore/tcell/v2/terminfo/k/kterm"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/l/linux"
|
_ "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/vt320"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt400"
|
_ "github.com/gdamore/tcell/v2/terminfo/v/vt400"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/v/vt420"
|
_ "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/xfce"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm"
|
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm"
|
||||||
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty"
|
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty"
|
||||||
|
|||||||
-28
@@ -13,7 +13,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||||
@@ -38,33 +37,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-56
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -39,33 +38,6 @@ func init() {
|
|||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
@@ -76,7 +48,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -103,33 +74,6 @@ func init() {
|
|||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: 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,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -40,33 +39,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[<",
|
Mouse: "\x1b[<",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
@@ -77,7 +49,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -105,33 +76,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[<",
|
Mouse: "\x1b[<",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
|
|||||||
-32
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -34,37 +33,6 @@ func init() {
|
|||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
|
|||||||
-35
@@ -10,7 +10,6 @@ func init() {
|
|||||||
terminfo.AddTerminfo(&terminfo.Terminfo{
|
terminfo.AddTerminfo(&terminfo.Terminfo{
|
||||||
Name: "linux",
|
Name: "linux",
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
ShowCursor: "\x1b[?25h\x1b[?0c",
|
ShowCursor: "\x1b[?25h\x1b[?0c",
|
||||||
HideCursor: "\x1b[?25l\x1b[?1c",
|
HideCursor: "\x1b[?25l\x1b[?1c",
|
||||||
@@ -33,40 +32,6 @@ func init() {
|
|||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
InsertChar: "\x1b[@",
|
InsertChar: "\x1b[@",
|
||||||
})
|
})
|
||||||
|
|||||||
-2
@@ -1,7 +1,6 @@
|
|||||||
aixterm
|
aixterm
|
||||||
alacritty
|
alacritty
|
||||||
ansi
|
ansi
|
||||||
beterm
|
|
||||||
cygwin
|
cygwin
|
||||||
dtterm
|
dtterm
|
||||||
eterm,eterm-color|emacs
|
eterm,eterm-color|emacs
|
||||||
@@ -15,7 +14,6 @@ rxvt,rxvt-256color,rxvt-88color,rxvt-unicode,rxvt-unicode-256color
|
|||||||
screen,screen-256color
|
screen,screen-256color
|
||||||
st,st-256color|simpleterm
|
st,st-256color|simpleterm
|
||||||
tmux,tmux-256color
|
tmux,tmux-256color
|
||||||
vt52
|
|
||||||
vt100
|
vt100
|
||||||
vt102
|
vt102
|
||||||
vt220
|
vt220
|
||||||
|
|||||||
-9
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
AttrOff: "\x1b[0;10m",
|
AttrOff: "\x1b[0;10m",
|
||||||
Underline: "\x1b[4m",
|
Underline: "\x1b[4m",
|
||||||
@@ -28,14 +27,6 @@ func init() {
|
|||||||
EnterAcs: "\x1b[12m",
|
EnterAcs: "\x1b[12m",
|
||||||
ExitAcs: "\x1b[10m",
|
ExitAcs: "\x1b[10m",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-317
@@ -13,7 +13,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -37,78 +36,6 @@ func init() {
|
|||||||
EnableAcs: "\x1b(B\x1b)0",
|
EnableAcs: "\x1b(B\x1b)0",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
@@ -119,7 +46,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -143,78 +69,6 @@ func init() {
|
|||||||
EnableAcs: "\x1b(B\x1b)0",
|
EnableAcs: "\x1b(B\x1b)0",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
@@ -225,7 +79,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 88,
|
Colors: 88,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -249,78 +102,6 @@ func init() {
|
|||||||
EnableAcs: "\x1b(B\x1b)0",
|
EnableAcs: "\x1b(B\x1b)0",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
@@ -331,7 +112,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 88,
|
Colors: 88,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[r\x1b[?1049l",
|
ExitCA: "\x1b[r\x1b[?1049l",
|
||||||
@@ -356,54 +136,6 @@ func init() {
|
|||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
InsertChar: "\x1b[@",
|
InsertChar: "\x1b[@",
|
||||||
})
|
})
|
||||||
@@ -414,7 +146,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[r\x1b[?1049l",
|
ExitCA: "\x1b[r\x1b[?1049l",
|
||||||
@@ -439,54 +170,6 @@ func init() {
|
|||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
InsertChar: "\x1b[@",
|
InsertChar: "\x1b[@",
|
||||||
})
|
})
|
||||||
|
|||||||
-54
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[?1049l",
|
ExitCA: "\x1b[?1049l",
|
||||||
@@ -37,32 +36,6 @@ func init() {
|
|||||||
EnableAcs: "\x1b(B\x1b)0",
|
EnableAcs: "\x1b(B\x1b)0",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -72,7 +45,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[?1049l",
|
ExitCA: "\x1b[?1049l",
|
||||||
@@ -97,32 +69,6 @@ func init() {
|
|||||||
EnableAcs: "\x1b(B\x1b)0",
|
EnableAcs: "\x1b(B\x1b)0",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-56
@@ -13,7 +13,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[?1049l",
|
ExitCA: "\x1b[?1049l",
|
||||||
@@ -39,33 +38,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
@@ -77,7 +49,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[?1049l",
|
ExitCA: "\x1b[?1049l",
|
||||||
@@ -103,33 +74,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
|
|||||||
-52
@@ -30,37 +30,11 @@ func init() {
|
|||||||
Aliases: []string{"sun1", "sun2"},
|
Aliases: []string{"sun1", "sun2"},
|
||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 34,
|
Lines: 34,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\f",
|
Clear: "\f",
|
||||||
AttrOff: "\x1b[m",
|
AttrOff: "\x1b[m",
|
||||||
Reverse: "\x1b[7m",
|
Reverse: "\x1b[7m",
|
||||||
PadChar: "\x00",
|
PadChar: "\x00",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
InsertChar: "\x1b[@",
|
InsertChar: "\x1b[@",
|
||||||
})
|
})
|
||||||
@@ -71,7 +45,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 34,
|
Lines: 34,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\f",
|
Clear: "\f",
|
||||||
AttrOff: "\x1b[m",
|
AttrOff: "\x1b[m",
|
||||||
Bold: "\x1b[1m",
|
Bold: "\x1b[1m",
|
||||||
@@ -81,31 +54,6 @@ func init() {
|
|||||||
ResetFgBg: "\x1b[0m",
|
ResetFgBg: "\x1b[0m",
|
||||||
PadChar: "\x00",
|
PadChar: "\x00",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
InsertChar: "\x1b[@",
|
InsertChar: "\x1b[@",
|
||||||
})
|
})
|
||||||
|
|||||||
+2
-64
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[?1049l",
|
ExitCA: "\x1b[?1049l",
|
||||||
@@ -39,38 +38,8 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
DoubleUnderline: "\x1b[4:2m",
|
XTermLike: true,
|
||||||
CurlyUnderline: "\x1b[4:3m",
|
|
||||||
DottedUnderline: "\x1b[4:4m",
|
|
||||||
DashedUnderline: "\x1b[4:5m",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// tmux with 256 colors
|
// tmux with 256 colors
|
||||||
@@ -79,7 +48,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[?1049l",
|
ExitCA: "\x1b[?1049l",
|
||||||
@@ -106,37 +74,7 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
DoubleUnderline: "\x1b[4:2m",
|
XTermLike: true,
|
||||||
CurlyUnderline: "\x1b[4:3m",
|
|
||||||
DottedUnderline: "\x1b[4:4m",
|
|
||||||
DashedUnderline: "\x1b[4:5m",
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-168
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2024 The TCell Authors
|
// Copyright 2025 The TCell Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use file except in compliance with the License.
|
// you may not use file except in compliance with the License.
|
||||||
@@ -46,7 +46,6 @@ type Terminfo struct {
|
|||||||
Columns int // cols
|
Columns int // cols
|
||||||
Lines int // lines
|
Lines int // lines
|
||||||
Colors int // colors
|
Colors int // colors
|
||||||
Bell string // bell
|
|
||||||
Clear string // clear
|
Clear string // clear
|
||||||
EnterCA string // smcup
|
EnterCA string // smcup
|
||||||
ExitCA string // rmcup
|
ExitCA string // rmcup
|
||||||
@@ -65,101 +64,12 @@ type Terminfo struct {
|
|||||||
SetBg string // setab
|
SetBg string // setab
|
||||||
ResetFgBg string // op
|
ResetFgBg string // op
|
||||||
SetCursor string // cup
|
SetCursor string // cup
|
||||||
CursorBack1 string // cub1
|
|
||||||
CursorUp1 string // cuu1
|
|
||||||
PadChar string // pad
|
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
|
Mouse string // kmous
|
||||||
AltChars string // acsc
|
AltChars string // acsc
|
||||||
EnterAcs string // smacs
|
EnterAcs string // smacs
|
||||||
ExitAcs string // rmacs
|
ExitAcs string // rmacs
|
||||||
EnableAcs string // enacs
|
EnableAcs string // enacs
|
||||||
KeyShfRight string // kRIT
|
|
||||||
KeyShfLeft string // kLFT
|
|
||||||
KeyShfHome string // kHOM
|
|
||||||
KeyShfEnd string // kEND
|
|
||||||
KeyShfInsert string // kIC
|
|
||||||
KeyShfDelete string // kDC
|
|
||||||
|
|
||||||
// These are non-standard extensions to terminfo. This includes
|
// These are non-standard extensions to terminfo. This includes
|
||||||
// true color support, and some additional keys. Its kind of bizarre
|
// true color support, and some additional keys. Its kind of bizarre
|
||||||
@@ -172,90 +82,17 @@ type Terminfo struct {
|
|||||||
SetFgBgRGB string // setfgbgrgb
|
SetFgBgRGB string // setfgbgrgb
|
||||||
SetFgRGB string // setfrgb
|
SetFgRGB string // setfrgb
|
||||||
SetBgRGB string // setbrgb
|
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)
|
InsertChar string // string to insert a character (ich1)
|
||||||
AutoMargin bool // true if writing to last cell in line advances
|
AutoMargin bool // true if writing to last cell in line advances
|
||||||
TrueColor bool // true if the terminal supports direct color
|
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
|
DisableAutoMargin string // smam
|
||||||
EnableAutoMargin string // rmam
|
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
|
XTermLike bool // (XT) has XTerm extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
type stack []any
|
||||||
ModifiersNone = 0
|
|
||||||
ModifiersXTerm = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
type stack []interface{}
|
func (st stack) Push(v any) stack {
|
||||||
|
|
||||||
func (st stack) Push(v interface{}) stack {
|
|
||||||
if b, ok := v.(bool); ok {
|
if b, ok := v.(bool); ok {
|
||||||
if b {
|
if b {
|
||||||
return append(st, 1)
|
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
|
// TParm takes a terminfo parameterized string, such as setaf or cup, and
|
||||||
// evaluates the string, and returns the result with the parameter
|
// evaluates the string, and returns the result with the parameter
|
||||||
// applied.
|
// applied.
|
||||||
func (t *Terminfo) TParm(s string, p ...interface{}) string {
|
func (t *Terminfo) TParm(s string, p ...any) string {
|
||||||
var stk stack
|
var stk stack
|
||||||
var a string
|
var a string
|
||||||
var ai, bi int
|
var ai, bi int
|
||||||
var dvars [26]string
|
var dvars [26]string
|
||||||
var params [9]interface{}
|
var params [9]any
|
||||||
var pb = ¶msBuffer{}
|
var pb = ¶msBuffer{}
|
||||||
|
|
||||||
pb.Start(s)
|
pb.Start(s)
|
||||||
@@ -682,6 +519,7 @@ var (
|
|||||||
// AddTerminfo can be called to register a new Terminfo entry.
|
// AddTerminfo can be called to register a new Terminfo entry.
|
||||||
func AddTerminfo(t *Terminfo) {
|
func AddTerminfo(t *Terminfo) {
|
||||||
dblock.Lock()
|
dblock.Lock()
|
||||||
|
|
||||||
terminfos[t.Name] = t
|
terminfos[t.Name] = t
|
||||||
for _, x := range t.Aliases {
|
for _, x := range t.Aliases {
|
||||||
terminfos[x] = t
|
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.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"
|
t.ResetFgBg = "\x1b[39;49m"
|
||||||
}
|
}
|
||||||
|
|
||||||
return t, nil
|
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"},
|
Aliases: []string{"vt100-am"},
|
||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J$<50>",
|
Clear: "\x1b[H\x1b[J$<50>",
|
||||||
AttrOff: "\x1b[m\x0f$<2>",
|
AttrOff: "\x1b[m\x0f$<2>",
|
||||||
Underline: "\x1b[4m$<2>",
|
Underline: "\x1b[4m$<2>",
|
||||||
@@ -29,23 +28,6 @@ func init() {
|
|||||||
EnableAutoMargin: "\x1b[?7h",
|
EnableAutoMargin: "\x1b[?7h",
|
||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-18
@@ -11,7 +11,6 @@ func init() {
|
|||||||
Name: "vt102",
|
Name: "vt102",
|
||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J$<50>",
|
Clear: "\x1b[H\x1b[J$<50>",
|
||||||
AttrOff: "\x1b[m\x0f$<2>",
|
AttrOff: "\x1b[m\x0f$<2>",
|
||||||
Underline: "\x1b[4m$<2>",
|
Underline: "\x1b[4m$<2>",
|
||||||
@@ -28,23 +27,6 @@ func init() {
|
|||||||
EnableAutoMargin: "\x1b[?7h",
|
EnableAutoMargin: "\x1b[?7h",
|
||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-30
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Aliases: []string{"vt200"},
|
Aliases: []string{"vt200"},
|
||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[J",
|
Clear: "\x1b[H\x1b[J",
|
||||||
ShowCursor: "\x1b[?25h",
|
ShowCursor: "\x1b[?25h",
|
||||||
HideCursor: "\x1b[?25l",
|
HideCursor: "\x1b[?25l",
|
||||||
@@ -29,35 +28,6 @@ func init() {
|
|||||||
EnableAutoMargin: "\x1b[?7h",
|
EnableAutoMargin: "\x1b[?7h",
|
||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-32
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Aliases: []string{"vt300"},
|
Aliases: []string{"vt300"},
|
||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
ShowCursor: "\x1b[?25h",
|
ShowCursor: "\x1b[?25h",
|
||||||
HideCursor: "\x1b[?25l",
|
HideCursor: "\x1b[?25l",
|
||||||
@@ -30,37 +29,6 @@ func init() {
|
|||||||
EnableAutoMargin: "\x1b[?7h",
|
EnableAutoMargin: "\x1b[?7h",
|
||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
-15
@@ -29,21 +29,6 @@ func init() {
|
|||||||
EnableAutoMargin: "\x1b[?7h",
|
EnableAutoMargin: "\x1b[?7h",
|
||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
InsertChar: "\x1b[@",
|
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
|
package vt420
|
||||||
|
|
||||||
@@ -11,7 +14,6 @@ func init() {
|
|||||||
Name: "vt420",
|
Name: "vt420",
|
||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J$<50>",
|
Clear: "\x1b[H\x1b[2J$<50>",
|
||||||
ShowCursor: "\x1b[?25h",
|
ShowCursor: "\x1b[?25h",
|
||||||
HideCursor: "\x1b[?25l",
|
HideCursor: "\x1b[?25l",
|
||||||
@@ -30,27 +32,6 @@ func init() {
|
|||||||
EnableAutoMargin: "\x1b[?7h",
|
EnableAutoMargin: "\x1b[?7h",
|
||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH$<10>",
|
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,
|
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,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b7\x1b[?47h",
|
EnterCA: "\x1b7\x1b[?47h",
|
||||||
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
ExitCA: "\x1b[2J\x1b[?47l\x1b8",
|
||||||
@@ -37,33 +36,6 @@ func init() {
|
|||||||
DisableAutoMargin: "\x1b[?7l",
|
DisableAutoMargin: "\x1b[?7l",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
|
|||||||
-28
@@ -31,7 +31,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||||
@@ -59,33 +58,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
TrueColor: true,
|
TrueColor: true,
|
||||||
})
|
})
|
||||||
|
|||||||
-84
@@ -13,7 +13,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 8,
|
Colors: 8,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||||
@@ -40,33 +39,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[<",
|
Mouse: "\x1b[<",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
@@ -77,7 +49,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 88,
|
Colors: 88,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||||
@@ -104,33 +75,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[<",
|
Mouse: "\x1b[<",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
@@ -141,7 +85,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
EnterCA: "\x1b[?1049h\x1b[22;0;0t",
|
||||||
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
ExitCA: "\x1b[?1049l\x1b[23;0;0t",
|
||||||
@@ -168,33 +111,6 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[<",
|
Mouse: "\x1b[<",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
AutoMargin: true,
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
|
|||||||
-32
@@ -13,7 +13,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[?1049l",
|
ExitCA: "\x1b[?1049l",
|
||||||
@@ -40,40 +39,9 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[<",
|
Mouse: "\x1b[<",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
TrueColor: true,
|
||||||
AutoMargin: true,
|
AutoMargin: true,
|
||||||
InsertChar: "\x1b[@",
|
InsertChar: "\x1b[@",
|
||||||
DoubleUnderline: "\x1b[4:2m",
|
|
||||||
CurlyUnderline: "\x1b[4:3m",
|
|
||||||
DottedUnderline: "\x1b[4:4m",
|
|
||||||
DashedUnderline: "\x1b[4:5m",
|
|
||||||
XTermLike: true,
|
XTermLike: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-32
@@ -12,7 +12,6 @@ func init() {
|
|||||||
Columns: 80,
|
Columns: 80,
|
||||||
Lines: 24,
|
Lines: 24,
|
||||||
Colors: 256,
|
Colors: 256,
|
||||||
Bell: "\a",
|
|
||||||
Clear: "\x1b[H\x1b[2J",
|
Clear: "\x1b[H\x1b[2J",
|
||||||
EnterCA: "\x1b[?1049h",
|
EnterCA: "\x1b[?1049h",
|
||||||
ExitCA: "\x1b[?1049l",
|
ExitCA: "\x1b[?1049l",
|
||||||
@@ -38,38 +37,8 @@ func init() {
|
|||||||
StrikeThrough: "\x1b[9m",
|
StrikeThrough: "\x1b[9m",
|
||||||
Mouse: "\x1b[M",
|
Mouse: "\x1b[M",
|
||||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
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,
|
TrueColor: true,
|
||||||
AutoMargin: true,
|
AutoMargin: true,
|
||||||
DoubleUnderline: "\x1b[4:2m",
|
XTermLike: true,
|
||||||
CurlyUnderline: "\x1b[4:3m",
|
|
||||||
DottedUnderline: "\x1b[4:4m",
|
|
||||||
DashedUnderline: "\x1b[4:5m",
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+108
-940
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+15
-11
@@ -1,7 +1,7 @@
|
|||||||
//go:build plan9 || windows
|
//go:build plan9
|
||||||
// +build plan9 windows
|
// +build plan9
|
||||||
|
|
||||||
// Copyright 2022 The TCell Authors
|
// Copyright 2025 The TCell Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use file except in compliance with the License.
|
// you may not use file except in compliance with the License.
|
||||||
@@ -17,16 +17,20 @@
|
|||||||
|
|
||||||
package tcell
|
package tcell
|
||||||
|
|
||||||
// NB: We might someday wish to move Windows to this model. However,
|
import "os"
|
||||||
// that would probably mean sacrificing some of the richer key reporting
|
|
||||||
// that we can obtain with the console API present on Windows.
|
|
||||||
|
|
||||||
|
// initialize on Plan 9: if no TTY was provided, use the Plan 9 TTY.
|
||||||
func (t *tScreen) initialize() error {
|
func (t *tScreen) initialize() error {
|
||||||
if t.tty == nil {
|
if os.Getenv("TERM") == "" {
|
||||||
return ErrNoScreen
|
// TERM should be "vt100" in a vt(1) window; color/mouse support will be limited.
|
||||||
|
_ = os.Setenv("TERM", "vt100")
|
||||||
|
}
|
||||||
|
if t.tty == nil {
|
||||||
|
tty, err := NewDevTty()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.tty = tty
|
||||||
}
|
}
|
||||||
// If a tty was supplied (custom), it should work.
|
|
||||||
// Custom screen implementations will need to provide a TTY
|
|
||||||
// implementation that we can use.
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package tcell
|
||||||
|
|
||||||
|
import (
|
||||||
|
// import the stock terminals
|
||||||
|
_ "github.com/gdamore/tcell/v2/terminfo/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// initialize is used at application startup, and sets up the initial values
|
||||||
|
// including file descriptors used for terminals and saving the initial state
|
||||||
|
// so that it can be restored when the application terminates.
|
||||||
|
func (t *tScreen) initialize() error {
|
||||||
|
var err error
|
||||||
|
if t.tty == nil {
|
||||||
|
t.tty, err = NewDevTty()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
defaultTerm = "xterm-truecolor"
|
||||||
|
}
|
||||||
+270
@@ -0,0 +1,270 @@
|
|||||||
|
//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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// p9Tty implements tcell.Tty using Plan 9's /dev/cons and /dev/consctl.
|
||||||
|
// Raw mode is enabled by writing "rawon" to /dev/consctl while the fd stays open.
|
||||||
|
// Resize notifications are read from /dev/wctl: the first read returns geometry,
|
||||||
|
// subsequent reads block until the window changes (rio(4)).
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
// - kbdfs(8): cons/consctl rawon|rawoff semantics
|
||||||
|
// - rio(4): wctl geometry and blocking-on-change behavior
|
||||||
|
// - vt(1): VT100 emulator typically used for TUI programs on Plan 9
|
||||||
|
//
|
||||||
|
// Limitations:
|
||||||
|
// - We assume VT100-level capabilities (often no colors, no mouse).
|
||||||
|
// - Window size is conservative: we return 80x24 unless overridden.
|
||||||
|
// Set LINES/COLUMNS (or TCELL_LINES/TCELL_COLS) to refine.
|
||||||
|
// - Mouse and bracketed paste are not wired; terminfo/xterm queries
|
||||||
|
// are not attempted because vt(1) may not support them.
|
||||||
|
type p9Tty struct {
|
||||||
|
cons *os.File // /dev/cons (read+write)
|
||||||
|
consctl *os.File // /dev/consctl (write "rawon"/"rawoff")
|
||||||
|
wctl *os.File // /dev/wctl (resize notifications)
|
||||||
|
|
||||||
|
// protect close/stop; Read/Write are serialized by os.File
|
||||||
|
mu sync.Mutex
|
||||||
|
closed atomic.Bool
|
||||||
|
|
||||||
|
// resize callback
|
||||||
|
onResize atomic.Value // func()
|
||||||
|
wg sync.WaitGroup
|
||||||
|
stopCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDevTty() (Tty, error) { // tcell signature
|
||||||
|
return newPlan9TTY()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStdIoTty() (Tty, error) { // also required by tcell
|
||||||
|
// On Plan 9 there is no POSIX tty discipline on stdin/stdout;
|
||||||
|
// use /dev/cons explicitly for robustness.
|
||||||
|
return newPlan9TTY()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDevTtyFromDev(_ string) (Tty, error) { // required by tcell
|
||||||
|
// Plan 9 does not have multiple "ttys" in the POSIX sense;
|
||||||
|
// always bind to /dev/cons and /dev/consctl.
|
||||||
|
return newPlan9TTY()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPlan9TTY() (Tty, error) {
|
||||||
|
cons, err := os.OpenFile("/dev/cons", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open /dev/cons: %w", err)
|
||||||
|
}
|
||||||
|
consctl, err := os.OpenFile("/dev/consctl", os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
_ = cons.Close()
|
||||||
|
return nil, fmt.Errorf("open /dev/consctl: %w", err)
|
||||||
|
}
|
||||||
|
// /dev/wctl may not exist (console without rio); best-effort.
|
||||||
|
wctl, _ := os.OpenFile("/dev/wctl", os.O_RDWR, 0)
|
||||||
|
|
||||||
|
t := &p9Tty{
|
||||||
|
cons: cons,
|
||||||
|
consctl: consctl,
|
||||||
|
wctl: wctl,
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *p9Tty) Start() error {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
if t.closed.Load() {
|
||||||
|
return errors.New("tty closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate stop channel if absent or closed (supports resume).
|
||||||
|
if t.stopCh == nil || isClosed(t.stopCh) {
|
||||||
|
t.stopCh = make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put console into raw mode; remains active while consctl is open.
|
||||||
|
if _, err := t.consctl.Write([]byte("rawon")); err != nil {
|
||||||
|
return fmt.Errorf("enable raw mode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopen /dev/wctl on resume; best-effort (system console may lack it).
|
||||||
|
if t.wctl == nil {
|
||||||
|
if f, err := os.OpenFile("/dev/wctl", os.O_RDWR, 0); err == nil {
|
||||||
|
t.wctl = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.wctl != nil {
|
||||||
|
t.wg.Add(1)
|
||||||
|
go t.watchResize()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *p9Tty) Drain() error {
|
||||||
|
// Per tcell docs, this may reasonably be a no-op on non-POSIX ttys.
|
||||||
|
// Read deadlines are not available on plan9 os.File; we rely on Stop().
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *p9Tty) Stop() error {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
// Signal watcher to stop (if not already).
|
||||||
|
if t.stopCh != nil && !isClosed(t.stopCh) {
|
||||||
|
close(t.stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit raw mode first.
|
||||||
|
_, _ = t.consctl.Write([]byte("rawoff"))
|
||||||
|
|
||||||
|
// Closing wctl unblocks watchResize; nil it so Start() can reopen later.
|
||||||
|
if t.wctl != nil {
|
||||||
|
_ = t.wctl.Close()
|
||||||
|
t.wctl = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure watcher goroutine has exited before returning.
|
||||||
|
t.wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *p9Tty) Close() error {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
if t.closed.Swap(true) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.stopCh != nil && !isClosed(t.stopCh) {
|
||||||
|
close(t.stopCh)
|
||||||
|
}
|
||||||
|
_, _ = t.consctl.Write([]byte("rawoff"))
|
||||||
|
|
||||||
|
_ = t.cons.Close()
|
||||||
|
_ = t.consctl.Close()
|
||||||
|
if t.wctl != nil {
|
||||||
|
_ = t.wctl.Close()
|
||||||
|
t.wctl = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *p9Tty) Read(p []byte) (int, error) {
|
||||||
|
return t.cons.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *p9Tty) Write(p []byte) (int, error) {
|
||||||
|
return t.cons.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *p9Tty) NotifyResize(cb func()) {
|
||||||
|
if cb == nil {
|
||||||
|
t.onResize.Store((func())(nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.onResize.Store(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *p9Tty) WindowSize() (WindowSize, error) {
|
||||||
|
// Strategy:
|
||||||
|
// 1) honor explicit overrides (TCELL_LINES/TCELL_COLS, LINES/COLUMNS),
|
||||||
|
// 2) otherwise return conservative 80x24.
|
||||||
|
// Reading /dev/wctl gives pixel geometry, but char cell metrics are
|
||||||
|
// not generally available to non-draw clients; vt(1) is fixed-cell.
|
||||||
|
lines, cols := envInt("TCELL_LINES"), envInt("TCELL_COLS")
|
||||||
|
if lines == 0 {
|
||||||
|
lines = envInt("LINES")
|
||||||
|
}
|
||||||
|
if cols == 0 {
|
||||||
|
cols = envInt("COLUMNS")
|
||||||
|
}
|
||||||
|
if lines <= 0 {
|
||||||
|
lines = 24
|
||||||
|
}
|
||||||
|
if cols <= 0 {
|
||||||
|
cols = 80
|
||||||
|
}
|
||||||
|
return WindowSize{Width: cols, Height: lines}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchResize blocks on /dev/wctl reads; each read returns when the window
|
||||||
|
// changes size/position/state, per rio(4). We ignore the parsed geometry and
|
||||||
|
// just notify tcell to re-query WindowSize().
|
||||||
|
func (t *p9Tty) watchResize() {
|
||||||
|
defer t.wg.Done()
|
||||||
|
|
||||||
|
r := bufio.NewReader(t.wctl)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t.stopCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
// Each read delivers something like:
|
||||||
|
// " minx miny maxx maxy visible current\n"
|
||||||
|
// We don't need to parse here; just signal.
|
||||||
|
_, err := r.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// transient errors: continue
|
||||||
|
}
|
||||||
|
if cb, _ := t.onResize.Load().(func()); cb != nil {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func envInt(name string) int {
|
||||||
|
if s := strings.TrimSpace(os.Getenv(name)); s != "" {
|
||||||
|
if v, err := strconv.Atoi(s); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper: safe check if a channel is closed
|
||||||
|
func isClosed(ch <-chan struct{}) bool {
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
+290
@@ -0,0 +1,290 @@
|
|||||||
|
// Copyright 2026 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.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package tcell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
k32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
|
||||||
|
procGetNumberOfConsoleInputEvents = k32.NewProc("GetNumberOfConsoleInputEvents")
|
||||||
|
procFlushConsoleInputBuffer = k32.NewProc("FlushConsoleInputBuffer")
|
||||||
|
procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects")
|
||||||
|
procSetConsoleMode = k32.NewProc("SetConsoleMode")
|
||||||
|
procGetConsoleMode = k32.NewProc("GetConsoleMode")
|
||||||
|
procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
procCreateEvent = k32.NewProc("CreateEventW")
|
||||||
|
procSetEvent = k32.NewProc("SetEvent")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyEvent uint16 = 1
|
||||||
|
mouseEvent uint16 = 2
|
||||||
|
resizeEvent uint16 = 4
|
||||||
|
menuEvent uint16 = 8 // don't use
|
||||||
|
focusEvent uint16 = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
type inputRecord struct {
|
||||||
|
typ uint16
|
||||||
|
_ uint16
|
||||||
|
data [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type winTty struct {
|
||||||
|
buf chan byte
|
||||||
|
out syscall.Handle
|
||||||
|
in syscall.Handle
|
||||||
|
cancelFlag syscall.Handle
|
||||||
|
running bool
|
||||||
|
stopQ chan struct{}
|
||||||
|
resizeCb func()
|
||||||
|
cols uint16
|
||||||
|
rows uint16
|
||||||
|
pair []uint16 // for surrogate pairs (UTF-16)
|
||||||
|
oimode uint32 // original input mode
|
||||||
|
oomode uint32 // original output mode
|
||||||
|
oscreen consoleInfo
|
||||||
|
wg sync.WaitGroup
|
||||||
|
surrogate rune
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) Read(b []byte) (int, error) {
|
||||||
|
// first character read blocks
|
||||||
|
var num int
|
||||||
|
select {
|
||||||
|
case c := <-w.buf:
|
||||||
|
b[0] = c
|
||||||
|
num++
|
||||||
|
case <-w.stopQ:
|
||||||
|
// stopping, so make sure we eat everything, which might require
|
||||||
|
// very short sleeps to ensure all buffered data is consumed.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// second character read is non-blocking
|
||||||
|
for ; num < len(b); num++ {
|
||||||
|
select {
|
||||||
|
case c := <-w.buf:
|
||||||
|
b[num] = c
|
||||||
|
case <-time.After(time.Millisecond * 10):
|
||||||
|
return num, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) Write(b []byte) (int, error) {
|
||||||
|
esc := utf16.Encode([]rune(string(b)))
|
||||||
|
if len(esc) > 0 {
|
||||||
|
err := syscall.WriteConsole(w.out, &esc[0], uint32(len(esc)), nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) Close() error {
|
||||||
|
_ = syscall.Close(w.in)
|
||||||
|
_ = syscall.Close(w.out)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) Drain() error {
|
||||||
|
close(w.stopQ)
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
_, _, _ = procSetEvent.Call(uintptr(w.cancelFlag))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) getConsoleInput() error {
|
||||||
|
// cancelFlag comes first as WaitForMultipleObjects returns the lowest index
|
||||||
|
// in the event that both events are signaled.
|
||||||
|
waitObjects := []syscall.Handle{w.cancelFlag, w.in}
|
||||||
|
|
||||||
|
// As arrays are contiguous in memory, a pointer to the first object is the
|
||||||
|
// same as a pointer to the array itself.
|
||||||
|
pWaitObjects := unsafe.Pointer(&waitObjects[0])
|
||||||
|
|
||||||
|
rv, _, er := procWaitForMultipleObjects.Call(
|
||||||
|
uintptr(len(waitObjects)),
|
||||||
|
uintptr(pWaitObjects),
|
||||||
|
uintptr(0),
|
||||||
|
w32Infinite)
|
||||||
|
|
||||||
|
// WaitForMultipleObjects returns WAIT_OBJECT_0 + the index.
|
||||||
|
switch rv {
|
||||||
|
case w32WaitObject0: // w.cancelFlag
|
||||||
|
return errors.New("cancelled")
|
||||||
|
case w32WaitObject0 + 1: // w.in
|
||||||
|
// rec := &inputRecord{}
|
||||||
|
var nrec int32
|
||||||
|
rv, _, er := procGetNumberOfConsoleInputEvents.Call(
|
||||||
|
uintptr(w.in),
|
||||||
|
uintptr(unsafe.Pointer(&nrec)))
|
||||||
|
rec := make([]inputRecord, nrec)
|
||||||
|
rv, _, er = procReadConsoleInput.Call(
|
||||||
|
uintptr(w.in),
|
||||||
|
uintptr(unsafe.Pointer(&rec[0])),
|
||||||
|
uintptr(nrec),
|
||||||
|
uintptr(unsafe.Pointer(&nrec)))
|
||||||
|
if rv == 0 {
|
||||||
|
return er
|
||||||
|
}
|
||||||
|
loop:
|
||||||
|
for i := range nrec {
|
||||||
|
ir := rec[i]
|
||||||
|
switch ir.typ {
|
||||||
|
case keyEvent:
|
||||||
|
// we normally only expect to see ascii, but paste data may come in as UTF-16.
|
||||||
|
wc := rune(binary.LittleEndian.Uint16(ir.data[10:]))
|
||||||
|
if wc >= 0xD800 && wc <= 0xDBFF {
|
||||||
|
// if it was a high surrogate, which happens for pasted UTF-16,
|
||||||
|
// then save it until we get the low and can decode it.
|
||||||
|
w.surrogate = wc
|
||||||
|
continue
|
||||||
|
} else if wc >= 0xDC00 && wc <= 0xDFFF {
|
||||||
|
wc = utf16.DecodeRune(w.surrogate, wc)
|
||||||
|
}
|
||||||
|
w.surrogate = 0
|
||||||
|
for _, chr := range []byte(string(wc)) {
|
||||||
|
// We normally expect only to see ASCII (win32-input-mode),
|
||||||
|
// but apparently pasted data can arrive in UTF-16 here.
|
||||||
|
select {
|
||||||
|
case w.buf <- chr:
|
||||||
|
case <-w.stopQ:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case resizeEvent:
|
||||||
|
w.Lock()
|
||||||
|
w.cols = binary.LittleEndian.Uint16(ir.data[0:])
|
||||||
|
w.rows = binary.LittleEndian.Uint16(ir.data[2:])
|
||||||
|
cb := w.resizeCb
|
||||||
|
w.Unlock()
|
||||||
|
if cb != nil {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return er
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) scanInput() {
|
||||||
|
defer w.wg.Done()
|
||||||
|
for {
|
||||||
|
if e := w.getConsoleInput(); e != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) Start() error {
|
||||||
|
|
||||||
|
w.Lock()
|
||||||
|
defer w.Unlock()
|
||||||
|
|
||||||
|
if w.running {
|
||||||
|
return errors.New("already engaged")
|
||||||
|
}
|
||||||
|
_, _, _ = procFlushConsoleInputBuffer.Call(uintptr(w.in))
|
||||||
|
w.stopQ = make(chan struct{})
|
||||||
|
cf, _, err := procCreateEvent.Call(
|
||||||
|
uintptr(0),
|
||||||
|
uintptr(1),
|
||||||
|
uintptr(0),
|
||||||
|
uintptr(0))
|
||||||
|
if cf == uintptr(0) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.running = true
|
||||||
|
w.cancelFlag = syscall.Handle(cf)
|
||||||
|
|
||||||
|
_, _, _ = procSetConsoleMode.Call(uintptr(w.in),
|
||||||
|
uintptr(modeVtInput|modeResizeEn|modeExtendFlg))
|
||||||
|
_, _, _ = procSetConsoleMode.Call(uintptr(w.out),
|
||||||
|
uintptr(modeVtOutput|modeNoAutoNL|modeCookedOut|modeUnderline))
|
||||||
|
|
||||||
|
w.wg.Add(1)
|
||||||
|
go w.scanInput()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) Stop() error {
|
||||||
|
w.wg.Wait()
|
||||||
|
w.Lock()
|
||||||
|
defer w.Unlock()
|
||||||
|
_, _, _ = procSetConsoleMode.Call(uintptr(w.in), uintptr(w.oimode))
|
||||||
|
_, _, _ = procSetConsoleMode.Call(uintptr(w.out), uintptr(w.oomode))
|
||||||
|
_, _, _ = procFlushConsoleInputBuffer.Call(uintptr(w.in))
|
||||||
|
w.running = false
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) NotifyResize(cb func()) {
|
||||||
|
w.resizeCb = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *winTty) WindowSize() (WindowSize, error) {
|
||||||
|
w.Lock()
|
||||||
|
defer w.Unlock()
|
||||||
|
return WindowSize{Width: int(w.cols), Height: int(w.rows)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDevTty() (Tty, error) {
|
||||||
|
w := &winTty{}
|
||||||
|
var err error
|
||||||
|
w.in, err = syscall.Open("CONIN$", syscall.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.out, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
_ = syscall.Close(w.in)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.buf = make(chan byte, 128)
|
||||||
|
|
||||||
|
_, _, _ = procGetConsoleScreenBufferInfo.Call(uintptr(w.out), uintptr(unsafe.Pointer(&w.oscreen)))
|
||||||
|
_, _, _ = procGetConsoleMode.Call(uintptr(w.out), uintptr(unsafe.Pointer(&w.oomode)))
|
||||||
|
_, _, _ = procGetConsoleMode.Call(uintptr(w.in), uintptr(unsafe.Pointer(&w.oimode)))
|
||||||
|
w.rows = uint16(w.oscreen.size.y)
|
||||||
|
w.cols = uint16(w.oscreen.size.x)
|
||||||
|
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
+9
-50
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2024 The TCell Authors
|
// Copyright 2025 The TCell Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use file except in compliance with the License.
|
// you may not use file except in compliance with the License.
|
||||||
@@ -20,7 +20,6 @@ package tcell
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -121,7 +120,7 @@ func paletteColor(c Color) int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *wScreen) drawCell(x, y int) int {
|
func (t *wScreen) drawCell(x, y int) int {
|
||||||
mainc, combc, style, width := t.cells.GetContent(x, y)
|
str, style, width := t.cells.Get(x, y)
|
||||||
|
|
||||||
if !t.cells.Dirty(x, y) {
|
if !t.cells.Dirty(x, y) {
|
||||||
return width
|
return width
|
||||||
@@ -143,18 +142,8 @@ func (t *wScreen) drawCell(x, y int) int {
|
|||||||
uc = 0x000000
|
uc = 0x000000
|
||||||
}
|
}
|
||||||
|
|
||||||
s := ""
|
|
||||||
if len(combc) > 0 {
|
|
||||||
b := make([]rune, 0, 1 + len(combc))
|
|
||||||
b = append(b, mainc)
|
|
||||||
b = append(b, combc...)
|
|
||||||
s = string(b)
|
|
||||||
} else {
|
|
||||||
s = string(mainc)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.cells.SetDirty(x, y, false)
|
t.cells.SetDirty(x, y, false)
|
||||||
js.Global().Call("drawCell", x, y, s, fg, bg, int(style.attrs), int(us), int(uc))
|
js.Global().Call("drawCell", x, y, str, fg, bg, int(style.attrs), int(us), int(uc))
|
||||||
|
|
||||||
return width
|
return width
|
||||||
}
|
}
|
||||||
@@ -277,6 +266,12 @@ func (t *wScreen) DisableFocus() {
|
|||||||
t.Unlock()
|
t.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *wScreen) GetClipboard() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *wScreen) SetClipboard(_ []byte) {
|
||||||
|
}
|
||||||
|
|
||||||
func (t *wScreen) Size() (int, int) {
|
func (t *wScreen) Size() (int, int) {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
w, h := t.w, t.h
|
w, h := t.w, t.h
|
||||||
@@ -376,14 +371,6 @@ func (t *wScreen) onKeyEvent(this js.Value, args []js.Value) interface{} {
|
|||||||
mod |= ModMeta
|
mod |= ModMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for special case of Ctrl + key
|
|
||||||
if mod == ModCtrl {
|
|
||||||
if k, ok := WebKeyNames["Ctrl-"+strings.ToLower(key)]; ok {
|
|
||||||
t.postEvent(NewEventKey(k, 0, mod))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// next try function keys
|
// next try function keys
|
||||||
if k, ok := WebKeyNames[key]; ok {
|
if k, ok := WebKeyNames[key]; ok {
|
||||||
t.postEvent(NewEventKey(k, 0, mod))
|
t.postEvent(NewEventKey(k, 0, mod))
|
||||||
@@ -625,34 +612,6 @@ var WebKeyNames = map[string]Key{
|
|||||||
"F62": KeyF62,
|
"F62": KeyF62,
|
||||||
"F63": KeyF63,
|
"F63": KeyF63,
|
||||||
"F64": KeyF64,
|
"F64": KeyF64,
|
||||||
"Ctrl-a": KeyCtrlA, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-b": KeyCtrlB, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-c": KeyCtrlC, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-d": KeyCtrlD, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-e": KeyCtrlE, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-f": KeyCtrlF, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-g": KeyCtrlG, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-j": KeyCtrlJ, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-k": KeyCtrlK, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-l": KeyCtrlL, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-n": KeyCtrlN, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-o": KeyCtrlO, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-p": KeyCtrlP, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-q": KeyCtrlQ, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-r": KeyCtrlR, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-s": KeyCtrlS, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-t": KeyCtrlT, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-u": KeyCtrlU, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-v": KeyCtrlV, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-w": KeyCtrlW, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-x": KeyCtrlX, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-y": KeyCtrlY, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-z": KeyCtrlZ, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl- ": KeyCtrlSpace, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-_": KeyCtrlUnderscore, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-]": KeyCtrlRightSq, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-\\": KeyCtrlBackslash, // not reported by HTML- need to do special check
|
|
||||||
"Ctrl-^": KeyCtrlCarat, // not reported by HTML- need to do special check
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var curStyleClasses = map[CursorStyle]string{
|
var curStyleClasses = map[CursorStyle]string{
|
||||||
|
|||||||
+7
-2
@@ -1,9 +1,14 @@
|
|||||||
# See for configurations: https://golangci-lint.run/usage/configuration/
|
# See for configurations: https://golangci-lint.run/usage/configuration/
|
||||||
version: 2
|
version: "2"
|
||||||
|
|
||||||
|
linters:
|
||||||
|
default: none
|
||||||
|
enable:
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
|
||||||
# See: https://golangci-lint.run/usage/formatters/
|
# See: https://golangci-lint.run/usage/formatters/
|
||||||
formatters:
|
formatters:
|
||||||
default: none
|
|
||||||
enable:
|
enable:
|
||||||
- gofmt # https://pkg.go.dev/cmd/gofmt
|
- gofmt # https://pkg.go.dev/cmd/gofmt
|
||||||
- gofumpt # https://github.com/mvdan/gofumpt
|
- gofumpt # https://github.com/mvdan/gofumpt
|
||||||
|
|||||||
+92
@@ -1,3 +1,95 @@
|
|||||||
|
# 5.9.2 (April 18, 2026)
|
||||||
|
|
||||||
|
Fix SQL Injection via placeholder confusion with dollar quoted string literals (GHSA-j88v-2chj-qfwx)
|
||||||
|
|
||||||
|
SQL injection can occur when:
|
||||||
|
|
||||||
|
1. The non-default simple protocol is used.
|
||||||
|
2. A dollar quoted string literal is used in the SQL query.
|
||||||
|
3. That query contains text that would be would be interpreted outside as a placeholder outside of a string literal.
|
||||||
|
4. The value of that placeholder is controllable by the attacker.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
```go
|
||||||
|
attackValue := `$tag$; drop table canary; --`
|
||||||
|
_, err = tx.Exec(ctx, `select $tag$ $1 $tag$, $1`, pgx.QueryExecModeSimpleProtocol, attackValue)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is unlikely to occur outside of a contrived scenario.
|
||||||
|
|
||||||
|
# 5.9.1 (March 22, 2026)
|
||||||
|
|
||||||
|
* Fix: batch result format corruption when using cached prepared statements (reported by Dirkjan Bussink)
|
||||||
|
|
||||||
|
# 5.9.0 (March 21, 2026)
|
||||||
|
|
||||||
|
This release includes a number of new features such as SCRAM-SHA-256-PLUS support, OAuth authentication support, and
|
||||||
|
PostgreSQL protocol 3.2 support.
|
||||||
|
|
||||||
|
It significantly reduces the amount of network traffic when using prepared statements (which are used automatically by
|
||||||
|
default) by avoiding unnecessary Describe Portal messages. This also reduces local memory usage.
|
||||||
|
|
||||||
|
It also includes multiple fixes for potential DoS due to panic or OOM if connected to a malicious server that sends
|
||||||
|
deliberately malformed messages.
|
||||||
|
|
||||||
|
* Require Go 1.25+
|
||||||
|
* Add SCRAM-SHA-256-PLUS support (Adam Brightwell)
|
||||||
|
* Add OAuth authentication support for PostgreSQL 18 (David Schneider)
|
||||||
|
* Add PostgreSQL protocol 3.2 support (Dirkjan Bussink)
|
||||||
|
* Add tsvector type support (Adam Brightwell)
|
||||||
|
* Skip Describe Portal for cached prepared statements reducing network round trips
|
||||||
|
* Make LoadTypes query easier to support on "postgres-like" servers (Jelte Fennema-Nio)
|
||||||
|
* Default empty user to current OS user matching libpq behavior (ShivangSrivastava)
|
||||||
|
* Optimize LRU statement cache with custom linked list and node pooling (Mathias Bogaert)
|
||||||
|
* Optimize date scanning by replacing regex with manual parsing (Mathias Bogaert)
|
||||||
|
* Optimize pgio append/set functions with direct byte shifts (Mathias Bogaert)
|
||||||
|
* Make RowsAffected faster (Abhishek Chanda)
|
||||||
|
* Fix: Pipeline.Close panic when server sends multiple FATAL errors (Varun Chawla)
|
||||||
|
* Fix: ContextWatcher goroutine leak (Hank Donnay)
|
||||||
|
* Fix: stdlib discard connections with open transactions in ResetSession (Jeremy Schneider)
|
||||||
|
* Fix: pipelineBatchResults.Exec silently swallowing lastRows error
|
||||||
|
* Fix: ColumnTypeLength using BPCharArrayOID instead of BPCharOID
|
||||||
|
* Fix: TSVector text encoding returning nil for valid empty tsvector
|
||||||
|
* Fix: wrong error messages for Int2 and Int4 underflow
|
||||||
|
* Fix: Numeric nil Int pointer dereference with Valid: true
|
||||||
|
* Fix: reversed strings.ContainsAny arguments in Numeric.ScanScientific
|
||||||
|
* Fix: message length parsing on 32-bit platforms
|
||||||
|
* Fix: FunctionCallResponse.Decode mishandling of signed result size
|
||||||
|
* Fix: returning wrong error in configTLS when DecryptPEMBlock fails (Maxim Motyshen)
|
||||||
|
* Fix: misleading ParseConfig error when default_query_exec_mode is invalid (Skarm)
|
||||||
|
* Fix: missed Unwatch in Pipeline error paths
|
||||||
|
* Clarify too many failed acquire attempts error message
|
||||||
|
* Better error wrapping with context and SQL statement (Aneesh Makala)
|
||||||
|
* Enable govet and ineffassign linters (Federico Guerinoni)
|
||||||
|
* Guard against various malformed binary messages (arrays, hstore, multirange, protocol messages)
|
||||||
|
* Fix various godoc comments (ferhat elmas)
|
||||||
|
* Fix typos in comments (Oleksandr Redko)
|
||||||
|
|
||||||
|
# 5.8.0 (December 26, 2025)
|
||||||
|
|
||||||
|
* Require Go 1.24+
|
||||||
|
* Remove golang.org/x/crypto dependency
|
||||||
|
* Add OptionShouldPing to control ResetSession ping behavior (ilyam8)
|
||||||
|
* Fix: Avoid overflow when MaxConns is set to MaxInt32
|
||||||
|
* Fix: Close batch pipeline after a query error (Anthonin Bonnefoy)
|
||||||
|
* Faster shutdown of pgxpool.Pool background goroutines (Blake Gentry)
|
||||||
|
* Add pgxpool ping timeout (Amirsalar Safaei)
|
||||||
|
* Fix: Rows.FieldDescriptions for empty query
|
||||||
|
* Scan unknown types into *any as string or []byte based on format code
|
||||||
|
* Optimize pgtype.Numeric (Philip Dubé)
|
||||||
|
* Add AfterNetConnect hook to pgconn.Config
|
||||||
|
* Fix: Handle for preparing statements that fail during the Describe phase
|
||||||
|
* Fix overflow in numeric scanning (Ilia Demianenko)
|
||||||
|
* Fix: json/jsonb sql.Scanner source type is []byte
|
||||||
|
* Migrate from math/rand to math/rand/v2 (Mathias Bogaert)
|
||||||
|
* Optimize internal iobufpool (Mathias Bogaert)
|
||||||
|
* Optimize stmtcache invalidation (Mathias Bogaert)
|
||||||
|
* Fix: missing error case in interval parsing (Maxime Soulé)
|
||||||
|
* Fix: invalidate statement/description cache in Exec (James Hartig)
|
||||||
|
* ColumnTypeLength method return the type length for varbit type (DengChan)
|
||||||
|
* Array and Composite codecs handle typed nils
|
||||||
|
|
||||||
# 5.7.6 (September 8, 2025)
|
# 5.7.6 (September 8, 2025)
|
||||||
|
|
||||||
* Use ParseConfigError in pgx.ParseConfig and pgxpool.ParseConfig (Yurasov Ilia)
|
* Use ParseConfigError in pgx.ParseConfig and pgxpool.ParseConfig (Yurasov Ilia)
|
||||||
|
|||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
pgx is a PostgreSQL driver and toolkit for Go (`github.com/jackc/pgx/v5`). It provides both a native PostgreSQL interface and a `database/sql` compatible driver. Requires Go 1.25+ and supports PostgreSQL 14+ and CockroachDB.
|
||||||
|
|
||||||
|
## Build & Test Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests (requires PGX_TEST_DATABASE to be set)
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# Run a specific test
|
||||||
|
go test -run TestFunctionName ./...
|
||||||
|
|
||||||
|
# Run tests for a specific package
|
||||||
|
go test ./pgconn/...
|
||||||
|
|
||||||
|
# Run tests with race detector
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
# DevContainer: run tests against specific PostgreSQL versions
|
||||||
|
./test.sh pg18 # Default: PostgreSQL 18
|
||||||
|
./test.sh pg16 -run TestConnect # Specific test against PG16
|
||||||
|
./test.sh crdb # CockroachDB
|
||||||
|
./test.sh all # All targets (pg14-18 + crdb)
|
||||||
|
|
||||||
|
# Format (always run after making changes)
|
||||||
|
goimports -w .
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
golangci-lint run ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Database Setup
|
||||||
|
|
||||||
|
Tests require `PGX_TEST_DATABASE` environment variable. In the devcontainer, `test.sh` handles this. For local development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PGX_TEST_DATABASE="host=localhost user=postgres password=postgres dbname=pgx_test"
|
||||||
|
```
|
||||||
|
|
||||||
|
The test database needs extensions: `hstore`, `ltree`, and a `uint64` domain. See `testsetup/postgresql_setup.sql` for full setup. Many tests are skipped unless additional `PGX_TEST_*` env vars are set (for TLS, SCRAM, MD5, unix socket, PgBouncer testing).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The codebase is a layered architecture, bottom-up:
|
||||||
|
|
||||||
|
- **pgproto3/** — PostgreSQL wire protocol v3 encoder/decoder. Defines `FrontendMessage` and `BackendMessage` types for every protocol message.
|
||||||
|
- **pgconn/** — Low-level connection layer (roughly libpq-equivalent). Handles authentication, TLS, query execution, COPY protocol, and notifications. `PgConn` is the core type.
|
||||||
|
- **pgx** (root package) — High-level query interface built on `pgconn`. Provides `Conn`, `Rows`, `Tx`, `Batch`, `CopyFrom`, and generic helpers like `CollectRows`/`ForEachRow`. Includes automatic statement caching (LRU).
|
||||||
|
- **pgtype/** — Type system mapping between Go and PostgreSQL types (70+ types). Key interfaces: `Codec`, `Type`, `TypeMap`. Custom types (enums, composites, domains) are registered through `TypeMap`.
|
||||||
|
- **pgxpool/** — Concurrency-safe connection pool built on `puddle/v2`. `Pool` is the main type; wraps `pgx.Conn`.
|
||||||
|
- **stdlib/** — `database/sql` compatibility adapter.
|
||||||
|
|
||||||
|
Supporting packages:
|
||||||
|
- **internal/stmtcache/** — Prepared statement cache with LRU eviction
|
||||||
|
- **internal/sanitize/** — SQL query sanitization
|
||||||
|
- **tracelog/** — Logging adapter that implements tracer interfaces
|
||||||
|
- **multitracer/** — Composes multiple tracers into one
|
||||||
|
- **pgxtest/** — Test helpers for running tests across connection types
|
||||||
|
|
||||||
|
## Key Design Conventions
|
||||||
|
|
||||||
|
- **Semantic versioning** — strictly followed. Do not break the public API (no removing or renaming exported types, functions, methods, or fields; no changing function signatures).
|
||||||
|
- **Minimal dependencies** — adding new dependencies is strongly discouraged (see CONTRIBUTING.md).
|
||||||
|
- **Context-based** — all blocking operations take `context.Context`.
|
||||||
|
- **Tracer interfaces** — observability via `QueryTracer`, `BatchTracer`, `CopyFromTracer`, `PrepareTracer` on `ConnConfig.Tracer`.
|
||||||
|
- **Formatting** — always run `goimports -w .` after making changes to ensure code is properly formatted. CI checks formatting via `gofmt -l -s -w . && git diff --exit-code`. `gofumpt` with extra rules is also enforced via `golangci-lint`.
|
||||||
|
- **Linters** — `govet` and `ineffassign` only (configured in `.golangci.yml`).
|
||||||
|
- **CI matrix** — tests run against Go 1.25/1.26 × PostgreSQL 14-18 + CockroachDB, on Linux and Windows. Race detector enabled on Linux only.
|
||||||
+22
-4
@@ -10,6 +10,18 @@ proposal. This will help to ensure your proposed change has a reasonable chance
|
|||||||
Adding a dependency is a big deal. While on occasion a new dependency may be accepted, the default answer to any change
|
Adding a dependency is a big deal. While on occasion a new dependency may be accepted, the default answer to any change
|
||||||
that adds a dependency is no.
|
that adds a dependency is no.
|
||||||
|
|
||||||
|
## AI
|
||||||
|
|
||||||
|
Using AI is acceptable (not that it can really be stopped) under one the following conditions.
|
||||||
|
|
||||||
|
* AI was used, but you deeply understand the code and you can answer questions regarding your change. You are not going
|
||||||
|
to answer questions with "I don't know", AI did it. You are not going to "answer" questions by relaying them to your
|
||||||
|
agent. This is wasteful of the code reviewer's time.
|
||||||
|
* AI was used to solve a problem without your deep understanding. This can still be a good starting point for a fix or
|
||||||
|
feature. But you need to clearly state that this is an AI proposal. You should include additional information such as
|
||||||
|
the AI used and what prompts were used. You should also be aware that large, complicated, or subtle changes may be
|
||||||
|
rejected simply because the reviewer is not confident in a change that no human understands.
|
||||||
|
|
||||||
## Development Environment Setup
|
## Development Environment Setup
|
||||||
|
|
||||||
pgx tests naturally require a PostgreSQL database. It will connect to the database specified in the `PGX_TEST_DATABASE`
|
pgx tests naturally require a PostgreSQL database. It will connect to the database specified in the `PGX_TEST_DATABASE`
|
||||||
@@ -17,7 +29,12 @@ environment variable. The `PGX_TEST_DATABASE` environment variable can either be
|
|||||||
the standard `PG*` environment variables will be respected. Consider using [direnv](https://github.com/direnv/direnv) to
|
the standard `PG*` environment variables will be respected. Consider using [direnv](https://github.com/direnv/direnv) to
|
||||||
simplify environment variable handling.
|
simplify environment variable handling.
|
||||||
|
|
||||||
### Using an Existing PostgreSQL Cluster
|
### Devcontainer
|
||||||
|
|
||||||
|
The easiest way to start development is with the included devcontainer. It includes containers for each supported
|
||||||
|
PostgreSQL version as well as CockroachDB. `./test.sh all` will run the tests against all database types.
|
||||||
|
|
||||||
|
### Using an Existing PostgreSQL Cluster Outside of a Devcontainer
|
||||||
|
|
||||||
If you already have a PostgreSQL development server this is the quickest way to start and run the majority of the pgx
|
If you already have a PostgreSQL development server this is the quickest way to start and run the majority of the pgx
|
||||||
test suite. Some tests will be skipped that require server configuration changes (e.g. those testing different
|
test suite. Some tests will be skipped that require server configuration changes (e.g. those testing different
|
||||||
@@ -49,7 +66,7 @@ go test ./...
|
|||||||
|
|
||||||
This will run the vast majority of the tests, but some tests will be skipped (e.g. those testing different connection methods).
|
This will run the vast majority of the tests, but some tests will be skipped (e.g. those testing different connection methods).
|
||||||
|
|
||||||
### Creating a New PostgreSQL Cluster Exclusively for Testing
|
### Creating a New PostgreSQL Cluster Exclusively for Testing Outside of a Devcontainer
|
||||||
|
|
||||||
The following environment variables need to be set both for initial setup and whenever the tests are run. (direnv is
|
The following environment variables need to be set both for initial setup and whenever the tests are run. (direnv is
|
||||||
highly recommended). Depending on your platform, you may need to change the host for `PGX_TEST_UNIX_SOCKET_CONN_STRING`.
|
highly recommended). Depending on your platform, you may need to change the host for `PGX_TEST_UNIX_SOCKET_CONN_STRING`.
|
||||||
@@ -63,10 +80,11 @@ export POSTGRESQL_DATA_DIR=postgresql
|
|||||||
export PGX_TEST_DATABASE="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret"
|
export PGX_TEST_DATABASE="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret"
|
||||||
export PGX_TEST_UNIX_SOCKET_CONN_STRING="host=/private/tmp database=pgx_test"
|
export PGX_TEST_UNIX_SOCKET_CONN_STRING="host=/private/tmp database=pgx_test"
|
||||||
export PGX_TEST_TCP_CONN_STRING="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret"
|
export PGX_TEST_TCP_CONN_STRING="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret"
|
||||||
export PGX_TEST_SCRAM_PASSWORD_CONN_STRING="host=127.0.0.1 user=pgx_scram password=secret database=pgx_test"
|
export PGX_TEST_SCRAM_PASSWORD_CONN_STRING="host=127.0.0.1 user=pgx_scram password=secret database=pgx_test channel_binding=disable"
|
||||||
|
export PGX_TEST_SCRAM_PLUS_CONN_STRING="host=localhost user=pgx_ssl password=secret sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem database=pgx_test channel_binding=require"
|
||||||
export PGX_TEST_MD5_PASSWORD_CONN_STRING="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret"
|
export PGX_TEST_MD5_PASSWORD_CONN_STRING="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret"
|
||||||
export PGX_TEST_PLAIN_PASSWORD_CONN_STRING="host=127.0.0.1 user=pgx_pw password=secret"
|
export PGX_TEST_PLAIN_PASSWORD_CONN_STRING="host=127.0.0.1 user=pgx_pw password=secret"
|
||||||
export PGX_TEST_TLS_CONN_STRING="host=localhost user=pgx_ssl password=secret sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem"
|
export PGX_TEST_TLS_CONN_STRING="host=localhost user=pgx_ssl password=secret sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem channel_binding=disable"
|
||||||
export PGX_SSL_PASSWORD=certpw
|
export PGX_SSL_PASSWORD=certpw
|
||||||
export PGX_TEST_TLS_CLIENT_CONN_STRING="host=localhost user=pgx_sslcert sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem database=pgx_test sslcert=`pwd`/.testdb/pgx_sslcert.crt sslkey=`pwd`/.testdb/pgx_sslcert.key"
|
export PGX_TEST_TLS_CLIENT_CONN_STRING="host=localhost user=pgx_sslcert sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem database=pgx_test sslcert=`pwd`/.testdb/pgx_sslcert.crt sslkey=`pwd`/.testdb/pgx_sslcert.key"
|
||||||
```
|
```
|
||||||
|
|||||||
+3
-2
@@ -92,7 +92,7 @@ See the presentation at Golang Estonia, [PGX Top to Bottom](https://www.youtube.
|
|||||||
|
|
||||||
## Supported Go and PostgreSQL Versions
|
## Supported Go and PostgreSQL Versions
|
||||||
|
|
||||||
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.23 and higher and PostgreSQL 13 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
|
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.25 and higher and PostgreSQL 14 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
|
||||||
|
|
||||||
## Version Policy
|
## Version Policy
|
||||||
|
|
||||||
@@ -120,6 +120,7 @@ pgerrcode contains constants for the PostgreSQL error codes.
|
|||||||
|
|
||||||
* [github.com/jackc/pgx-gofrs-uuid](https://github.com/jackc/pgx-gofrs-uuid)
|
* [github.com/jackc/pgx-gofrs-uuid](https://github.com/jackc/pgx-gofrs-uuid)
|
||||||
* [github.com/jackc/pgx-shopspring-decimal](https://github.com/jackc/pgx-shopspring-decimal)
|
* [github.com/jackc/pgx-shopspring-decimal](https://github.com/jackc/pgx-shopspring-decimal)
|
||||||
|
* [github.com/ColeBurch/pgx-govalues-decimal](https://github.com/ColeBurch/pgx-govalues-decimal)
|
||||||
* [github.com/twpayne/pgx-geos](https://github.com/twpayne/pgx-geos) ([PostGIS](https://postgis.net/) and [GEOS](https://libgeos.org/) via [go-geos](https://github.com/twpayne/go-geos))
|
* [github.com/twpayne/pgx-geos](https://github.com/twpayne/pgx-geos) ([PostGIS](https://postgis.net/) and [GEOS](https://libgeos.org/) via [go-geos](https://github.com/twpayne/go-geos))
|
||||||
* [github.com/vgarvardt/pgx-google-uuid](https://github.com/vgarvardt/pgx-google-uuid)
|
* [github.com/vgarvardt/pgx-google-uuid](https://github.com/vgarvardt/pgx-google-uuid)
|
||||||
|
|
||||||
@@ -186,6 +187,6 @@ Simple Golang implementation for transactional outbox pattern for PostgreSQL usi
|
|||||||
|
|
||||||
Simplifies working with the pgx library, providing convenient scanning of nested structures.
|
Simplifies working with the pgx library, providing convenient scanning of nested structures.
|
||||||
|
|
||||||
## [https://github.com/KoNekoD/pgx-colon-query-rewriter](https://github.com/KoNekoD/pgx-colon-query-rewriter)
|
### [https://github.com/KoNekoD/pgx-colon-query-rewriter](https://github.com/KoNekoD/pgx-colon-query-rewriter)
|
||||||
|
|
||||||
Implementation of the pgx query rewriter to use ':' instead of '@' in named query parameters.
|
Implementation of the pgx query rewriter to use ':' instead of '@' in named query parameters.
|
||||||
|
|||||||
+83
-15
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
)
|
)
|
||||||
|
|
||||||
// QueuedQuery is a query that has been queued for execution via a Batch.
|
// QueuedQuery is a query that has been queued for execution via a [Batch].
|
||||||
type QueuedQuery struct {
|
type QueuedQuery struct {
|
||||||
SQL string
|
SQL string
|
||||||
Arguments []any
|
Arguments []any
|
||||||
@@ -46,7 +46,7 @@ func (qq *QueuedQuery) QueryRow(fn func(row Row) error) {
|
|||||||
//
|
//
|
||||||
// Note: for simple batch insert uses where it is not required to handle
|
// Note: for simple batch insert uses where it is not required to handle
|
||||||
// each potential error individually, it's sufficient to not set any callbacks,
|
// each potential error individually, it's sufficient to not set any callbacks,
|
||||||
// and just handle the return value of BatchResults.Close.
|
// and just handle the return value of [BatchResults.Close].
|
||||||
func (qq *QueuedQuery) Exec(fn func(ct pgconn.CommandTag) error) {
|
func (qq *QueuedQuery) Exec(fn func(ct pgconn.CommandTag) error) {
|
||||||
qq.Fn = func(br BatchResults) error {
|
qq.Fn = func(br BatchResults) error {
|
||||||
ct, err := br.Exec()
|
ct, err := br.Exec()
|
||||||
@@ -65,12 +65,13 @@ type Batch struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Queue queues a query to batch b. query can be an SQL query or the name of a prepared statement. The only pgx option
|
// Queue queues a query to batch b. query can be an SQL query or the name of a prepared statement. The only pgx option
|
||||||
// argument that is supported is QueryRewriter. Queries are executed using the connection's DefaultQueryExecMode.
|
// argument that is supported is [QueryRewriter]. Queries are executed using the connection's DefaultQueryExecMode
|
||||||
|
// (see [ConnConfig.DefaultQueryExecMode]).
|
||||||
//
|
//
|
||||||
// While query can contain multiple statements if the connection's DefaultQueryExecMode is QueryModeSimple, this should
|
// While query can contain multiple statements if the connection's DefaultQueryExecMode is [QueryExecModeSimpleProtocol],
|
||||||
// be avoided. QueuedQuery.Fn must not be set as it will only be called for the first query. That is, QueuedQuery.Query,
|
// this should be avoided. QueuedQuery.Fn must not be set as it will only be called for the first query. That is,
|
||||||
// QueuedQuery.QueryRow, and QueuedQuery.Exec must not be called. In addition, any error messages or tracing that
|
// [QueuedQuery.Query], [QueuedQuery.QueryRow], and [QueuedQuery.Exec] must not be called. In addition, any error
|
||||||
// include the current query may reference the wrong query.
|
// messages or tracing that include the current query may reference the wrong query.
|
||||||
func (b *Batch) Queue(query string, arguments ...any) *QueuedQuery {
|
func (b *Batch) Queue(query string, arguments ...any) *QueuedQuery {
|
||||||
qq := &QueuedQuery{
|
qq := &QueuedQuery{
|
||||||
SQL: query,
|
SQL: query,
|
||||||
@@ -86,20 +87,20 @@ func (b *Batch) Len() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BatchResults interface {
|
type BatchResults interface {
|
||||||
// Exec reads the results from the next query in the batch as if the query has been sent with Conn.Exec. Prefer
|
// Exec reads the results from the next query in the batch as if the query has been sent with [Conn.Exec]. Prefer
|
||||||
// calling Exec on the QueuedQuery, or just calling Close.
|
// calling Exec on the QueuedQuery, or just calling Close.
|
||||||
Exec() (pgconn.CommandTag, error)
|
Exec() (pgconn.CommandTag, error)
|
||||||
|
|
||||||
// Query reads the results from the next query in the batch as if the query has been sent with Conn.Query. Prefer
|
// Query reads the results from the next query in the batch as if the query has been sent with [Conn.Query]. Prefer
|
||||||
// calling Query on the QueuedQuery.
|
// calling [QueuedQuery.Query].
|
||||||
Query() (Rows, error)
|
Query() (Rows, error)
|
||||||
|
|
||||||
// QueryRow reads the results from the next query in the batch as if the query has been sent with Conn.QueryRow.
|
// QueryRow reads the results from the next query in the batch as if the query has been sent with [Conn.QueryRow].
|
||||||
// Prefer calling QueryRow on the QueuedQuery.
|
// Prefer calling [QueuedQuery.QueryRow].
|
||||||
QueryRow() Row
|
QueryRow() Row
|
||||||
|
|
||||||
// Close closes the batch operation. All unread results are read and any callback functions registered with
|
// Close closes the batch operation. All unread results are read and any callback functions registered with
|
||||||
// QueuedQuery.Query, QueuedQuery.QueryRow, or QueuedQuery.Exec will be called. If a callback function returns an
|
// [QueuedQuery.Query], [QueuedQuery.QueryRow], or [QueuedQuery.Exec] will be called. If a callback function returns an
|
||||||
// error or the batch encounters an error subsequent callback functions will not be called.
|
// error or the batch encounters an error subsequent callback functions will not be called.
|
||||||
//
|
//
|
||||||
// For simple batch inserts inside a transaction or similar queries, it's sufficient to not set any callbacks,
|
// For simple batch inserts inside a transaction or similar queries, it's sufficient to not set any callbacks,
|
||||||
@@ -272,7 +273,7 @@ func (br *batchResults) nextQueryAndArgs() (query string, args []any, ok bool) {
|
|||||||
ok = true
|
ok = true
|
||||||
br.qqIdx++
|
br.qqIdx++
|
||||||
}
|
}
|
||||||
return
|
return query, args, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
type pipelineBatchResults struct {
|
type pipelineBatchResults struct {
|
||||||
@@ -296,6 +297,7 @@ func (br *pipelineBatchResults) Exec() (pgconn.CommandTag, error) {
|
|||||||
return pgconn.CommandTag{}, fmt.Errorf("batch already closed")
|
return pgconn.CommandTag{}, fmt.Errorf("batch already closed")
|
||||||
}
|
}
|
||||||
if br.lastRows != nil && br.lastRows.err != nil {
|
if br.lastRows != nil && br.lastRows.err != nil {
|
||||||
|
br.err = br.lastRows.err
|
||||||
return pgconn.CommandTag{}, br.err
|
return pgconn.CommandTag{}, br.err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,7 +406,6 @@ func (br *pipelineBatchResults) Close() error {
|
|||||||
|
|
||||||
if br.err == nil && br.lastRows != nil && br.lastRows.err != nil {
|
if br.err == nil && br.lastRows != nil && br.lastRows.err != nil {
|
||||||
br.err = br.lastRows.err
|
br.err = br.lastRows.err
|
||||||
return br.err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if br.closed {
|
if br.closed {
|
||||||
@@ -451,6 +452,45 @@ func (br *pipelineBatchResults) nextQueryAndArgs() (query string, args []any, er
|
|||||||
return bi.SQL, bi.Arguments, nil
|
return bi.SQL, bi.Arguments, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type emptyBatchResults struct {
|
||||||
|
conn *Conn
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec reads the results from the next query in the batch as if the query has been sent with Exec.
|
||||||
|
func (br *emptyBatchResults) Exec() (pgconn.CommandTag, error) {
|
||||||
|
if br.closed {
|
||||||
|
return pgconn.CommandTag{}, fmt.Errorf("batch already closed")
|
||||||
|
}
|
||||||
|
return pgconn.CommandTag{}, errors.New("no more results in batch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query reads the results from the next query in the batch as if the query has been sent with Query.
|
||||||
|
func (br *emptyBatchResults) Query() (Rows, error) {
|
||||||
|
if br.closed {
|
||||||
|
alreadyClosedErr := fmt.Errorf("batch already closed")
|
||||||
|
return &baseRows{err: alreadyClosedErr, closed: true}, alreadyClosedErr
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := br.conn.getRows(context.Background(), "", nil)
|
||||||
|
rows.err = errors.New("no more results in batch")
|
||||||
|
rows.closed = true
|
||||||
|
return rows, rows.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRow reads the results from the next query in the batch as if the query has been sent with QueryRow.
|
||||||
|
func (br *emptyBatchResults) QueryRow() Row {
|
||||||
|
rows, _ := br.Query()
|
||||||
|
return (*connRow)(rows.(*baseRows))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the batch operation. Any error that occurred during a batch operation may have made it impossible to
|
||||||
|
// resyncronize the connection with the server. In this case the underlying connection will have been closed.
|
||||||
|
func (br *emptyBatchResults) Close() error {
|
||||||
|
br.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// invalidates statement and description caches on batch results error
|
// invalidates statement and description caches on batch results error
|
||||||
func invalidateCachesOnBatchResultsError(conn *Conn, b *Batch, err error) {
|
func invalidateCachesOnBatchResultsError(conn *Conn, b *Batch, err error) {
|
||||||
if err != nil && conn != nil && b != nil {
|
if err != nil && conn != nil && b != nil {
|
||||||
@@ -467,3 +507,31 @@ func invalidateCachesOnBatchResultsError(conn *Conn, b *Batch, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrPreprocessingBatch occurs when an error is encountered while preprocessing a batch.
|
||||||
|
// The two preprocessing steps are "prepare" (server-side SQL parse/plan) and
|
||||||
|
// "build" (client-side argument encoding).
|
||||||
|
type ErrPreprocessingBatch struct {
|
||||||
|
step string // "prepare" or "build"
|
||||||
|
sql string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newErrPreprocessingBatch(step, sql string, err error) ErrPreprocessingBatch {
|
||||||
|
return ErrPreprocessingBatch{step: step, sql: sql, err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrPreprocessingBatch) Error() string {
|
||||||
|
// intentionally not including the SQL query in the error message
|
||||||
|
// to avoid leaking potentially sensitive information into logs.
|
||||||
|
// If the user wants the SQL, they can call SQL().
|
||||||
|
return fmt.Sprintf("error preprocessing batch (%s): %v", e.step, e.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrPreprocessingBatch) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrPreprocessingBatch) SQL() string {
|
||||||
|
return e.sql
|
||||||
|
}
|
||||||
|
|||||||
+54
-20
@@ -17,8 +17,8 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConnConfig contains all the options used to establish a connection. It must be created by ParseConfig and
|
// ConnConfig contains all the options used to establish a connection. It must be created by [ParseConfig] and
|
||||||
// then it can be modified. A manually initialized ConnConfig will cause ConnectConfig to panic.
|
// then it can be modified. A manually initialized ConnConfig will cause [ConnectConfig] to panic.
|
||||||
type ConnConfig struct {
|
type ConnConfig struct {
|
||||||
pgconn.Config
|
pgconn.Config
|
||||||
|
|
||||||
@@ -37,8 +37,8 @@ type ConnConfig struct {
|
|||||||
|
|
||||||
// DefaultQueryExecMode controls the default mode for executing queries. By default pgx uses the extended protocol
|
// DefaultQueryExecMode controls the default mode for executing queries. By default pgx uses the extended protocol
|
||||||
// and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as
|
// and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as
|
||||||
// PGBouncer. In this case it may be preferable to use QueryExecModeExec or QueryExecModeSimpleProtocol. The same
|
// PGBouncer. In this case it may be preferable to use [QueryExecModeExec] or [QueryExecModeSimpleProtocol]. The same
|
||||||
// functionality can be controlled on a per query basis by passing a QueryExecMode as the first query argument.
|
// functionality can be controlled on a per query basis by passing a [QueryExecMode] as the first query argument.
|
||||||
DefaultQueryExecMode QueryExecMode
|
DefaultQueryExecMode QueryExecMode
|
||||||
|
|
||||||
createdByParseConfig bool // Used to enforce created by ParseConfig rule.
|
createdByParseConfig bool // Used to enforce created by ParseConfig rule.
|
||||||
@@ -68,6 +68,7 @@ type Conn struct {
|
|||||||
pgConn *pgconn.PgConn
|
pgConn *pgconn.PgConn
|
||||||
config *ConnConfig // config used when establishing this connection
|
config *ConnConfig // config used when establishing this connection
|
||||||
preparedStatements map[string]*pgconn.StatementDescription
|
preparedStatements map[string]*pgconn.StatementDescription
|
||||||
|
failedDescribeStatement string
|
||||||
statementCache stmtcache.Cache
|
statementCache stmtcache.Cache
|
||||||
descriptionCache stmtcache.Cache
|
descriptionCache stmtcache.Cache
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Connect establishes a connection with a PostgreSQL server with a connection string. See
|
// Connect establishes a connection with a PostgreSQL server with a connection string. See
|
||||||
// pgconn.Connect for details.
|
// [pgconn.Connect] for details.
|
||||||
func Connect(ctx context.Context, connString string) (*Conn, error) {
|
func Connect(ctx context.Context, connString string) (*Conn, error) {
|
||||||
connConfig, err := ParseConfig(connString)
|
connConfig, err := ParseConfig(connString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -140,7 +141,7 @@ func Connect(ctx context.Context, connString string) (*Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ConnectWithOptions behaves exactly like Connect with the addition of options. At the present options is only used to
|
// ConnectWithOptions behaves exactly like Connect with the addition of options. At the present options is only used to
|
||||||
// provide a GetSSLPassword function.
|
// provide a [pgconn.GetSSLPasswordFunc] function.
|
||||||
func ConnectWithOptions(ctx context.Context, connString string, options ParseConfigOptions) (*Conn, error) {
|
func ConnectWithOptions(ctx context.Context, connString string, options ParseConfigOptions) (*Conn, error) {
|
||||||
connConfig, err := ParseConfigWithOptions(connString, options)
|
connConfig, err := ParseConfigWithOptions(connString, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -150,7 +151,7 @@ func ConnectWithOptions(ctx context.Context, connString string, options ParseCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ConnectConfig establishes a connection with a PostgreSQL server with a configuration struct.
|
// ConnectConfig establishes a connection with a PostgreSQL server with a configuration struct.
|
||||||
// connConfig must have been created by ParseConfig.
|
// connConfig must have been created by [ParseConfig].
|
||||||
func ConnectConfig(ctx context.Context, connConfig *ConnConfig) (*Conn, error) {
|
func ConnectConfig(ctx context.Context, connConfig *ConnConfig) (*Conn, error) {
|
||||||
// In general this improves safety. In particular avoid the config.Config.OnNotification mutation from affecting other
|
// In general this improves safety. In particular avoid the config.Config.OnNotification mutation from affecting other
|
||||||
// connections with the same config. See https://github.com/jackc/pgx/issues/618.
|
// connections with the same config. See https://github.com/jackc/pgx/issues/618.
|
||||||
@@ -159,8 +160,8 @@ func ConnectConfig(ctx context.Context, connConfig *ConnConfig) (*Conn, error) {
|
|||||||
return connect(ctx, connConfig)
|
return connect(ctx, connConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseConfigWithOptions behaves exactly as ParseConfig does with the addition of options. At the present options is
|
// ParseConfigWithOptions behaves exactly as [ParseConfig] does with the addition of options. At the present options is
|
||||||
// only used to provide a GetSSLPassword function.
|
// only used to provide a [pgconn.GetSSLPasswordFunc] function.
|
||||||
func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*ConnConfig, error) {
|
func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*ConnConfig, error) {
|
||||||
config, err := pgconn.ParseConfigWithOptions(connString, options.ParseConfigOptions)
|
config, err := pgconn.ParseConfigWithOptions(connString, options.ParseConfigOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -202,7 +203,9 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
|||||||
case "simple_protocol":
|
case "simple_protocol":
|
||||||
defaultQueryExecMode = QueryExecModeSimpleProtocol
|
defaultQueryExecMode = QueryExecModeSimpleProtocol
|
||||||
default:
|
default:
|
||||||
return nil, pgconn.NewParseConfigError(connString, "invalid default_query_exec_mode", err)
|
return nil, pgconn.NewParseConfigError(
|
||||||
|
connString, "invalid default_query_exec_mode", fmt.Errorf("unknown value %q", s),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,8 +308,8 @@ func (c *Conn) Close(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare creates a prepared statement with name and sql. sql can contain placeholders for bound parameters. These
|
// Prepare creates a prepared statement with name and sql. sql can contain placeholders for bound parameters. These
|
||||||
// placeholders are referenced positionally as $1, $2, etc. name can be used instead of sql with Query, QueryRow, and
|
// placeholders are referenced positionally as $1, $2, etc. name can be used instead of sql with [Conn.Query],
|
||||||
// Exec to execute the statement. It can also be used with Batch.Queue.
|
// [Conn.QueryRow], and [Conn.Exec] to execute the statement. It can also be used with [Batch.Queue].
|
||||||
//
|
//
|
||||||
// The underlying PostgreSQL identifier for the prepared statement will be name if name != sql or a digest of sql if
|
// The underlying PostgreSQL identifier for the prepared statement will be name if name != sql or a digest of sql if
|
||||||
// name == sql.
|
// name == sql.
|
||||||
@@ -314,6 +317,14 @@ func (c *Conn) Close(ctx context.Context) error {
|
|||||||
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same name and sql arguments. This
|
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same name and sql arguments. This
|
||||||
// allows a code path to Prepare and Query/Exec without concern for if the statement has already been prepared.
|
// allows a code path to Prepare and Query/Exec without concern for if the statement has already been prepared.
|
||||||
func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error) {
|
func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error) {
|
||||||
|
if c.failedDescribeStatement != "" {
|
||||||
|
err = c.Deallocate(ctx, c.failedDescribeStatement)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deallocate previously failed statement %q: %w", c.failedDescribeStatement, err)
|
||||||
|
}
|
||||||
|
c.failedDescribeStatement = ""
|
||||||
|
}
|
||||||
|
|
||||||
if c.prepareTracer != nil {
|
if c.prepareTracer != nil {
|
||||||
ctx = c.prepareTracer.TracePrepareStart(ctx, c, TracePrepareStartData{Name: name, SQL: sql})
|
ctx = c.prepareTracer.TracePrepareStart(ctx, c, TracePrepareStartData{Name: name, SQL: sql})
|
||||||
}
|
}
|
||||||
@@ -346,6 +357,10 @@ func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.Statem
|
|||||||
|
|
||||||
sd, err = c.pgConn.Prepare(ctx, psName, sql, nil)
|
sd, err = c.pgConn.Prepare(ctx, psName, sql, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
var pErr *pgconn.PrepareError
|
||||||
|
if errors.As(err, &pErr) {
|
||||||
|
c.failedDescribeStatement = psKey
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,6 +517,18 @@ optionLoop:
|
|||||||
mode = QueryExecModeSimpleProtocol
|
mode = QueryExecModeSimpleProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
if sc := c.statementCache; sc != nil {
|
||||||
|
sc.Invalidate(sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc := c.descriptionCache; sc != nil {
|
||||||
|
sc.Invalidate(sql)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if sd, ok := c.preparedStatements[sql]; ok {
|
if sd, ok := c.preparedStatements[sql]; ok {
|
||||||
return c.execPrepared(ctx, sd, arguments)
|
return c.execPrepared(ctx, sd, arguments)
|
||||||
}
|
}
|
||||||
@@ -583,7 +610,7 @@ func (c *Conn) execPrepared(ctx context.Context, sd *pgconn.StatementDescription
|
|||||||
return pgconn.CommandTag{}, err
|
return pgconn.CommandTag{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := c.pgConn.ExecPrepared(ctx, sd.Name, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats).Read()
|
result := c.pgConn.ExecStatement(ctx, sd, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats).Read()
|
||||||
c.eqb.reset() // Allow c.eqb internal memory to be GC'ed as soon as possible.
|
c.eqb.reset() // Allow c.eqb internal memory to be GC'ed as soon as possible.
|
||||||
return result.CommandTag, result.Err
|
return result.CommandTag, result.Err
|
||||||
}
|
}
|
||||||
@@ -817,7 +844,7 @@ optionLoop:
|
|||||||
if !explicitPreparedStatement && mode == QueryExecModeCacheDescribe {
|
if !explicitPreparedStatement && mode == QueryExecModeCacheDescribe {
|
||||||
rows.resultReader = c.pgConn.ExecParams(ctx, sql, c.eqb.ParamValues, sd.ParamOIDs, c.eqb.ParamFormats, resultFormats)
|
rows.resultReader = c.pgConn.ExecParams(ctx, sql, c.eqb.ParamValues, sd.ParamOIDs, c.eqb.ParamFormats, resultFormats)
|
||||||
} else {
|
} else {
|
||||||
rows.resultReader = c.pgConn.ExecPrepared(ctx, sd.Name, c.eqb.ParamValues, c.eqb.ParamFormats, resultFormats)
|
rows.resultReader = c.pgConn.ExecStatement(ctx, sd, c.eqb.ParamValues, c.eqb.ParamFormats, resultFormats)
|
||||||
}
|
}
|
||||||
} else if mode == QueryExecModeExec {
|
} else if mode == QueryExecModeExec {
|
||||||
err := c.eqb.Build(c.typeMap, nil, args)
|
err := c.eqb.Build(c.typeMap, nil, args)
|
||||||
@@ -906,12 +933,16 @@ func (c *Conn) QueryRow(ctx context.Context, sql string, args ...any) Row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendBatch sends all queued queries to the server at once. All queries are run in an implicit transaction unless
|
// SendBatch sends all queued queries to the server at once. All queries are run in an implicit transaction unless
|
||||||
// explicit transaction control statements are executed. The returned BatchResults must be closed before the connection
|
// explicit transaction control statements are executed. The returned [BatchResults] must be closed before the connection
|
||||||
// is used again.
|
// is used again.
|
||||||
//
|
//
|
||||||
// Depending on the QueryExecMode, all queries may be prepared before any are executed. This means that creating a table
|
// Depending on the QueryExecMode, all queries may be prepared before any are executed. This means that creating a table
|
||||||
// and using it in a subsequent query in the same batch can fail.
|
// and using it in a subsequent query in the same batch can fail.
|
||||||
func (c *Conn) SendBatch(ctx context.Context, b *Batch) (br BatchResults) {
|
func (c *Conn) SendBatch(ctx context.Context, b *Batch) (br BatchResults) {
|
||||||
|
if len(b.QueuedQueries) == 0 {
|
||||||
|
return &emptyBatchResults{conn: c}
|
||||||
|
}
|
||||||
|
|
||||||
if c.batchTracer != nil {
|
if c.batchTracer != nil {
|
||||||
ctx = c.batchTracer.TraceBatchStart(ctx, c, TraceBatchStartData{Batch: b})
|
ctx = c.batchTracer.TraceBatchStart(ctx, c, TraceBatchStartData{Batch: b})
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -1163,7 +1194,7 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d
|
|||||||
for _, sd := range distinctNewQueries {
|
for _, sd := range distinctNewQueries {
|
||||||
results, err := pipeline.GetResults()
|
results, err := pipeline.GetResults()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return newErrPreprocessingBatch("prepare", sd.SQL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resultSD, ok := results.(*pgconn.StatementDescription)
|
resultSD, ok := results.(*pgconn.StatementDescription)
|
||||||
@@ -1197,15 +1228,18 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d
|
|||||||
for _, bi := range b.QueuedQueries {
|
for _, bi := range b.QueuedQueries {
|
||||||
err := c.eqb.Build(c.typeMap, bi.sd, bi.Arguments)
|
err := c.eqb.Build(c.typeMap, bi.sd, bi.Arguments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we wrap the error so we the user can understand which query failed inside the batch
|
err = newErrPreprocessingBatch("build", bi.SQL, err)
|
||||||
err = fmt.Errorf("error building query %s: %w", bi.SQL, err)
|
|
||||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true}
|
return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
if bi.sd.Name == "" {
|
if bi.sd.Name == "" {
|
||||||
pipeline.SendQueryParams(bi.sd.SQL, c.eqb.ParamValues, bi.sd.ParamOIDs, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
pipeline.SendQueryParams(bi.sd.SQL, c.eqb.ParamValues, bi.sd.ParamOIDs, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
||||||
} else {
|
} else {
|
||||||
pipeline.SendQueryPrepared(bi.sd.Name, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
// Copy ResultFormats because SendQueryStatement stores the slice for later use, and eqb.Build reuses the
|
||||||
|
// backing array on the next iteration.
|
||||||
|
resultFormats := make([]int16, len(c.eqb.ResultFormats))
|
||||||
|
copy(resultFormats, c.eqb.ResultFormats)
|
||||||
|
pipeline.SendQueryStatement(bi.sd, c.eqb.ParamValues, c.eqb.ParamFormats, resultFormats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1243,7 +1277,7 @@ func (c *Conn) sanitizeForSimpleQuery(sql string, args ...any) (string, error) {
|
|||||||
return sanitize.SanitizeSQL(sql, valueArgs...)
|
return sanitize.SanitizeSQL(sql, valueArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadType inspects the database for typeName and produces a pgtype.Type suitable for registration. typeName must be
|
// LoadType inspects the database for typeName and produces a [pgtype.Type] suitable for registration. typeName must be
|
||||||
// the name of a type where the underlying type(s) is already understood by pgx. It is for derived types. In particular,
|
// the name of a type where the underlying type(s) is already understood by pgx. It is for derived types. In particular,
|
||||||
// typeName must be one of the following:
|
// typeName must be one of the following:
|
||||||
// - An array type name of a type that is already registered. e.g. "_foo" when "foo" is registered.
|
// - An array type name of a type that is already registered. e.g. "_foo" when "foo" is registered.
|
||||||
|
|||||||
+8
-8
@@ -10,8 +10,8 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CopyFromRows returns a CopyFromSource interface over the provided rows slice
|
// CopyFromRows returns a [CopyFromSource] interface over the provided rows slice
|
||||||
// making it usable by *Conn.CopyFrom.
|
// making it usable by [Conn.CopyFrom].
|
||||||
func CopyFromRows(rows [][]any) CopyFromSource {
|
func CopyFromRows(rows [][]any) CopyFromSource {
|
||||||
return ©FromRows{rows: rows, idx: -1}
|
return ©FromRows{rows: rows, idx: -1}
|
||||||
}
|
}
|
||||||
@@ -34,8 +34,8 @@ func (ctr *copyFromRows) Err() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyFromSlice returns a CopyFromSource interface over a dynamic func
|
// CopyFromSlice returns a [CopyFromSource] interface over a dynamic func
|
||||||
// making it usable by *Conn.CopyFrom.
|
// making it usable by [Conn.CopyFrom].
|
||||||
func CopyFromSlice(length int, next func(int) ([]any, error)) CopyFromSource {
|
func CopyFromSlice(length int, next func(int) ([]any, error)) CopyFromSource {
|
||||||
return ©FromSlice{next: next, idx: -1, len: length}
|
return ©FromSlice{next: next, idx: -1, len: length}
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ func (cts *copyFromSlice) Err() error {
|
|||||||
return cts.err
|
return cts.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyFromFunc returns a CopyFromSource interface that relies on nxtf for values.
|
// CopyFromFunc returns a [CopyFromSource] interface that relies on nxtf for values.
|
||||||
// nxtf returns rows until it either signals an 'end of data' by returning row=nil and err=nil,
|
// nxtf returns rows until it either signals an 'end of data' by returning row=nil and err=nil,
|
||||||
// or it returns an error. If nxtf returns an error, the copy is aborted.
|
// or it returns an error. If nxtf returns an error, the copy is aborted.
|
||||||
func CopyFromFunc(nxtf func() (row []any, err error)) CopyFromSource {
|
func CopyFromFunc(nxtf func() (row []any, err error)) CopyFromSource {
|
||||||
@@ -91,7 +91,7 @@ func (g *copyFromFunc) Err() error {
|
|||||||
return g.err
|
return g.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyFromSource is the interface used by *Conn.CopyFrom as the source for copy data.
|
// CopyFromSource is the interface used by [Conn.CopyFrom] as the source for copy data.
|
||||||
type CopyFromSource interface {
|
type CopyFromSource interface {
|
||||||
// Next returns true if there is another row and makes the next row data
|
// Next returns true if there is another row and makes the next row data
|
||||||
// available to Values(). When there are no more rows available or an error
|
// available to Values(). When there are no more rows available or an error
|
||||||
@@ -260,8 +260,8 @@ func (ct *copyFrom) buildCopyBuf(buf []byte, sd *pgconn.StatementDescription) (b
|
|||||||
// CopyFrom requires all values use the binary format. A pgtype.Type that supports the binary format must be registered
|
// CopyFrom requires all values use the binary format. A pgtype.Type that supports the binary format must be registered
|
||||||
// for the type of each column. Almost all types implemented by pgx support the binary format.
|
// for the type of each column. Almost all types implemented by pgx support the binary format.
|
||||||
//
|
//
|
||||||
// Even though enum types appear to be strings they still must be registered to use with CopyFrom. This can be done with
|
// Even though enum types appear to be strings they still must be registered to use with [Conn.CopyFrom]. This can be done with
|
||||||
// Conn.LoadType and pgtype.Map.RegisterType.
|
// [Conn.LoadType] and [pgtype.Map.RegisterType].
|
||||||
func (c *Conn) CopyFrom(ctx context.Context, tableName Identifier, columnNames []string, rowSrc CopyFromSource) (int64, error) {
|
func (c *Conn) CopyFrom(ctx context.Context, tableName Identifier, columnNames []string, rowSrc CopyFromSource) (int64, error) {
|
||||||
ct := ©From{
|
ct := ©From{
|
||||||
conn: c,
|
conn: c,
|
||||||
|
|||||||
+3
-3
@@ -24,7 +24,7 @@ func buildLoadDerivedTypesSQL(pgVersion int64, typeNames []string) string {
|
|||||||
// This should not occur; this will not return any types
|
// This should not occur; this will not return any types
|
||||||
typeNamesClause = "= ''"
|
typeNamesClause = "= ''"
|
||||||
} else {
|
} else {
|
||||||
typeNamesClause = "= ANY($1)"
|
typeNamesClause = "= ANY($1::text[])"
|
||||||
}
|
}
|
||||||
parts := make([]string, 0, 10)
|
parts := make([]string, 0, 10)
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ func (c *Conn) LoadTypes(ctx context.Context, typeNames []string) ([]*pgtype.Typ
|
|||||||
// the SQL not support recent structures such as multirange
|
// the SQL not support recent structures such as multirange
|
||||||
serverVersion, _ := serverVersion(c)
|
serverVersion, _ := serverVersion(c)
|
||||||
sql := buildLoadDerivedTypesSQL(serverVersion, typeNames)
|
sql := buildLoadDerivedTypesSQL(serverVersion, typeNames)
|
||||||
rows, err := c.Query(ctx, sql, QueryExecModeSimpleProtocol, typeNames)
|
rows, err := c.Query(ctx, sql, QueryResultFormats{TextFormatCode}, typeNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("While generating load types query: %w", err)
|
return nil, fmt.Errorf("While generating load types query: %w", err)
|
||||||
}
|
}
|
||||||
@@ -227,7 +227,7 @@ func (c *Conn) LoadTypes(ctx context.Context, typeNames []string) ([]*pgtype.Typ
|
|||||||
return nil, fmt.Errorf("Unknown typtype %q was found while registering %q", ti.Typtype, ti.TypeName)
|
return nil, fmt.Errorf("Unknown typtype %q was found while registering %q", ti.Typtype, ti.TypeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the type_ is imposible to be null
|
// the type_ is impossible to be null
|
||||||
m.RegisterType(type_)
|
m.RegisterType(type_)
|
||||||
if ti.NspName != "" {
|
if ti.NspName != "" {
|
||||||
nspType := &pgtype.Type{Name: ti.NspName + "." + type_.Name, OID: type_.OID, Codec: type_.Codec}
|
nspType := &pgtype.Type{Name: ti.NspName + "." + type_.Name, OID: type_.OID, Codec: type_.Codec}
|
||||||
|
|||||||
+36
-31
@@ -1,8 +1,8 @@
|
|||||||
// Package pgx is a PostgreSQL database driver.
|
// Package pgx is a PostgreSQL database driver.
|
||||||
/*
|
/*
|
||||||
pgx provides a native PostgreSQL driver and can act as a database/sql driver. The native PostgreSQL interface is similar
|
pgx provides a native PostgreSQL driver and can act as a [database/sql/driver]. The native PostgreSQL interface is similar
|
||||||
to the database/sql interface while providing better speed and access to PostgreSQL specific features. Use
|
to the [database/sql] interface while providing better speed and access to PostgreSQL specific features. Use
|
||||||
github.com/jackc/pgx/v5/stdlib to use pgx as a database/sql compatible driver. See that package's documentation for
|
[github.com/jackc/pgx/v5/stdlib] to use pgx as a database/sql compatible driver. See that package's documentation for
|
||||||
details.
|
details.
|
||||||
|
|
||||||
Establishing a Connection
|
Establishing a Connection
|
||||||
@@ -19,15 +19,15 @@ string.
|
|||||||
Connection Pool
|
Connection Pool
|
||||||
|
|
||||||
[*pgx.Conn] represents a single connection to the database and is not concurrency safe. Use package
|
[*pgx.Conn] represents a single connection to the database and is not concurrency safe. Use package
|
||||||
github.com/jackc/pgx/v5/pgxpool for a concurrency safe connection pool.
|
[github.com/jackc/pgx/v5/pgxpool] for a concurrency safe connection pool.
|
||||||
|
|
||||||
Query Interface
|
Query Interface
|
||||||
|
|
||||||
pgx implements Query in the familiar database/sql style. However, pgx provides generic functions such as CollectRows and
|
pgx implements [Conn.Query] in the familiar database/sql style. However, pgx provides generic functions such as [CollectRows] and
|
||||||
ForEachRow that are a simpler and safer way of processing rows than manually calling defer rows.Close(), rows.Next(),
|
[ForEachRow] that are a simpler and safer way of processing rows than manually calling defer [Rows.Close], [Rows.Next],
|
||||||
rows.Scan, and rows.Err().
|
[Rows.Scan], and [Rows.Err].
|
||||||
|
|
||||||
CollectRows can be used collect all returned rows into a slice.
|
[CollectRows] can be used collect all returned rows into a slice.
|
||||||
|
|
||||||
rows, _ := conn.Query(context.Background(), "select generate_series(1,$1)", 5)
|
rows, _ := conn.Query(context.Background(), "select generate_series(1,$1)", 5)
|
||||||
numbers, err := pgx.CollectRows(rows, pgx.RowTo[int32])
|
numbers, err := pgx.CollectRows(rows, pgx.RowTo[int32])
|
||||||
@@ -36,7 +36,7 @@ CollectRows can be used collect all returned rows into a slice.
|
|||||||
}
|
}
|
||||||
// numbers => [1 2 3 4 5]
|
// numbers => [1 2 3 4 5]
|
||||||
|
|
||||||
ForEachRow can be used to execute a callback function for every row. This is often easier than iterating over rows
|
[ForEachRow] can be used to execute a callback function for every row. This is often easier than iterating over rows
|
||||||
directly.
|
directly.
|
||||||
|
|
||||||
var sum, n int32
|
var sum, n int32
|
||||||
@@ -49,7 +49,7 @@ directly.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pgx also implements QueryRow in the same style as database/sql.
|
pgx also implements [Conn.QueryRow] in the same style as database/sql.
|
||||||
|
|
||||||
var name string
|
var name string
|
||||||
var weight int64
|
var weight int64
|
||||||
@@ -58,7 +58,7 @@ pgx also implements QueryRow in the same style as database/sql.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
Use Exec to execute a query that does not return a result set.
|
Use [Conn.Exec] to execute a query that does not return a result set.
|
||||||
|
|
||||||
commandTag, err := conn.Exec(context.Background(), "delete from widgets where id=$1", 42)
|
commandTag, err := conn.Exec(context.Background(), "delete from widgets where id=$1", 42)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -70,13 +70,13 @@ Use Exec to execute a query that does not return a result set.
|
|||||||
|
|
||||||
PostgreSQL Data Types
|
PostgreSQL Data Types
|
||||||
|
|
||||||
pgx uses the pgtype package to converting Go values to and from PostgreSQL values. It supports many PostgreSQL types
|
pgx uses the [pgtype] package to converting Go values to and from PostgreSQL values. It supports many PostgreSQL types
|
||||||
directly and is customizable and extendable. User defined data types such as enums, domains, and composite types may
|
directly and is customizable and extendable. User defined data types such as enums, domains, and composite types may
|
||||||
require type registration. See that package's documentation for details.
|
require type registration. See that package's documentation for details.
|
||||||
|
|
||||||
Transactions
|
Transactions
|
||||||
|
|
||||||
Transactions are started by calling Begin.
|
Transactions are started by calling [Conn.Begin].
|
||||||
|
|
||||||
tx, err := conn.Begin(context.Background())
|
tx, err := conn.Begin(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -96,13 +96,13 @@ Transactions are started by calling Begin.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
The Tx returned from Begin also implements the Begin method. This can be used to implement pseudo nested transactions.
|
The [Tx] returned from [Conn.Begin] also implements the [Tx.Begin] method. This can be used to implement pseudo nested transactions.
|
||||||
These are internally implemented with savepoints.
|
These are internally implemented with savepoints.
|
||||||
|
|
||||||
Use BeginTx to control the transaction mode. BeginTx also can be used to ensure a new transaction is created instead of
|
Use [Conn.BeginTx] to control the transaction mode. [Conn.BeginTx] also can be used to ensure a new transaction is created instead of
|
||||||
a pseudo nested transaction.
|
a pseudo nested transaction.
|
||||||
|
|
||||||
BeginFunc and BeginTxFunc are functions that begin a transaction, execute a function, and commit or rollback the
|
[BeginFunc] and [BeginTxFunc] are functions that begin a transaction, execute a function, and commit or rollback the
|
||||||
transaction depending on the return value of the function. These can be simpler and less error prone to use.
|
transaction depending on the return value of the function. These can be simpler and less error prone to use.
|
||||||
|
|
||||||
err = pgx.BeginFunc(context.Background(), conn, func(tx pgx.Tx) error {
|
err = pgx.BeginFunc(context.Background(), conn, func(tx pgx.Tx) error {
|
||||||
@@ -115,16 +115,16 @@ transaction depending on the return value of the function. These can be simpler
|
|||||||
|
|
||||||
Prepared Statements
|
Prepared Statements
|
||||||
|
|
||||||
Prepared statements can be manually created with the Prepare method. However, this is rarely necessary because pgx
|
Prepared statements can be manually created with the [Conn.Prepare] method. However, this is rarely necessary because pgx
|
||||||
includes an automatic statement cache by default. Queries run through the normal Query, QueryRow, and Exec functions are
|
includes an automatic statement cache by default. Queries run through the normal [Conn.Query], [Conn.QueryRow], and [Conn.Exec]
|
||||||
automatically prepared on first execution and the prepared statement is reused on subsequent executions. See ParseConfig
|
functions are automatically prepared on first execution and the prepared statement is reused on subsequent executions.
|
||||||
for information on how to customize or disable the statement cache.
|
See [ParseConfig] for information on how to customize or disable the statement cache.
|
||||||
|
|
||||||
Copy Protocol
|
Copy Protocol
|
||||||
|
|
||||||
Use CopyFrom to efficiently insert multiple rows at a time using the PostgreSQL copy protocol. CopyFrom accepts a
|
Use [Conn.CopyFrom] to efficiently insert multiple rows at a time using the PostgreSQL copy protocol. [Conn.CopyFrom] accepts a
|
||||||
CopyFromSource interface. If the data is already in a [][]any use CopyFromRows to wrap it in a CopyFromSource interface.
|
[CopyFromSource] interface. If the data is already in a [][]any use [CopyFromRows] to wrap it in a [CopyFromSource] interface.
|
||||||
Or implement CopyFromSource to avoid buffering the entire data set in memory.
|
Or implement [CopyFromSource] to avoid buffering the entire data set in memory.
|
||||||
|
|
||||||
rows := [][]any{
|
rows := [][]any{
|
||||||
{"John", "Smith", int32(36)},
|
{"John", "Smith", int32(36)},
|
||||||
@@ -138,7 +138,7 @@ Or implement CopyFromSource to avoid buffering the entire data set in memory.
|
|||||||
pgx.CopyFromRows(rows),
|
pgx.CopyFromRows(rows),
|
||||||
)
|
)
|
||||||
|
|
||||||
When you already have a typed array using CopyFromSlice can be more convenient.
|
When you already have a typed array using [CopyFromSlice] can be more convenient.
|
||||||
|
|
||||||
rows := []User{
|
rows := []User{
|
||||||
{"John", "Smith", 36},
|
{"John", "Smith", 36},
|
||||||
@@ -158,7 +158,7 @@ CopyFrom can be faster than an insert with as few as 5 rows.
|
|||||||
|
|
||||||
Listen and Notify
|
Listen and Notify
|
||||||
|
|
||||||
pgx can listen to the PostgreSQL notification system with the `Conn.WaitForNotification` method. It blocks until a
|
pgx can listen to the PostgreSQL notification system with the [Conn.WaitForNotification] method. It blocks until a
|
||||||
notification is received or the context is canceled.
|
notification is received or the context is canceled.
|
||||||
|
|
||||||
_, err := conn.Exec(context.Background(), "listen channelname")
|
_, err := conn.Exec(context.Background(), "listen channelname")
|
||||||
@@ -175,20 +175,25 @@ notification is received or the context is canceled.
|
|||||||
|
|
||||||
Tracing and Logging
|
Tracing and Logging
|
||||||
|
|
||||||
pgx supports tracing by setting ConnConfig.Tracer. To combine several tracers you can use the multitracer.Tracer.
|
pgx supports tracing by setting [ConnConfig.Tracer]. To combine several tracers you can use the [github.com/jackc/pgx/v5/multitracer.Tracer].
|
||||||
|
|
||||||
In addition, the tracelog package provides the TraceLog type which lets a traditional logger act as a Tracer.
|
In addition, the [github.com/jackc/pgx/v5/tracelog] package provides the [github.com/jackc/pgx/v5/tracelog.TraceLog] type which lets a
|
||||||
|
traditional logger act as a [QueryTracer].
|
||||||
|
|
||||||
For debug tracing of the actual PostgreSQL wire protocol messages see github.com/jackc/pgx/v5/pgproto3.
|
For debug tracing of the actual PostgreSQL wire protocol messages see [github.com/jackc/pgx/v5/pgproto3].
|
||||||
|
|
||||||
Lower Level PostgreSQL Functionality
|
Lower Level PostgreSQL Functionality
|
||||||
|
|
||||||
github.com/jackc/pgx/v5/pgconn contains a lower level PostgreSQL driver roughly at the level of libpq. pgx.Conn is
|
[github.com/jackc/pgx/v5/pgconn] contains a lower level PostgreSQL driver roughly at the level of libpq. [Conn] is
|
||||||
implemented on top of pgconn. The Conn.PgConn() method can be used to access this lower layer.
|
implemented on top of [pgconn.PgConn]. The [Conn.PgConn] method can be used to access this lower layer.
|
||||||
|
|
||||||
PgBouncer
|
PgBouncer
|
||||||
|
|
||||||
By default pgx automatically uses prepared statements. Prepared statements are incompatible with PgBouncer. This can be
|
By default pgx automatically uses prepared statements. Prepared statements are incompatible with PgBouncer. This can be
|
||||||
disabled by setting a different QueryExecMode in ConnConfig.DefaultQueryExecMode.
|
disabled by setting a different [QueryExecMode] in [ConnConfig.DefaultQueryExecMode].
|
||||||
*/
|
*/
|
||||||
package pgx
|
package pgx
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/jackc/pgx/v5/pgconn" // Just for allowing godoc to resolve "pgconn"
|
||||||
|
)
|
||||||
|
|||||||
+24
-16
@@ -4,7 +4,10 @@
|
|||||||
// an allocation is purposely not documented. https://github.com/golang/go/issues/16323
|
// an allocation is purposely not documented. https://github.com/golang/go/issues/16323
|
||||||
package iobufpool
|
package iobufpool
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"math/bits"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
const minPoolExpOf2 = 8
|
const minPoolExpOf2 = 8
|
||||||
|
|
||||||
@@ -37,15 +40,14 @@ func Get(size int) *[]byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getPoolIdx(size int) int {
|
func getPoolIdx(size int) int {
|
||||||
size--
|
if size < 2 {
|
||||||
size >>= minPoolExpOf2
|
return 0
|
||||||
i := 0
|
|
||||||
for size > 0 {
|
|
||||||
size >>= 1
|
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
|
idx := bits.Len(uint(size-1)) - minPoolExpOf2
|
||||||
return i
|
if idx < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return idx
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put returns buf to the pool.
|
// Put returns buf to the pool.
|
||||||
@@ -59,12 +61,18 @@ func Put(buf *[]byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func putPoolIdx(size int) int {
|
func putPoolIdx(size int) int {
|
||||||
minPoolSize := 1 << minPoolExpOf2
|
// Only exact power-of-2 sizes match pool buckets
|
||||||
for i := range pools {
|
if size&(size-1) != 0 {
|
||||||
if size == minPoolSize<<i {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate log2(size) using trailing zeros count
|
||||||
|
exp := bits.TrailingZeros(uint(size))
|
||||||
|
idx := exp - minPoolExpOf2
|
||||||
|
|
||||||
|
if idx < 0 || idx >= len(pools) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
|||||||
+7
-15
@@ -1,26 +1,18 @@
|
|||||||
package pgio
|
package pgio
|
||||||
|
|
||||||
import "encoding/binary"
|
|
||||||
|
|
||||||
func AppendUint16(buf []byte, n uint16) []byte {
|
func AppendUint16(buf []byte, n uint16) []byte {
|
||||||
wp := len(buf)
|
return append(buf, byte(n>>8), byte(n))
|
||||||
buf = append(buf, 0, 0)
|
|
||||||
binary.BigEndian.PutUint16(buf[wp:], n)
|
|
||||||
return buf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppendUint32(buf []byte, n uint32) []byte {
|
func AppendUint32(buf []byte, n uint32) []byte {
|
||||||
wp := len(buf)
|
return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
||||||
buf = append(buf, 0, 0, 0, 0)
|
|
||||||
binary.BigEndian.PutUint32(buf[wp:], n)
|
|
||||||
return buf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppendUint64(buf []byte, n uint64) []byte {
|
func AppendUint64(buf []byte, n uint64) []byte {
|
||||||
wp := len(buf)
|
return append(buf,
|
||||||
buf = append(buf, 0, 0, 0, 0, 0, 0, 0, 0)
|
byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32),
|
||||||
binary.BigEndian.PutUint64(buf[wp:], n)
|
byte(n>>24), byte(n>>16), byte(n>>8), byte(n),
|
||||||
return buf
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppendInt16(buf []byte, n int16) []byte {
|
func AppendInt16(buf []byte, n int16) []byte {
|
||||||
@@ -36,5 +28,5 @@ func AppendInt64(buf []byte, n int64) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SetInt32(buf []byte, n int32) {
|
func SetInt32(buf []byte, n int32) {
|
||||||
binary.BigEndian.PutUint32(buf, uint32(n))
|
*(*[4]byte)(buf) = [4]byte{byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n)}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
Vendored
+1
-1
@@ -42,7 +42,7 @@ for i in "${!commits[@]}"; do
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Sanitized commmit message
|
# Sanitized commit message
|
||||||
commit_message=$(git log -1 --pretty=format:"%s" | tr -c '[:alnum:]-_' '_')
|
commit_message=$(git log -1 --pretty=format:"%s" | tr -c '[:alnum:]-_' '_')
|
||||||
|
|
||||||
# Benchmark data will go there
|
# Benchmark data will go there
|
||||||
+83
-2
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -206,6 +207,7 @@ type sqlLexer struct {
|
|||||||
start int
|
start int
|
||||||
pos int
|
pos int
|
||||||
nested int // multiline comment nesting level.
|
nested int // multiline comment nesting level.
|
||||||
|
dollarTag string // active tag while inside a dollar-quoted string (may be empty for $$).
|
||||||
stateFn stateFn
|
stateFn stateFn
|
||||||
parts []Part
|
parts []Part
|
||||||
}
|
}
|
||||||
@@ -237,6 +239,15 @@ func rawState(l *sqlLexer) stateFn {
|
|||||||
l.start = l.pos
|
l.start = l.pos
|
||||||
return placeholderState
|
return placeholderState
|
||||||
}
|
}
|
||||||
|
// PostgreSQL dollar-quoted string: $[tag]$...$[tag]$. The $ was
|
||||||
|
// just consumed; try to match the rest of the opening tag.
|
||||||
|
// Without this, placeholders embedded inside dollar-quoted
|
||||||
|
// literals would be incorrectly substituted.
|
||||||
|
if tagLen, ok := scanDollarQuoteTag(l.src[l.pos:]); ok {
|
||||||
|
l.dollarTag = l.src[l.pos : l.pos+tagLen]
|
||||||
|
l.pos += tagLen + 1 // advance past tag and closing '$'
|
||||||
|
return dollarQuoteState
|
||||||
|
}
|
||||||
case '-':
|
case '-':
|
||||||
nextRune, width := utf8.DecodeRuneInString(l.src[l.pos:])
|
nextRune, width := utf8.DecodeRuneInString(l.src[l.pos:])
|
||||||
if nextRune == '-' {
|
if nextRune == '-' {
|
||||||
@@ -319,8 +330,16 @@ func placeholderState(l *sqlLexer) stateFn {
|
|||||||
l.pos += width
|
l.pos += width
|
||||||
|
|
||||||
if '0' <= r && r <= '9' {
|
if '0' <= r && r <= '9' {
|
||||||
num *= 10
|
// Clamp rather than silently wrap on pathological input like
|
||||||
num += int(r - '0')
|
// "$92233720368547758070" which would otherwise overflow int and
|
||||||
|
// could land on a valid args index. Any value above MaxInt32 far
|
||||||
|
// exceeds any plausible args length, so Sanitize will correctly
|
||||||
|
// return "insufficient arguments".
|
||||||
|
if num > (math.MaxInt32-9)/10 {
|
||||||
|
num = math.MaxInt32
|
||||||
|
} else {
|
||||||
|
num = num*10 + int(r-'0')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
l.parts = append(l.parts, num)
|
l.parts = append(l.parts, num)
|
||||||
l.pos -= width
|
l.pos -= width
|
||||||
@@ -330,6 +349,68 @@ func placeholderState(l *sqlLexer) stateFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dollarQuoteState consumes the body of a PostgreSQL dollar-quoted string
|
||||||
|
// ($[tag]$...$[tag]$). The opening tag (including its terminating '$') has
|
||||||
|
// already been consumed.
|
||||||
|
func dollarQuoteState(l *sqlLexer) stateFn {
|
||||||
|
closer := "$" + l.dollarTag + "$"
|
||||||
|
idx := strings.Index(l.src[l.pos:], closer)
|
||||||
|
if idx < 0 {
|
||||||
|
// Unterminated — mirror the behavior of other quoted-string states by
|
||||||
|
// consuming the remaining input into the current part and stopping.
|
||||||
|
if len(l.src)-l.start > 0 {
|
||||||
|
l.parts = append(l.parts, l.src[l.start:])
|
||||||
|
l.start = len(l.src)
|
||||||
|
}
|
||||||
|
l.pos = len(l.src)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
l.pos += idx + len(closer)
|
||||||
|
l.dollarTag = ""
|
||||||
|
return rawState
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanDollarQuoteTag checks whether src begins with an optional dollar-quoted
|
||||||
|
// string tag followed by a closing '$'. src must point just past the opening
|
||||||
|
// '$'. Returns the byte length of the tag (zero for an anonymous $$) and
|
||||||
|
// whether a valid tag was found.
|
||||||
|
//
|
||||||
|
// Tag grammar matches the PostgreSQL lexer (scan.l):
|
||||||
|
//
|
||||||
|
// dolq_start: [A-Za-z_\x80-\xff]
|
||||||
|
// dolq_cont: [A-Za-z0-9_\x80-\xff]
|
||||||
|
func scanDollarQuoteTag(src string) (int, bool) {
|
||||||
|
first := true
|
||||||
|
for i := 0; i < len(src); {
|
||||||
|
r, w := utf8.DecodeRuneInString(src[i:])
|
||||||
|
if r == '$' {
|
||||||
|
return i, true
|
||||||
|
}
|
||||||
|
if !isDollarTagRune(r, first) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
i += w
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDollarTagRune(r rune, first bool) bool {
|
||||||
|
switch {
|
||||||
|
case r == '_':
|
||||||
|
return true
|
||||||
|
case 'a' <= r && r <= 'z':
|
||||||
|
return true
|
||||||
|
case 'A' <= r && r <= 'Z':
|
||||||
|
return true
|
||||||
|
case !first && '0' <= r && r <= '9':
|
||||||
|
return true
|
||||||
|
case r >= 0x80 && r != utf8.RuneError:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func escapeStringState(l *sqlLexer) stateFn {
|
func escapeStringState(l *sqlLexer) stateFn {
|
||||||
for {
|
for {
|
||||||
r, width := utf8.DecodeRuneInString(l.src[l.pos:])
|
r, width := utf8.DecodeRuneInString(l.src[l.pos:])
|
||||||
|
|||||||
+110
-34
@@ -1,37 +1,55 @@
|
|||||||
package stmtcache
|
package stmtcache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// lruNode is a typed doubly-linked list node with freelist support.
|
||||||
|
type lruNode struct {
|
||||||
|
sd *pgconn.StatementDescription
|
||||||
|
prev *lruNode
|
||||||
|
next *lruNode
|
||||||
|
}
|
||||||
|
|
||||||
// LRUCache implements Cache with a Least Recently Used (LRU) cache.
|
// LRUCache implements Cache with a Least Recently Used (LRU) cache.
|
||||||
type LRUCache struct {
|
type LRUCache struct {
|
||||||
|
m map[string]*lruNode
|
||||||
|
head *lruNode
|
||||||
|
|
||||||
|
tail *lruNode
|
||||||
|
len int
|
||||||
cap int
|
cap int
|
||||||
m map[string]*list.Element
|
freelist *lruNode
|
||||||
l *list.List
|
|
||||||
invalidStmts []*pgconn.StatementDescription
|
invalidStmts []*pgconn.StatementDescription
|
||||||
|
invalidSet map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLRUCache creates a new LRUCache. cap is the maximum size of the cache.
|
// NewLRUCache creates a new LRUCache. cap is the maximum size of the cache.
|
||||||
func NewLRUCache(cap int) *LRUCache {
|
func NewLRUCache(cap int) *LRUCache {
|
||||||
|
head := &lruNode{}
|
||||||
|
tail := &lruNode{}
|
||||||
|
head.next = tail
|
||||||
|
tail.prev = head
|
||||||
|
|
||||||
return &LRUCache{
|
return &LRUCache{
|
||||||
cap: cap,
|
cap: cap,
|
||||||
m: make(map[string]*list.Element),
|
m: make(map[string]*lruNode, cap),
|
||||||
l: list.New(),
|
head: head,
|
||||||
|
tail: tail,
|
||||||
|
invalidSet: make(map[string]struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the statement description for sql. Returns nil if not found.
|
// Get returns the statement description for sql. Returns nil if not found.
|
||||||
func (c *LRUCache) Get(key string) *pgconn.StatementDescription {
|
func (c *LRUCache) Get(key string) *pgconn.StatementDescription {
|
||||||
if el, ok := c.m[key]; ok {
|
node, ok := c.m[key]
|
||||||
c.l.MoveToFront(el)
|
if !ok {
|
||||||
return el.Value.(*pgconn.StatementDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
c.moveToFront(node)
|
||||||
|
return node.sd
|
||||||
|
}
|
||||||
|
|
||||||
// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache or
|
// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache or
|
||||||
// sd.SQL has been invalidated and HandleInvalidated has not been called yet.
|
// sd.SQL has been invalidated and HandleInvalidated has not been called yet.
|
||||||
@@ -45,39 +63,49 @@ func (c *LRUCache) Put(sd *pgconn.StatementDescription) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The statement may have been invalidated but not yet handled. Do not readd it to the cache.
|
// The statement may have been invalidated but not yet handled. Do not readd it to the cache.
|
||||||
for _, invalidSD := range c.invalidStmts {
|
if _, invalidated := c.invalidSet[sd.SQL]; invalidated {
|
||||||
if invalidSD.SQL == sd.SQL {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if c.l.Len() == c.cap {
|
if c.len == c.cap {
|
||||||
c.invalidateOldest()
|
c.invalidateOldest()
|
||||||
}
|
}
|
||||||
|
|
||||||
el := c.l.PushFront(sd)
|
node := c.allocNode()
|
||||||
c.m[sd.SQL] = el
|
node.sd = sd
|
||||||
|
c.insertAfter(c.head, node)
|
||||||
|
c.m[sd.SQL] = node
|
||||||
|
c.len++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate invalidates statement description identified by sql. Does nothing if not found.
|
// Invalidate invalidates statement description identified by sql. Does nothing if not found.
|
||||||
func (c *LRUCache) Invalidate(sql string) {
|
func (c *LRUCache) Invalidate(sql string) {
|
||||||
if el, ok := c.m[sql]; ok {
|
node, ok := c.m[sql]
|
||||||
delete(c.m, sql)
|
if !ok {
|
||||||
c.invalidStmts = append(c.invalidStmts, el.Value.(*pgconn.StatementDescription))
|
return
|
||||||
c.l.Remove(el)
|
|
||||||
}
|
}
|
||||||
|
delete(c.m, sql)
|
||||||
|
c.invalidStmts = append(c.invalidStmts, node.sd)
|
||||||
|
c.invalidSet[sql] = struct{}{}
|
||||||
|
c.unlink(node)
|
||||||
|
c.len--
|
||||||
|
c.freeNode(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvalidateAll invalidates all statement descriptions.
|
// InvalidateAll invalidates all statement descriptions.
|
||||||
func (c *LRUCache) InvalidateAll() {
|
func (c *LRUCache) InvalidateAll() {
|
||||||
el := c.l.Front()
|
for node := c.head.next; node != c.tail; {
|
||||||
for el != nil {
|
next := node.next
|
||||||
c.invalidStmts = append(c.invalidStmts, el.Value.(*pgconn.StatementDescription))
|
c.invalidStmts = append(c.invalidStmts, node.sd)
|
||||||
el = el.Next()
|
c.invalidSet[node.sd.SQL] = struct{}{}
|
||||||
|
c.freeNode(node)
|
||||||
|
node = next
|
||||||
}
|
}
|
||||||
|
|
||||||
c.m = make(map[string]*list.Element)
|
clear(c.m)
|
||||||
c.l = list.New()
|
c.head.next = c.tail
|
||||||
|
c.tail.prev = c.head
|
||||||
|
c.len = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated.
|
// GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated.
|
||||||
@@ -89,12 +117,13 @@ func (c *LRUCache) GetInvalidated() []*pgconn.StatementDescription {
|
|||||||
// call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were
|
// call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were
|
||||||
// never seen by the call to GetInvalidated.
|
// never seen by the call to GetInvalidated.
|
||||||
func (c *LRUCache) RemoveInvalidated() {
|
func (c *LRUCache) RemoveInvalidated() {
|
||||||
c.invalidStmts = nil
|
c.invalidStmts = c.invalidStmts[:0]
|
||||||
|
clear(c.invalidSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the number of cached prepared statement descriptions.
|
// Len returns the number of cached prepared statement descriptions.
|
||||||
func (c *LRUCache) Len() int {
|
func (c *LRUCache) Len() int {
|
||||||
return c.l.Len()
|
return c.len
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap returns the maximum number of cached prepared statement descriptions.
|
// Cap returns the maximum number of cached prepared statement descriptions.
|
||||||
@@ -103,9 +132,56 @@ func (c *LRUCache) Cap() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *LRUCache) invalidateOldest() {
|
func (c *LRUCache) invalidateOldest() {
|
||||||
oldest := c.l.Back()
|
node := c.tail.prev
|
||||||
sd := oldest.Value.(*pgconn.StatementDescription)
|
if node == c.head {
|
||||||
c.invalidStmts = append(c.invalidStmts, sd)
|
return
|
||||||
delete(c.m, sd.SQL)
|
}
|
||||||
c.l.Remove(oldest)
|
c.invalidStmts = append(c.invalidStmts, node.sd)
|
||||||
|
c.invalidSet[node.sd.SQL] = struct{}{}
|
||||||
|
delete(c.m, node.sd.SQL)
|
||||||
|
c.unlink(node)
|
||||||
|
c.len--
|
||||||
|
c.freeNode(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List operations - sentinel nodes eliminate nil checks
|
||||||
|
|
||||||
|
func (c *LRUCache) insertAfter(at, node *lruNode) {
|
||||||
|
node.prev = at
|
||||||
|
node.next = at.next
|
||||||
|
at.next.prev = node
|
||||||
|
at.next = node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LRUCache) unlink(node *lruNode) {
|
||||||
|
node.prev.next = node.next
|
||||||
|
node.next.prev = node.prev
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LRUCache) moveToFront(node *lruNode) {
|
||||||
|
if node.prev == c.head {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.unlink(node)
|
||||||
|
c.insertAfter(c.head, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node pool operations - reuse evicted nodes to avoid allocations
|
||||||
|
|
||||||
|
func (c *LRUCache) allocNode() *lruNode {
|
||||||
|
if c.freelist != nil {
|
||||||
|
node := c.freelist
|
||||||
|
c.freelist = node.next
|
||||||
|
node.next = nil
|
||||||
|
node.prev = nil
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
return &lruNode{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LRUCache) freeNode(node *lruNode) {
|
||||||
|
node.sd = nil
|
||||||
|
node.prev = nil
|
||||||
|
node.next = c.freelist
|
||||||
|
c.freelist = node
|
||||||
}
|
}
|
||||||
|
|||||||
-77
@@ -1,77 +0,0 @@
|
|||||||
package stmtcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnlimitedCache implements Cache with no capacity limit.
|
|
||||||
type UnlimitedCache struct {
|
|
||||||
m map[string]*pgconn.StatementDescription
|
|
||||||
invalidStmts []*pgconn.StatementDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUnlimitedCache creates a new UnlimitedCache.
|
|
||||||
func NewUnlimitedCache() *UnlimitedCache {
|
|
||||||
return &UnlimitedCache{
|
|
||||||
m: make(map[string]*pgconn.StatementDescription),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the statement description for sql. Returns nil if not found.
|
|
||||||
func (c *UnlimitedCache) Get(sql string) *pgconn.StatementDescription {
|
|
||||||
return c.m[sql]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache.
|
|
||||||
func (c *UnlimitedCache) Put(sd *pgconn.StatementDescription) {
|
|
||||||
if sd.SQL == "" {
|
|
||||||
panic("cannot store statement description with empty SQL")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, present := c.m[sd.SQL]; present {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.m[sd.SQL] = sd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalidate invalidates statement description identified by sql. Does nothing if not found.
|
|
||||||
func (c *UnlimitedCache) Invalidate(sql string) {
|
|
||||||
if sd, ok := c.m[sql]; ok {
|
|
||||||
delete(c.m, sql)
|
|
||||||
c.invalidStmts = append(c.invalidStmts, sd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidateAll invalidates all statement descriptions.
|
|
||||||
func (c *UnlimitedCache) InvalidateAll() {
|
|
||||||
for _, sd := range c.m {
|
|
||||||
c.invalidStmts = append(c.invalidStmts, sd)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.m = make(map[string]*pgconn.StatementDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated.
|
|
||||||
func (c *UnlimitedCache) GetInvalidated() []*pgconn.StatementDescription {
|
|
||||||
return c.invalidStmts
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveInvalidated removes all invalidated statement descriptions. No other calls to Cache must be made between a
|
|
||||||
// call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were
|
|
||||||
// never seen by the call to GetInvalidated.
|
|
||||||
func (c *UnlimitedCache) RemoveInvalidated() {
|
|
||||||
c.invalidStmts = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of cached prepared statement descriptions.
|
|
||||||
func (c *UnlimitedCache) Len() int {
|
|
||||||
return len(c.m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cap returns the maximum number of cached prepared statement descriptions.
|
|
||||||
func (c *UnlimitedCache) Cap() int {
|
|
||||||
return math.MaxInt
|
|
||||||
}
|
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
package pgconn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgproto3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *PgConn) oauthAuth(ctx context.Context) error {
|
||||||
|
if c.config.OAuthTokenProvider == nil {
|
||||||
|
return errors.New("OAuth authentication required but no token provider configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := c.config.OAuthTokenProvider(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to obtain OAuth token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7628.html#section-3.1
|
||||||
|
initialResponse := []byte("n,,\x01auth=Bearer " + token + "\x01\x01")
|
||||||
|
|
||||||
|
saslInitialResponse := &pgproto3.SASLInitialResponse{
|
||||||
|
AuthMechanism: "OAUTHBEARER",
|
||||||
|
Data: initialResponse,
|
||||||
|
}
|
||||||
|
c.frontend.Send(saslInitialResponse)
|
||||||
|
err = c.flushWithPotentialWriteReadDeadlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := c.receiveMessage()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m := msg.(type) {
|
||||||
|
case *pgproto3.AuthenticationOk:
|
||||||
|
return nil
|
||||||
|
case *pgproto3.AuthenticationSASLContinue:
|
||||||
|
// Server sent error response in SASL continue
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7628.html#section-3.2.2
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7628.html#section-3.2.3
|
||||||
|
errResponse := struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
OpenIDConfiguration string `json:"openid-configuration"`
|
||||||
|
}{}
|
||||||
|
err := json.Unmarshal(m.Data, &errResponse)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid OAuth error response from server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per RFC 7628 section 3.2.3, we should send a SASLResponse which only contains \x01.
|
||||||
|
// However, since the connection will be closed anyway, we can skip this
|
||||||
|
return fmt.Errorf("OAuth authentication failed: %s", errResponse.Status)
|
||||||
|
|
||||||
|
case *pgproto3.ErrorResponse:
|
||||||
|
return ErrorResponseToPgError(m)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected message type during OAuth auth: %T", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
+148
-21
@@ -1,7 +1,8 @@
|
|||||||
// SCRAM-SHA-256 authentication
|
// SCRAM-SHA-256 and SCRAM-SHA-256-PLUS authentication
|
||||||
//
|
//
|
||||||
// Resources:
|
// Resources:
|
||||||
// https://tools.ietf.org/html/rfc5802
|
// https://tools.ietf.org/html/rfc5802
|
||||||
|
// https://tools.ietf.org/html/rfc5929
|
||||||
// https://tools.ietf.org/html/rfc8265
|
// https://tools.ietf.org/html/rfc8265
|
||||||
// https://www.postgresql.org/docs/current/sasl-authentication.html
|
// https://www.postgresql.org/docs/current/sasl-authentication.html
|
||||||
//
|
//
|
||||||
@@ -15,19 +16,28 @@ package pgconn
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
|
"crypto/pbkdf2"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgproto3"
|
"github.com/jackc/pgx/v5/pgproto3"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
|
||||||
"golang.org/x/text/secure/precis"
|
"golang.org/x/text/secure/precis"
|
||||||
)
|
)
|
||||||
|
|
||||||
const clientNonceLen = 18
|
const (
|
||||||
|
clientNonceLen = 18
|
||||||
|
scramSHA256Name = "SCRAM-SHA-256"
|
||||||
|
scramSHA256PlusName = "SCRAM-SHA-256-PLUS"
|
||||||
|
)
|
||||||
|
|
||||||
// Perform SCRAM authentication.
|
// Perform SCRAM authentication.
|
||||||
func (c *PgConn) scramAuth(serverAuthMechanisms []string) error {
|
func (c *PgConn) scramAuth(serverAuthMechanisms []string) error {
|
||||||
@@ -36,9 +46,35 @@ func (c *PgConn) scramAuth(serverAuthMechanisms []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverHasPlus := slices.Contains(sc.serverAuthMechanisms, scramSHA256PlusName)
|
||||||
|
if c.config.ChannelBinding == "require" && !serverHasPlus {
|
||||||
|
return errors.New("channel binding required but server does not support SCRAM-SHA-256-PLUS")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a TLS connection and channel binding is not disabled, attempt to
|
||||||
|
// extract the server certificate hash for tls-server-end-point channel binding.
|
||||||
|
if tlsConn, ok := c.conn.(*tls.Conn); ok && c.config.ChannelBinding != "disable" {
|
||||||
|
certHash, err := getTLSCertificateHash(tlsConn)
|
||||||
|
if err != nil && c.config.ChannelBinding == "require" {
|
||||||
|
return fmt.Errorf("channel binding required but failed to get server certificate hash: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade to SCRAM-SHA-256-PLUS if we have binding data and the server supports it.
|
||||||
|
if certHash != nil && serverHasPlus {
|
||||||
|
sc.authMechanism = scramSHA256PlusName
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.channelBindingData = certHash
|
||||||
|
sc.hasTLS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.config.ChannelBinding == "require" && sc.channelBindingData == nil {
|
||||||
|
return errors.New("channel binding required but channel binding data is not available")
|
||||||
|
}
|
||||||
|
|
||||||
// Send client-first-message in a SASLInitialResponse
|
// Send client-first-message in a SASLInitialResponse
|
||||||
saslInitialResponse := &pgproto3.SASLInitialResponse{
|
saslInitialResponse := &pgproto3.SASLInitialResponse{
|
||||||
AuthMechanism: "SCRAM-SHA-256",
|
AuthMechanism: sc.authMechanism,
|
||||||
Data: sc.clientFirstMessage(),
|
Data: sc.clientFirstMessage(),
|
||||||
}
|
}
|
||||||
c.frontend.Send(saslInitialResponse)
|
c.frontend.Send(saslInitialResponse)
|
||||||
@@ -107,10 +143,31 @@ func (c *PgConn) rxSASLFinal() (*pgproto3.AuthenticationSASLFinal, error) {
|
|||||||
|
|
||||||
type scramClient struct {
|
type scramClient struct {
|
||||||
serverAuthMechanisms []string
|
serverAuthMechanisms []string
|
||||||
password []byte
|
password string
|
||||||
clientNonce []byte
|
clientNonce []byte
|
||||||
|
|
||||||
|
// authMechanism is the selected SASL mechanism for the client. Must be
|
||||||
|
// either SCRAM-SHA-256 (default) or SCRAM-SHA-256-PLUS.
|
||||||
|
//
|
||||||
|
// Upgraded to SCRAM-SHA-256-PLUS during authentication when channel binding
|
||||||
|
// is not disabled, channel binding data is available (TLS connection with
|
||||||
|
// an obtainable server certificate hash) and the server advertises
|
||||||
|
// SCRAM-SHA-256-PLUS.
|
||||||
|
authMechanism string
|
||||||
|
|
||||||
|
// hasTLS indicates whether the connection is using TLS. This is
|
||||||
|
// needed because the GS2 header must distinguish between a client that
|
||||||
|
// supports channel binding but the server does not ("y,,") versus one
|
||||||
|
// that does not support it at all ("n,,").
|
||||||
|
hasTLS bool
|
||||||
|
|
||||||
|
// channelBindingData is the hash of the server's TLS certificate, computed
|
||||||
|
// per the tls-server-end-point channel binding type (RFC 5929). Used as
|
||||||
|
// the binding input in SCRAM-SHA-256-PLUS. nil when not in use.
|
||||||
|
channelBindingData []byte
|
||||||
|
|
||||||
clientFirstMessageBare []byte
|
clientFirstMessageBare []byte
|
||||||
|
clientGS2Header []byte
|
||||||
|
|
||||||
serverFirstMessage []byte
|
serverFirstMessage []byte
|
||||||
clientAndServerNonce []byte
|
clientAndServerNonce []byte
|
||||||
@@ -124,26 +181,23 @@ type scramClient struct {
|
|||||||
func newScramClient(serverAuthMechanisms []string, password string) (*scramClient, error) {
|
func newScramClient(serverAuthMechanisms []string, password string) (*scramClient, error) {
|
||||||
sc := &scramClient{
|
sc := &scramClient{
|
||||||
serverAuthMechanisms: serverAuthMechanisms,
|
serverAuthMechanisms: serverAuthMechanisms,
|
||||||
|
authMechanism: scramSHA256Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure server supports SCRAM-SHA-256
|
// Ensure the server supports SCRAM-SHA-256. SCRAM-SHA-256-PLUS is the
|
||||||
hasScramSHA256 := false
|
// channel binding variant and is only advertised when the server supports
|
||||||
for _, mech := range sc.serverAuthMechanisms {
|
// SSL. PostgreSQL always advertises the base SCRAM-SHA-256 mechanism
|
||||||
if mech == "SCRAM-SHA-256" {
|
// regardless of SSL.
|
||||||
hasScramSHA256 = true
|
if !slices.Contains(sc.serverAuthMechanisms, scramSHA256Name) {
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasScramSHA256 {
|
|
||||||
return nil, errors.New("server does not support SCRAM-SHA-256")
|
return nil, errors.New("server does not support SCRAM-SHA-256")
|
||||||
}
|
}
|
||||||
|
|
||||||
// precis.OpaqueString is equivalent to SASLprep for password.
|
// precis.OpaqueString is equivalent to SASLprep for password.
|
||||||
var err error
|
var err error
|
||||||
sc.password, err = precis.OpaqueString.Bytes([]byte(password))
|
sc.password, err = precis.OpaqueString.String(password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// PostgreSQL allows passwords invalid according to SCRAM / SASLprep.
|
// PostgreSQL allows passwords invalid according to SCRAM / SASLprep.
|
||||||
sc.password = []byte(password)
|
sc.password = password
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, clientNonceLen)
|
buf := make([]byte, clientNonceLen)
|
||||||
@@ -158,8 +212,32 @@ func newScramClient(serverAuthMechanisms []string, password string) (*scramClien
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sc *scramClient) clientFirstMessage() []byte {
|
func (sc *scramClient) clientFirstMessage() []byte {
|
||||||
sc.clientFirstMessageBare = []byte(fmt.Sprintf("n=,r=%s", sc.clientNonce))
|
// The client-first-message is the GS2 header concatenated with the bare
|
||||||
return []byte(fmt.Sprintf("n,,%s", sc.clientFirstMessageBare))
|
// message (username + client nonce). The GS2 header communicates the
|
||||||
|
// client's channel binding capability to the server:
|
||||||
|
//
|
||||||
|
// "n,," - client is not using TLS (channel binding not possible)
|
||||||
|
// "y,," - client is using TLS but channel binding is not
|
||||||
|
// in use (e.g., server did not advertise SCRAM-SHA-256-PLUS
|
||||||
|
// or the server certificate hash was not obtainable)
|
||||||
|
// "p=tls-server-end-point,," - channel binding is active via SCRAM-SHA-256-PLUS
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc5802#section-6
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc5929#section-4
|
||||||
|
// https://www.postgresql.org/docs/current/sasl-authentication.html#SASL-SCRAM-SHA-256
|
||||||
|
|
||||||
|
sc.clientFirstMessageBare = fmt.Appendf(nil, "n=,r=%s", sc.clientNonce)
|
||||||
|
|
||||||
|
if sc.authMechanism == scramSHA256PlusName {
|
||||||
|
sc.clientGS2Header = []byte("p=tls-server-end-point,,")
|
||||||
|
} else if sc.hasTLS {
|
||||||
|
sc.clientGS2Header = []byte("y,,")
|
||||||
|
} else {
|
||||||
|
sc.clientGS2Header = []byte("n,,")
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(sc.clientGS2Header, sc.clientFirstMessageBare...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *scramClient) recvServerFirstMessage(serverFirstMessage []byte) error {
|
func (sc *scramClient) recvServerFirstMessage(serverFirstMessage []byte) error {
|
||||||
@@ -218,9 +296,25 @@ func (sc *scramClient) recvServerFirstMessage(serverFirstMessage []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sc *scramClient) clientFinalMessage() string {
|
func (sc *scramClient) clientFinalMessage() string {
|
||||||
clientFinalMessageWithoutProof := []byte(fmt.Sprintf("c=biws,r=%s", sc.clientAndServerNonce))
|
// The c= attribute carries the base64-encoded channel binding input.
|
||||||
|
//
|
||||||
|
// Without channel binding this is just the GS2 header alone ("biws" for
|
||||||
|
// "n,," or "eSws" for "y,,").
|
||||||
|
//
|
||||||
|
// With channel binding, this is the GS2 header with the channel binding data
|
||||||
|
// (certificate hash) appended.
|
||||||
|
channelBindInput := sc.clientGS2Header
|
||||||
|
if sc.authMechanism == scramSHA256PlusName {
|
||||||
|
channelBindInput = slices.Concat(sc.clientGS2Header, sc.channelBindingData)
|
||||||
|
}
|
||||||
|
channelBindingEncoded := base64.StdEncoding.EncodeToString(channelBindInput)
|
||||||
|
clientFinalMessageWithoutProof := fmt.Appendf(nil, "c=%s,r=%s", channelBindingEncoded, sc.clientAndServerNonce)
|
||||||
|
|
||||||
sc.saltedPassword = pbkdf2.Key([]byte(sc.password), sc.salt, sc.iterations, 32, sha256.New)
|
var err error
|
||||||
|
sc.saltedPassword, err = pbkdf2.Key(sha256.New, sc.password, sc.salt, sc.iterations, 32)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // This should never happen.
|
||||||
|
}
|
||||||
sc.authMessage = bytes.Join([][]byte{sc.clientFirstMessageBare, sc.serverFirstMessage, clientFinalMessageWithoutProof}, []byte(","))
|
sc.authMessage = bytes.Join([][]byte{sc.clientFirstMessageBare, sc.serverFirstMessage, clientFinalMessageWithoutProof}, []byte(","))
|
||||||
|
|
||||||
clientProof := computeClientProof(sc.saltedPassword, sc.authMessage)
|
clientProof := computeClientProof(sc.saltedPassword, sc.authMessage)
|
||||||
@@ -254,7 +348,7 @@ func computeClientProof(saltedPassword, authMessage []byte) []byte {
|
|||||||
clientSignature := computeHMAC(storedKey[:], authMessage)
|
clientSignature := computeHMAC(storedKey[:], authMessage)
|
||||||
|
|
||||||
clientProof := make([]byte, len(clientSignature))
|
clientProof := make([]byte, len(clientSignature))
|
||||||
for i := 0; i < len(clientSignature); i++ {
|
for i := range clientSignature {
|
||||||
clientProof[i] = clientKey[i] ^ clientSignature[i]
|
clientProof[i] = clientKey[i] ^ clientSignature[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,3 +364,36 @@ func computeServerSignature(saltedPassword, authMessage []byte) []byte {
|
|||||||
base64.StdEncoding.Encode(buf, serverSignature)
|
base64.StdEncoding.Encode(buf, serverSignature)
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the server certificate hash for SCRAM channel binding type
|
||||||
|
// tls-server-end-point.
|
||||||
|
func getTLSCertificateHash(conn *tls.Conn) ([]byte, error) {
|
||||||
|
state := conn.ConnectionState()
|
||||||
|
if len(state.PeerCertificates) == 0 {
|
||||||
|
return nil, errors.New("no peer certificates for channel binding")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := state.PeerCertificates[0]
|
||||||
|
|
||||||
|
// Per RFC 5929 section 4.1: If the certificate's signatureAlgorithm uses
|
||||||
|
// MD5 or SHA-1, use SHA-256. Otherwise use the hash from the signature
|
||||||
|
// algorithm.
|
||||||
|
//
|
||||||
|
// See: https://www.rfc-editor.org/rfc/rfc5929.html#section-4.1
|
||||||
|
var h hash.Hash
|
||||||
|
switch cert.SignatureAlgorithm {
|
||||||
|
case x509.MD5WithRSA, x509.SHA1WithRSA, x509.ECDSAWithSHA1:
|
||||||
|
h = sha256.New()
|
||||||
|
case x509.SHA256WithRSA, x509.SHA256WithRSAPSS, x509.ECDSAWithSHA256:
|
||||||
|
h = sha256.New()
|
||||||
|
case x509.SHA384WithRSA, x509.SHA384WithRSAPSS, x509.ECDSAWithSHA384:
|
||||||
|
h = sha512.New384()
|
||||||
|
case x509.SHA512WithRSA, x509.SHA512WithRSAPSS, x509.ECDSAWithSHA512:
|
||||||
|
h = sha512.New()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("tls-server-end-point channel binding is undefined for certificate signature algorithm %v", cert.SignatureAlgorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Write(cert.Raw)
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|||||||
+100
-10
@@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -55,6 +56,13 @@ type Config struct {
|
|||||||
|
|
||||||
SSLNegotiation string // sslnegotiation=postgres or sslnegotiation=direct
|
SSLNegotiation string // sslnegotiation=postgres or sslnegotiation=direct
|
||||||
|
|
||||||
|
// AfterNetConnect is called after the network connection, including TLS if applicable, is established but before any
|
||||||
|
// PostgreSQL protocol communication. It takes the established net.Conn and returns a net.Conn that will be used in
|
||||||
|
// its place. It can be used to wrap the net.Conn (e.g. for logging, diagnostics, or testing). Its functionality has
|
||||||
|
// some overlap with DialFunc. However, DialFunc takes place before TLS is established and cannot be used to control
|
||||||
|
// the final net.Conn used for PostgreSQL protocol communication while AfterNetConnect can.
|
||||||
|
AfterNetConnect func(ctx context.Context, config *Config, conn net.Conn) (net.Conn, error)
|
||||||
|
|
||||||
// ValidateConnect is called during a connection attempt after a successful authentication with the PostgreSQL server.
|
// ValidateConnect is called during a connection attempt after a successful authentication with the PostgreSQL server.
|
||||||
// It can be used to validate that the server is acceptable. If this returns an error the connection is closed and the next
|
// It can be used to validate that the server is acceptable. If this returns an error the connection is closed and the next
|
||||||
// fallback config is tried. This allows implementing high availability behavior such as libpq does with target_session_attrs.
|
// fallback config is tried. This allows implementing high availability behavior such as libpq does with target_session_attrs.
|
||||||
@@ -75,6 +83,23 @@ type Config struct {
|
|||||||
// that you close on FATAL errors by returning false.
|
// that you close on FATAL errors by returning false.
|
||||||
OnPgError PgErrorHandler
|
OnPgError PgErrorHandler
|
||||||
|
|
||||||
|
// OAuthTokenProvider is a function that returns an OAuth token for authentication. If set, it will be used for
|
||||||
|
// OAUTHBEARER SASL authentication when the server requests it.
|
||||||
|
OAuthTokenProvider func(context.Context) (string, error)
|
||||||
|
|
||||||
|
// MinProtocolVersion is the minimum acceptable PostgreSQL protocol version.
|
||||||
|
// If the server does not support at least this version, the connection will fail.
|
||||||
|
// Valid values: "3.0", "3.2", "latest". Defaults to "3.0".
|
||||||
|
MinProtocolVersion string
|
||||||
|
|
||||||
|
// MaxProtocolVersion is the maximum PostgreSQL protocol version to request from the server.
|
||||||
|
// Valid values: "3.0", "3.2", "latest". Defaults to "3.0" for compatibility.
|
||||||
|
MaxProtocolVersion string
|
||||||
|
|
||||||
|
// ChannelBinding is the channel_binding parameter for SCRAM-SHA-256-PLUS authentication.
|
||||||
|
// Valid values: "disable", "prefer", "require". Defaults to "prefer".
|
||||||
|
ChannelBinding string
|
||||||
|
|
||||||
createdByParseConfig bool // Used to enforce created by ParseConfig rule.
|
createdByParseConfig bool // Used to enforce created by ParseConfig rule.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,9 +121,7 @@ func (c *Config) Copy() *Config {
|
|||||||
}
|
}
|
||||||
if newConf.RuntimeParams != nil {
|
if newConf.RuntimeParams != nil {
|
||||||
newConf.RuntimeParams = make(map[string]string, len(c.RuntimeParams))
|
newConf.RuntimeParams = make(map[string]string, len(c.RuntimeParams))
|
||||||
for k, v := range c.RuntimeParams {
|
maps.Copy(newConf.RuntimeParams, c.RuntimeParams)
|
||||||
newConf.RuntimeParams[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if newConf.Fallbacks != nil {
|
if newConf.Fallbacks != nil {
|
||||||
newConf.Fallbacks = make([]*FallbackConfig, len(c.Fallbacks))
|
newConf.Fallbacks = make([]*FallbackConfig, len(c.Fallbacks))
|
||||||
@@ -207,6 +230,8 @@ func NetworkAddress(host string, port uint16) (network, address string) {
|
|||||||
// PGCONNECT_TIMEOUT
|
// PGCONNECT_TIMEOUT
|
||||||
// PGTARGETSESSIONATTRS
|
// PGTARGETSESSIONATTRS
|
||||||
// PGTZ
|
// PGTZ
|
||||||
|
// PGMINPROTOCOLVERSION
|
||||||
|
// PGMAXPROTOCOLVERSION
|
||||||
//
|
//
|
||||||
// See http://www.postgresql.org/docs/current/static/libpq-envars.html for details on the meaning of environment variables.
|
// See http://www.postgresql.org/docs/current/static/libpq-envars.html for details on the meaning of environment variables.
|
||||||
//
|
//
|
||||||
@@ -332,6 +357,9 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
|||||||
"target_session_attrs": {},
|
"target_session_attrs": {},
|
||||||
"service": {},
|
"service": {},
|
||||||
"servicefile": {},
|
"servicefile": {},
|
||||||
|
"min_protocol_version": {},
|
||||||
|
"max_protocol_version": {},
|
||||||
|
"channel_binding": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding kerberos configuration
|
// Adding kerberos configuration
|
||||||
@@ -424,6 +452,52 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
|||||||
return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("unknown target_session_attrs value: %v", tsa)}
|
return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("unknown target_session_attrs value: %v", tsa)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
minProto, err := parseProtocolVersion(settings["min_protocol_version"])
|
||||||
|
if err != nil {
|
||||||
|
return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("invalid min_protocol_version: %q", settings["min_protocol_version"]), err: err}
|
||||||
|
}
|
||||||
|
maxProto, err := parseProtocolVersion(settings["max_protocol_version"])
|
||||||
|
if err != nil {
|
||||||
|
return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("invalid max_protocol_version: %q", settings["max_protocol_version"]), err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.MinProtocolVersion = settings["min_protocol_version"]
|
||||||
|
config.MaxProtocolVersion = settings["max_protocol_version"]
|
||||||
|
|
||||||
|
if config.MinProtocolVersion == "" {
|
||||||
|
config.MinProtocolVersion = "3.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// When max_protocol_version is not explicitly set, default based on
|
||||||
|
// min_protocol_version. This matches libpq behavior: if min > 3.0,
|
||||||
|
// default max to latest; otherwise default to 3.0 for compatibility
|
||||||
|
// with older servers/poolers that don't support NegotiateProtocolVersion.
|
||||||
|
if config.MaxProtocolVersion == "" {
|
||||||
|
if minProto > pgproto3.ProtocolVersion30 {
|
||||||
|
config.MaxProtocolVersion = "latest"
|
||||||
|
} else {
|
||||||
|
config.MaxProtocolVersion = "3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only error when max_protocol_version was explicitly set and conflicts
|
||||||
|
// with min_protocol_version. When max_protocol_version is not explicitly
|
||||||
|
// set, the auto-raise logic above already ensures a valid default.
|
||||||
|
if minProto > maxProto && settings["max_protocol_version"] != "" {
|
||||||
|
return nil, &ParseConfigError{ConnString: connString, msg: "min_protocol_version cannot be greater than max_protocol_version"}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch channelBinding := settings["channel_binding"]; channelBinding {
|
||||||
|
case "", "prefer":
|
||||||
|
config.ChannelBinding = "prefer"
|
||||||
|
case "disable":
|
||||||
|
config.ChannelBinding = "disable"
|
||||||
|
case "require":
|
||||||
|
config.ChannelBinding = "require"
|
||||||
|
default:
|
||||||
|
return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("unknown channel_binding value: %v", channelBinding)}
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,9 +505,7 @@ func mergeSettings(settingSets ...map[string]string) map[string]string {
|
|||||||
settings := make(map[string]string)
|
settings := make(map[string]string)
|
||||||
|
|
||||||
for _, s2 := range settingSets {
|
for _, s2 := range settingSets {
|
||||||
for k, v := range s2 {
|
maps.Copy(settings, s2)
|
||||||
settings[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
@@ -463,6 +535,8 @@ func parseEnvSettings() map[string]string {
|
|||||||
"PGSERVICEFILE": "servicefile",
|
"PGSERVICEFILE": "servicefile",
|
||||||
"PGTZ": "timezone",
|
"PGTZ": "timezone",
|
||||||
"PGOPTIONS": "options",
|
"PGOPTIONS": "options",
|
||||||
|
"PGMINPROTOCOLVERSION": "min_protocol_version",
|
||||||
|
"PGMAXPROTOCOLVERSION": "max_protocol_version",
|
||||||
}
|
}
|
||||||
|
|
||||||
for envname, realname := range nameMap {
|
for envname, realname := range nameMap {
|
||||||
@@ -487,7 +561,9 @@ func parseURLSettings(connString string) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if parsedURL.User != nil {
|
if parsedURL.User != nil {
|
||||||
settings["user"] = parsedURL.User.Username()
|
if u := parsedURL.User.Username(); u != "" {
|
||||||
|
settings["user"] = u
|
||||||
|
}
|
||||||
if password, present := parsedURL.User.Password(); present {
|
if password, present := parsedURL.User.Password(); present {
|
||||||
settings["password"] = password
|
settings["password"] = password
|
||||||
}
|
}
|
||||||
@@ -496,7 +572,7 @@ func parseURLSettings(connString string) (map[string]string, error) {
|
|||||||
// Handle multiple host:port's in url.Host by splitting them into host,host,host and port,port,port.
|
// Handle multiple host:port's in url.Host by splitting them into host,host,host and port,port,port.
|
||||||
var hosts []string
|
var hosts []string
|
||||||
var ports []string
|
var ports []string
|
||||||
for _, host := range strings.Split(parsedURL.Host, ",") {
|
for host := range strings.SplitSeq(parsedURL.Host, ",") {
|
||||||
if host == "" {
|
if host == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -614,6 +690,9 @@ func parseKeywordValueSettings(s string) (map[string]string, error) {
|
|||||||
return nil, errors.New("invalid keyword/value")
|
return nil, errors.New("invalid keyword/value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key == "user" && val == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
settings[key] = val
|
settings[key] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,7 +863,7 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P
|
|||||||
// Attempt decryption with pass phrase
|
// Attempt decryption with pass phrase
|
||||||
// NOTE: only supports RSA (PKCS#1)
|
// NOTE: only supports RSA (PKCS#1)
|
||||||
if sslpassword != "" {
|
if sslpassword != "" {
|
||||||
decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword))
|
decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) //nolint:ineffassign
|
||||||
}
|
}
|
||||||
// if sslpassword not provided or has decryption error when use it
|
// if sslpassword not provided or has decryption error when use it
|
||||||
// try to find sslpassword with callback function
|
// try to find sslpassword with callback function
|
||||||
@@ -799,7 +878,7 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P
|
|||||||
decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword))
|
decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword))
|
||||||
// Should we also provide warning for PKCS#1 needed?
|
// Should we also provide warning for PKCS#1 needed?
|
||||||
if decryptedError != nil {
|
if decryptedError != nil {
|
||||||
return nil, fmt.Errorf("unable to decrypt key: %w", err)
|
return nil, fmt.Errorf("unable to decrypt key: %w", decryptedError)
|
||||||
}
|
}
|
||||||
|
|
||||||
pemBytes := pem.Block{
|
pemBytes := pem.Block{
|
||||||
@@ -951,3 +1030,14 @@ func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseProtocolVersion(s string) (uint32, error) {
|
||||||
|
switch s {
|
||||||
|
case "", "3.0":
|
||||||
|
return pgproto3.ProtocolVersion30, nil
|
||||||
|
case "3.2", "latest":
|
||||||
|
return pgproto3.ProtocolVersion32, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("invalid protocol version: %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+15
-23
@@ -9,11 +9,12 @@ import (
|
|||||||
// time.
|
// time.
|
||||||
type ContextWatcher struct {
|
type ContextWatcher struct {
|
||||||
handler Handler
|
handler Handler
|
||||||
unwatchChan chan struct{}
|
|
||||||
|
|
||||||
|
// Lock protects the members below.
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
watchInProgress bool
|
// Stop is the handle for an "after func". See [context.AfterFunc].
|
||||||
onCancelWasCalled bool
|
stop func() bool
|
||||||
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContextWatcher returns a ContextWatcher. onCancel will be called when a watched context is canceled.
|
// NewContextWatcher returns a ContextWatcher. onCancel will be called when a watched context is canceled.
|
||||||
@@ -22,7 +23,6 @@ type ContextWatcher struct {
|
|||||||
func NewContextWatcher(handler Handler) *ContextWatcher {
|
func NewContextWatcher(handler Handler) *ContextWatcher {
|
||||||
cw := &ContextWatcher{
|
cw := &ContextWatcher{
|
||||||
handler: handler,
|
handler: handler,
|
||||||
unwatchChan: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cw
|
return cw
|
||||||
@@ -33,25 +33,16 @@ func (cw *ContextWatcher) Watch(ctx context.Context) {
|
|||||||
cw.lock.Lock()
|
cw.lock.Lock()
|
||||||
defer cw.lock.Unlock()
|
defer cw.lock.Unlock()
|
||||||
|
|
||||||
if cw.watchInProgress {
|
if cw.stop != nil {
|
||||||
panic("Watch already in progress")
|
panic("watch already in progress")
|
||||||
}
|
}
|
||||||
|
|
||||||
cw.onCancelWasCalled = false
|
|
||||||
|
|
||||||
if ctx.Done() != nil {
|
if ctx.Done() != nil {
|
||||||
cw.watchInProgress = true
|
cw.done = make(chan struct{})
|
||||||
go func() {
|
cw.stop = context.AfterFunc(ctx, func() {
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
cw.handler.HandleCancel(ctx)
|
cw.handler.HandleCancel(ctx)
|
||||||
cw.onCancelWasCalled = true
|
close(cw.done)
|
||||||
<-cw.unwatchChan
|
})
|
||||||
case <-cw.unwatchChan:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
cw.watchInProgress = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +52,13 @@ func (cw *ContextWatcher) Unwatch() {
|
|||||||
cw.lock.Lock()
|
cw.lock.Lock()
|
||||||
defer cw.lock.Unlock()
|
defer cw.lock.Unlock()
|
||||||
|
|
||||||
if cw.watchInProgress {
|
if cw.stop != nil {
|
||||||
cw.unwatchChan <- struct{}{}
|
if !cw.stop() {
|
||||||
if cw.onCancelWasCalled {
|
<-cw.done
|
||||||
cw.handler.HandleUnwatchAfterCancel()
|
cw.handler.HandleUnwatchAfterCancel()
|
||||||
}
|
}
|
||||||
cw.watchInProgress = false
|
cw.stop = nil
|
||||||
|
cw.done = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
@@ -254,3 +254,20 @@ func (e *NotPreferredError) SafeToRetry() bool {
|
|||||||
func (e *NotPreferredError) Unwrap() error {
|
func (e *NotPreferredError) Unwrap() error {
|
||||||
return e.err
|
return e.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PrepareError struct {
|
||||||
|
err error
|
||||||
|
|
||||||
|
ParseComplete bool // Indicates whether the error occurred after a ParseComplete message was received.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PrepareError) Error() string {
|
||||||
|
if e.ParseComplete {
|
||||||
|
return fmt.Sprintf("prepare failed after ParseComplete: %s", e.err.Error())
|
||||||
|
}
|
||||||
|
return e.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PrepareError) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|||||||
+588
-120
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user