Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e29c7e31c1 | |||
| 43f4680176 | |||
| d9f27c1775 |
@@ -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.59
|
||||||
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.59
|
||||||
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.
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
-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
+1
@@ -33,6 +33,7 @@ func (dst *AuthenticationSASL) Decode(src []byte) error {
|
|||||||
return errors.New("bad auth type")
|
return errors.New("bad auth type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dst.AuthMechanisms = dst.AuthMechanisms[:0]
|
||||||
authMechanisms := src[4:]
|
authMechanisms := src[4:]
|
||||||
for len(authMechanisms) > 1 {
|
for len(authMechanisms) > 1 {
|
||||||
idx := bytes.IndexByte(authMechanisms, 0)
|
idx := bytes.IndexByte(authMechanisms, 0)
|
||||||
|
|||||||
+4
-4
@@ -47,7 +47,7 @@ type Backend struct {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
minStartupPacketLen = 4 // minStartupPacketLen is a single 32-bit int version or code.
|
minStartupPacketLen = 4 // minStartupPacketLen is a single 32-bit int version or code.
|
||||||
maxStartupPacketLen = 10000 // maxStartupPacketLen is MAX_STARTUP_PACKET_LENGTH from PG source.
|
maxStartupPacketLen = 10_000 // maxStartupPacketLen is MAX_STARTUP_PACKET_LENGTH from PG source.
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewBackend creates a new Backend.
|
// NewBackend creates a new Backend.
|
||||||
@@ -123,7 +123,7 @@ func (b *Backend) ReceiveStartupMessage() (FrontendMessage, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
msgSize := int(binary.BigEndian.Uint32(buf) - 4)
|
msgSize := int(int32(binary.BigEndian.Uint32(buf)) - 4)
|
||||||
|
|
||||||
if msgSize < minStartupPacketLen || msgSize > maxStartupPacketLen {
|
if msgSize < minStartupPacketLen || msgSize > maxStartupPacketLen {
|
||||||
return nil, fmt.Errorf("invalid length of startup packet: %d", msgSize)
|
return nil, fmt.Errorf("invalid length of startup packet: %d", msgSize)
|
||||||
@@ -137,7 +137,7 @@ func (b *Backend) ReceiveStartupMessage() (FrontendMessage, error) {
|
|||||||
code := binary.BigEndian.Uint32(buf)
|
code := binary.BigEndian.Uint32(buf)
|
||||||
|
|
||||||
switch code {
|
switch code {
|
||||||
case ProtocolVersionNumber:
|
case ProtocolVersion30, ProtocolVersion32:
|
||||||
err = b.startupMessage.Decode(buf)
|
err = b.startupMessage.Decode(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -176,7 +176,7 @@ func (b *Backend) Receive() (FrontendMessage, error) {
|
|||||||
|
|
||||||
b.msgType = header[0]
|
b.msgType = header[0]
|
||||||
|
|
||||||
msgLength := int(binary.BigEndian.Uint32(header[1:]))
|
msgLength := int(int32(binary.BigEndian.Uint32(header[1:])))
|
||||||
if msgLength < 4 {
|
if msgLength < 4 {
|
||||||
return nil, fmt.Errorf("invalid message length: %d", msgLength)
|
return nil, fmt.Errorf("invalid message length: %d", msgLength)
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-6
@@ -2,6 +2,7 @@ package pgproto3
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/internal/pgio"
|
"github.com/jackc/pgx/v5/internal/pgio"
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
|
|
||||||
type BackendKeyData struct {
|
type BackendKeyData struct {
|
||||||
ProcessID uint32
|
ProcessID uint32
|
||||||
SecretKey uint32
|
SecretKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backend identifies this message as sendable by the PostgreSQL backend.
|
// Backend identifies this message as sendable by the PostgreSQL backend.
|
||||||
@@ -18,12 +19,13 @@ func (*BackendKeyData) Backend() {}
|
|||||||
// Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message
|
// Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message
|
||||||
// type identifier and 4 byte message length.
|
// type identifier and 4 byte message length.
|
||||||
func (dst *BackendKeyData) Decode(src []byte) error {
|
func (dst *BackendKeyData) Decode(src []byte) error {
|
||||||
if len(src) != 8 {
|
if len(src) < 8 {
|
||||||
return &invalidMessageLenErr{messageType: "BackendKeyData", expectedLen: 8, actualLen: len(src)}
|
return &invalidMessageLenErr{messageType: "BackendKeyData", expectedLen: 8, actualLen: len(src)}
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.ProcessID = binary.BigEndian.Uint32(src[:4])
|
dst.ProcessID = binary.BigEndian.Uint32(src[:4])
|
||||||
dst.SecretKey = binary.BigEndian.Uint32(src[4:])
|
dst.SecretKey = make([]byte, len(src)-4)
|
||||||
|
copy(dst.SecretKey, src[4:])
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -32,7 +34,7 @@ func (dst *BackendKeyData) Decode(src []byte) error {
|
|||||||
func (src *BackendKeyData) Encode(dst []byte) ([]byte, error) {
|
func (src *BackendKeyData) Encode(dst []byte) ([]byte, error) {
|
||||||
dst, sp := beginMessage(dst, 'K')
|
dst, sp := beginMessage(dst, 'K')
|
||||||
dst = pgio.AppendUint32(dst, src.ProcessID)
|
dst = pgio.AppendUint32(dst, src.ProcessID)
|
||||||
dst = pgio.AppendUint32(dst, src.SecretKey)
|
dst = append(dst, src.SecretKey...)
|
||||||
return finishMessage(dst, sp)
|
return finishMessage(dst, sp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,10 +43,29 @@ func (src BackendKeyData) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(struct {
|
return json.Marshal(struct {
|
||||||
Type string
|
Type string
|
||||||
ProcessID uint32
|
ProcessID uint32
|
||||||
SecretKey uint32
|
SecretKey string
|
||||||
}{
|
}{
|
||||||
Type: "BackendKeyData",
|
Type: "BackendKeyData",
|
||||||
ProcessID: src.ProcessID,
|
ProcessID: src.ProcessID,
|
||||||
SecretKey: src.SecretKey,
|
SecretKey: hex.EncodeToString(src.SecretKey),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements encoding/json.Unmarshaler.
|
||||||
|
func (dst *BackendKeyData) UnmarshalJSON(data []byte) error {
|
||||||
|
var msg struct {
|
||||||
|
ProcessID uint32
|
||||||
|
SecretKey string
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.ProcessID = msg.ProcessID
|
||||||
|
secretKey, err := hex.DecodeString(msg.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dst.SecretKey = secretKey
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
+4
-4
@@ -54,7 +54,7 @@ func (dst *Bind) Decode(src []byte) error {
|
|||||||
if len(src[rp:]) < len(dst.ParameterFormatCodes)*2 {
|
if len(src[rp:]) < len(dst.ParameterFormatCodes)*2 {
|
||||||
return &invalidMessageFormatErr{messageType: "Bind"}
|
return &invalidMessageFormatErr{messageType: "Bind"}
|
||||||
}
|
}
|
||||||
for i := 0; i < parameterFormatCodeCount; i++ {
|
for i := range parameterFormatCodeCount {
|
||||||
dst.ParameterFormatCodes[i] = int16(binary.BigEndian.Uint16(src[rp:]))
|
dst.ParameterFormatCodes[i] = int16(binary.BigEndian.Uint16(src[rp:]))
|
||||||
rp += 2
|
rp += 2
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ func (dst *Bind) Decode(src []byte) error {
|
|||||||
if parameterCount > 0 {
|
if parameterCount > 0 {
|
||||||
dst.Parameters = make([][]byte, parameterCount)
|
dst.Parameters = make([][]byte, parameterCount)
|
||||||
|
|
||||||
for i := 0; i < parameterCount; i++ {
|
for i := range parameterCount {
|
||||||
if len(src[rp:]) < 4 {
|
if len(src[rp:]) < 4 {
|
||||||
return &invalidMessageFormatErr{messageType: "Bind"}
|
return &invalidMessageFormatErr{messageType: "Bind"}
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ func (dst *Bind) Decode(src []byte) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(src[rp:]) < msgSize {
|
if msgSize < 0 || len(src[rp:]) < msgSize {
|
||||||
return &invalidMessageFormatErr{messageType: "Bind"}
|
return &invalidMessageFormatErr{messageType: "Bind"}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ func (dst *Bind) Decode(src []byte) error {
|
|||||||
if len(src[rp:]) < len(dst.ResultFormatCodes)*2 {
|
if len(src[rp:]) < len(dst.ResultFormatCodes)*2 {
|
||||||
return &invalidMessageFormatErr{messageType: "Bind"}
|
return &invalidMessageFormatErr{messageType: "Bind"}
|
||||||
}
|
}
|
||||||
for i := 0; i < resultFormatCodeCount; i++ {
|
for i := range resultFormatCodeCount {
|
||||||
dst.ResultFormatCodes[i] = int16(binary.BigEndian.Uint16(src[rp:]))
|
dst.ResultFormatCodes[i] = int16(binary.BigEndian.Uint16(src[rp:]))
|
||||||
rp += 2
|
rp += 2
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-9
@@ -2,6 +2,7 @@ package pgproto3
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
@@ -12,35 +13,42 @@ const cancelRequestCode = 80877102
|
|||||||
|
|
||||||
type CancelRequest struct {
|
type CancelRequest struct {
|
||||||
ProcessID uint32
|
ProcessID uint32
|
||||||
SecretKey uint32
|
SecretKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frontend identifies this message as sendable by a PostgreSQL frontend.
|
// Frontend identifies this message as sendable by a PostgreSQL frontend.
|
||||||
func (*CancelRequest) Frontend() {}
|
func (*CancelRequest) Frontend() {}
|
||||||
|
|
||||||
func (dst *CancelRequest) Decode(src []byte) error {
|
func (dst *CancelRequest) Decode(src []byte) error {
|
||||||
if len(src) != 12 {
|
if len(src) < 12 {
|
||||||
return errors.New("bad cancel request size")
|
return errors.New("cancel request too short")
|
||||||
|
}
|
||||||
|
if len(src) > 264 {
|
||||||
|
return errors.New("cancel request too long")
|
||||||
}
|
}
|
||||||
|
|
||||||
requestCode := binary.BigEndian.Uint32(src)
|
requestCode := binary.BigEndian.Uint32(src)
|
||||||
|
|
||||||
if requestCode != cancelRequestCode {
|
if requestCode != cancelRequestCode {
|
||||||
return errors.New("bad cancel request code")
|
return errors.New("bad cancel request code")
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.ProcessID = binary.BigEndian.Uint32(src[4:])
|
dst.ProcessID = binary.BigEndian.Uint32(src[4:])
|
||||||
dst.SecretKey = binary.BigEndian.Uint32(src[8:])
|
dst.SecretKey = make([]byte, len(src)-8)
|
||||||
|
copy(dst.SecretKey, src[8:])
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode encodes src into dst. dst will include the 4 byte message length.
|
// Encode encodes src into dst. dst will include the 4 byte message length.
|
||||||
func (src *CancelRequest) Encode(dst []byte) ([]byte, error) {
|
func (src *CancelRequest) Encode(dst []byte) ([]byte, error) {
|
||||||
dst = pgio.AppendInt32(dst, 16)
|
if len(src.SecretKey) > 256 {
|
||||||
|
return nil, errors.New("secret key too long")
|
||||||
|
}
|
||||||
|
msgLen := int32(12 + len(src.SecretKey))
|
||||||
|
dst = pgio.AppendInt32(dst, msgLen)
|
||||||
dst = pgio.AppendInt32(dst, cancelRequestCode)
|
dst = pgio.AppendInt32(dst, cancelRequestCode)
|
||||||
dst = pgio.AppendUint32(dst, src.ProcessID)
|
dst = pgio.AppendUint32(dst, src.ProcessID)
|
||||||
dst = pgio.AppendUint32(dst, src.SecretKey)
|
dst = append(dst, src.SecretKey...)
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,10 +57,29 @@ func (src CancelRequest) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(struct {
|
return json.Marshal(struct {
|
||||||
Type string
|
Type string
|
||||||
ProcessID uint32
|
ProcessID uint32
|
||||||
SecretKey uint32
|
SecretKey string
|
||||||
}{
|
}{
|
||||||
Type: "CancelRequest",
|
Type: "CancelRequest",
|
||||||
ProcessID: src.ProcessID,
|
ProcessID: src.ProcessID,
|
||||||
SecretKey: src.SecretKey,
|
SecretKey: hex.EncodeToString(src.SecretKey),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements encoding/json.Unmarshaler.
|
||||||
|
func (dst *CancelRequest) UnmarshalJSON(data []byte) error {
|
||||||
|
var msg struct {
|
||||||
|
ProcessID uint32
|
||||||
|
SecretKey string
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.ProcessID = msg.ProcessID
|
||||||
|
secretKey, err := hex.DecodeString(msg.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dst.SecretKey = secretKey
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ func (dst *CopyBothResponse) Decode(src []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
columnFormatCodes := make([]uint16, columnCount)
|
columnFormatCodes := make([]uint16, columnCount)
|
||||||
for i := 0; i < columnCount; i++ {
|
for i := range columnCount {
|
||||||
columnFormatCodes[i] = binary.BigEndian.Uint16(buf.Next(2))
|
columnFormatCodes[i] = binary.BigEndian.Uint16(buf.Next(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
@@ -15,6 +15,10 @@ func (*CopyFail) Frontend() {}
|
|||||||
// Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message
|
// Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message
|
||||||
// type identifier and 4 byte message length.
|
// type identifier and 4 byte message length.
|
||||||
func (dst *CopyFail) Decode(src []byte) error {
|
func (dst *CopyFail) Decode(src []byte) error {
|
||||||
|
if len(src) == 0 {
|
||||||
|
return &invalidMessageFormatErr{messageType: "CopyFail"}
|
||||||
|
}
|
||||||
|
|
||||||
idx := bytes.IndexByte(src, 0)
|
idx := bytes.IndexByte(src, 0)
|
||||||
if idx != len(src)-1 {
|
if idx != len(src)-1 {
|
||||||
return &invalidMessageFormatErr{messageType: "CopyFail"}
|
return &invalidMessageFormatErr{messageType: "CopyFail"}
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ func (dst *CopyInResponse) Decode(src []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
columnFormatCodes := make([]uint16, columnCount)
|
columnFormatCodes := make([]uint16, columnCount)
|
||||||
for i := 0; i < columnCount; i++ {
|
for i := range columnCount {
|
||||||
columnFormatCodes[i] = binary.BigEndian.Uint16(buf.Next(2))
|
columnFormatCodes[i] = binary.BigEndian.Uint16(buf.Next(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user