├── .gitignore
├── LICENSE
├── README.md
├── ai1.png
├── aih.gif
├── bard.go
├── build.sh
├── chatgpt.go
├── claude.go
├── claude.goo
├── else
├── README.md-
├── bard.py
├── chatgpt.py
├── claude2.py
├── dev.go
├── eng
│ ├── go.mod
│ ├── go.sum
│ └── playphrase.go
├── ggt.go
├── huggingchat.py
└── thought.txt
├── falcon180.go
├── go.mod
├── go.sum
├── llama2.go
├── main.go
├── release.sh
├── release
└── upload.go
├── ryy
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── TODO.md
├── action.go
├── buffer.go
├── clipboard.go
├── commands.go
├── config.go
├── go.mod
├── go.sum
├── highlighting.go
├── hooks.go
├── init.go
├── key.go
├── mark.go
├── mode_term.go
├── mode_visual.go
├── modes.go
├── prompt.go
├── render.go
├── ry.go
├── ryy
├── screenshot.png
├── search.go
├── styles.go
├── terminal
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── color.go
│ ├── csi.go
│ ├── csi_test.go
│ ├── doc.go
│ ├── ioctl.go
│ ├── parse.go
│ ├── state.go
│ ├── str.go
│ ├── str_test.go
│ ├── vt.go
│ └── vt_test.go
├── utils.go
└── view.go
└── vi.go
/.gitignore:
--------------------------------------------------------------------------------
1 | aih
2 | mvi
3 | vi
4 | aih.json
5 | aih.mov
6 | .history
7 | .quest.txt
8 | .aih.json
9 | .mvi
10 | .vi
11 | release/*
12 | !release/upload.go
13 | history.txt
14 | test
15 | test/*
16 | ryy/.git
17 | ryy/.gitignore
18 | tmp
19 | tmp/*
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 ashahChen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Talk to AI modes in terminal.
2 |
3 |
4 |
5 | ## Usage
6 | Download [binary file](https://github.com/Databingo/aih/releases) then type:
7 | ```bash
8 | ./aih
9 | ```
10 | ## Command list
11 | | Command | Operation|
12 | |------------|----------|
13 | |. | Select AI mode of Bard/ChatGPT/Claude2/Llama2/Falcon180 |
14 | |↑ | Previous input|
15 | |↓ | Next input|
16 | |<< | Start multiple lines input mode|
17 | |>> | End multiple lines input mode|
18 | |j | Scroll down|
19 | |k | Scroll up|
20 | |f | Page down|
21 | |p | Page up|
22 | |g | Scroll to top|
23 | |G | Scroll to bottom|
24 | |q or Enter | Back to conversation|
25 | |.v | Mini `vi` to edit quest, `:ai` send, `:q` cancel|
26 | |.c or .clear| Clear the screen|
27 | |.h or .history | Show history of conversations|
28 | |.r or .restart | Re-start AI model|
29 | |.proxy | Set proxy, for example: socks5://127.0.0.1:7890|
30 | |.help | Show help|
31 | |.exit | Exit Aih|
32 |
33 | ## Prerequisites
34 | - [Chrome Browser](https://google.com/chrome)
35 | - Free account of [Bard](https://bard.google.com), [Claude](https://claude.ai), [OpenAI](https://chat.openai.com), [HuggingChat](https://huggingface.co/chat) logged-in manually on your Chrome browser.
36 | - (Optional) Paid ChatGPT API on [Billing](https://platform.openai.com/account/billing/overview).
37 |
38 | ## Tips
39 | - Close Chrome browser before run Aih at the first time.
40 | - More usage of command [Liner](https://github.com/peterh/liner#line-editing).
41 | - More usage of mini [vi](https://github.com/kiasaki/ry#features).
42 | - Answer will be auotmatically saved in system clipboard for pasting.
43 | - Conversations were persisted in `history.txt` beside Aih binary.
44 | - `./aih -rod=show` to monitor in browser.
45 | - All-In-One mode will display answers from all the AI modes.
46 |
47 |
48 | ## Supported OS
49 | - Mac/Linux/Windows
50 |
51 | ## Installation
52 | ```
53 | $ git clone https://github.com/Databingo/aih
54 | $ go clean -cache && go clean -modcache
55 | $ cd aih/ryy && go mod tidy && go build -o ../vi && cd ..
56 | $ go mod tidy && go build -tags vi
57 | ```
58 | ## Acknowledgements
59 | - github.com/rivo/tview
60 | - github.com/peterh/liner
61 | - github.com/gdamore/tcell/v2
62 | - github.com/atotto/clipboard
63 | - github.com/go-rod/rod
64 | - github.com/go-rod/stealth
65 | - github.com/tidwall/gjson
66 | - github.com/tidwall/sjson
67 | - github.com/manifoldco/promptui
68 | - github.com/sashabaranov/go-openai
69 | - github.com/kiasaki/ry
70 |
71 | ## Todo
72 | - x for disable certain AI mode.
73 | - Change deamon mode to minimal browser mode for passing bot checking of ChatGPT.
74 |
75 | ## License
76 | MIT and "You can choose not to include this MIT license into your copies".
77 |
--------------------------------------------------------------------------------
/ai1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Databingo/aih/31676d2ffa7fd0758f37d8e5b21ea464900fa215/ai1.png
--------------------------------------------------------------------------------
/aih.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Databingo/aih/31676d2ffa7fd0758f37d8e5b21ea464900fa215/aih.gif
--------------------------------------------------------------------------------
/bard.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "embed"
5 | "fmt"
6 | "github.com/go-rod/rod"
7 | "github.com/go-rod/stealth"
8 | "strings"
9 | "time"
10 | )
11 |
12 |
13 | // Set up client of Bard (Rod version)
14 | var page_bard *rod.Page
15 | var relogin_bard = true
16 | var channel_bard chan string
17 |
18 | func Bard() {
19 | channel_bard = make(chan string)
20 | defer func() {
21 | if err := recover(); err != nil {
22 | relogin_bard = true
23 | }
24 | }()
25 | page_bard = stealth.MustPage(browser)
26 | page_bard.MustNavigate("https://bard.google.com")
27 |
28 | for i := 1; i <= 30; i++ {
29 | //if page_bard.MustHasX("//textarea[@id='mat-input-0']") {
30 | //if page_bard.MustHasX("//rich-textarea[@aria-label='Input for prompt text']") {
31 | if page_bard.MustHasX("//rich-textarea[@enterkeyhint='send']") {
32 | relogin_bard = false
33 | break
34 | }
35 | // Check "I'm not a robot"
36 | info := page_bard.MustInfo()
37 | if strings.HasPrefix(info.URL, "https://google.com/sorry") {
38 | relogin_bard = true
39 | break
40 | }
41 | // Check "Sign in"
42 | if page_bard.MustHasX("//a[contains(text(), 'Sign in')]") {
43 | relogin_bard = true
44 | break
45 | }
46 | // Check "You've been signed out"
47 | if page_bard.MustHasX("//h1[contains(text(), 've been signed out')]") {
48 | relogin_bard = true
49 | break
50 | }
51 |
52 | time.Sleep(time.Second)
53 | }
54 | if relogin_bard == true {
55 | sprint("✘ Bard")
56 | }
57 | if relogin_bard == false {
58 | sprint("✔ Bard")
59 | for {
60 | select {
61 | case question := <-channel_bard:
62 | page_bard.MustActivate()
63 | //page_bard.MustElementX("//rich-textarea[@aria-label='Input for prompt text']").MustWaitVisible().MustInput(question)
64 | page_bard.MustElementX("//rich-textarea[@enterkeyhint='send']").MustWaitVisible().MustInput(question)
65 | page_bard.MustElementX("//button[@mattooltip='Submit']").MustClick()
66 | fmt.Println("Bard generating...", role)
67 | //page_bard.MustActivate()
68 | if role == ".all" {
69 | //fmt.Println("Bard role", role)
70 | channel_bard <- "click_bard"
71 | }
72 | // wait generated icon
73 | var generated_icon_appear = false
74 | var c = 0
75 | for i := 1; i <= 60; i++ {
76 | if page_bard.MustHasX("//img[contains(@src, 'https://www.gstatic.com/lamda/images/sparkle_resting_v2_1ff6f6a71f2d298b1a31.gif')]") {
77 | generated_icon_appear = true
78 | break
79 | }
80 | c = c + 1
81 | if c == 5 {
82 | page_bard.MustActivate()
83 | c = 0
84 | }
85 | time.Sleep(1 * time.Second)
86 | }
87 | if generated_icon_appear == true {
88 | img := page_bard.MustElementX("//img[contains(@src, 'https://www.gstatic.com/lamda/images/sparkle_resting_v2_1ff6f6a71f2d298b1a31.gif')][last()]").MustWaitVisible()
89 | //response := img.MustElementX("parent::div/parent::bard-logo/parent::div/parent::div").MustWaitVisible()
90 | //response := img.MustElementX("parent::div/parent::div/parent::bard-avatar/parent::div/parent::div").MustWaitVisible()
91 | response := img.MustElementX("ancestor::bard-avatar[1]/parent::div/parent::div").MustWaitVisible()
92 | answer := response.MustText()
93 | channel_bard <- answer
94 | } else {
95 | channel_bard <- "✘✘ Bard, Please check the internet connection and verify login status."
96 | relogin_bard = true
97 |
98 | }
99 | }
100 | }
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | cd ryy && go build -o ../vi && cd -
2 | go build -tags vi
3 |
--------------------------------------------------------------------------------
/chatgpt.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "embed"
5 | "fmt"
6 | "github.com/go-rod/rod"
7 | "github.com/go-rod/stealth"
8 | "strings"
9 | "time"
10 | )
11 |
12 | // Set up client of chatgpt (rod version)
13 | var page_chatgpt *rod.Page
14 | var relogin_chatgpt = true
15 | var channel_chatgpt chan string
16 |
17 | func Chatgpt() {
18 | channel_chatgpt = make(chan string)
19 | defer func() {
20 | if err := recover(); err != nil {
21 | relogin_chatgpt = true
22 | }
23 | }()
24 | //page_chatgpt = browser.MustPage("https://chat.openai.com")
25 | page_chatgpt = stealth.MustPage(browser)
26 | page_chatgpt.MustNavigate("https://chat.openai.com")
27 |
28 | channel_chatgpt_tips := make(chan string)
29 | go func() {
30 | for i := 1; i <= 30; i++ {
31 | if page_chatgpt.MustHasX("//div[contains(text(), 'Okay, let')]") {
32 | page_chatgpt.MustElementX("//div[contains(text(), 'Okay, let')]").MustWaitVisible().MustClick()
33 | close(channel_chatgpt_tips)
34 | break
35 | }
36 | if page_chatgpt.MustHasX("//h2[contains(text(), 'Your session has expired')]") {
37 | relogin_chatgpt = true
38 | close(channel_chatgpt_tips)
39 | break
40 | }
41 | time.Sleep(time.Second)
42 | }
43 | }()
44 |
45 | //for i := 1; i <= 30; i++ {
46 | // if page_chatgpt.MustHasX("//div[contains(text(), 'Okay, let')]") {
47 | // page_chatgpt.MustElementX("//div[contains(text(), 'Okay, let')]").MustWaitVisible().MustClick()
48 | // }
49 | // time.Sleep(time.Second)
50 | //}
51 | //for i := 1; i <= 30; i++ {
52 | // if page_chatgpt.MustHasX("//h2[contains(text(), 'Your session has expired')]") {
53 | // relogin_chatgpt = true
54 | // break
55 | // }
56 | // if page_chatgpt.MustHasX("//div[contains(text(), 'Something went wrong. If this issue persists please')]") {
57 | // //fmt.Println("ChatGPT web error")
58 | // //channel_chatgpt <- "✘✘ ChatGPT, Please check the internet connection and verify login status."
59 | // relogin_chatgpt = true
60 | // break
61 | // //page_chatgpt.MustPDF("ChatGPT✘.pdf")
62 | // }
63 | // time.Sleep(time.Second)
64 | //}
65 | for i := 1; i <= 30; i++ {
66 | if page_chatgpt.MustHasX("//textarea[@id='prompt-textarea']") && !page_chatgpt.MustHasX("//h2[contains(text(), 'Your session has expired')]") {
67 | relogin_chatgpt = false
68 | break
69 | }
70 | time.Sleep(time.Second)
71 | }
72 |
73 | if relogin_chatgpt == true {
74 | sprint("✘ ChatGPT")
75 | //page_chatgpt.MustPDF("./tmp/ChatGPT✘.pdf")
76 | }
77 | if relogin_chatgpt == false {
78 | sprint("✔ ChatGPT")
79 | for {
80 | select {
81 | case question := <-channel_chatgpt:
82 | page_chatgpt.MustActivate()
83 | for i := 1; i <= 20; i++ {
84 | if page_chatgpt.MustHasX("//textarea[@id='prompt-textarea']") {
85 | page_chatgpt.MustElementX("//textarea[@id='prompt-textarea']").MustInput(question)
86 | break
87 | }
88 | time.Sleep(1 * time.Second)
89 | }
90 | for i := 1; i <= 20; i++ {
91 | if page_chatgpt.MustHasX("//textarea[@id='prompt-textarea']/..//button") {
92 | page_chatgpt.MustElementX("//textarea[@id='prompt-textarea']/..//button").MustClick()
93 | break
94 | }
95 | time.Sleep(1 * time.Second)
96 | }
97 | fmt.Println("ChatGPT generating...", role)
98 | //page_chatgpt.MustActivate()
99 | if role == ".all" {
100 | channel_chatgpt <- "click_chatgpt"
101 | }
102 |
103 |
104 | //page_chatgpt.MustElementX("//div[contains(text(), 'Stop generating')]")
105 | //page_chatgpt.MustElement("svg path[d='M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2z']")
106 | //fmt.Println("found creating icon")
107 | var regenerate_icon = false
108 |
109 | for i := 1; i <= 60; i++ {
110 | //if page_chatgpt.MustHas("svg path[d='M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15']") {
111 | if page_chatgpt.MustHas("svg path[d='M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2z']") {
112 |
113 | time.Sleep(1 * time.Second)
114 | continue
115 | }
116 |
117 | if page_chatgpt.MustHas("svg path[d='M4.5 2.5C5.05228 2.5 5.5 2.94772 5.5 3.5V5.07196C7.19872 3.47759 9.48483 2.5 12 2.5C17.2467 2.5 21.5 6.75329 21.5 12C21.5 17.2467 17.2467 21.5 12 21.5C7.1307 21.5 3.11828 17.8375 2.565 13.1164C2.50071 12.5679 2.89327 12.0711 3.4418 12.0068C3.99033 11.9425 4.48712 12.3351 4.5514 12.8836C4.98798 16.6089 8.15708 19.5 12 19.5C16.1421 19.5 19.5 16.1421 19.5 12C19.5 7.85786 16.1421 4.5 12 4.5C9.7796 4.5 7.7836 5.46469 6.40954 7H9C9.55228 7 10 7.44772 10 8C10 8.55228 9.55228 9 9 9H4.5C3.96064 9 3.52101 8.57299 3.50073 8.03859C3.49983 8.01771 3.49958 7.99677 3.5 7.9758V3.5C3.5 2.94772 3.94771 2.5 4.5 2.5Z']") {
118 | //page_chatgpt.MustElement("svg path[d='M7 11L12 6L17 11M12 18V7']").MustWaitVisible().MustWaitStable()
119 | if page_chatgpt.MustHas("svg path[d='M7 11L12 6L17 11M12 18V7']") {
120 | regenerate_icon = true
121 | //fmt.Println("wait...")
122 | break
123 | }
124 | }
125 | time.Sleep(1 * time.Second)
126 | }
127 | if regenerate_icon == true {
128 | page_chatgpt.MustActivate()
129 | time.Sleep(3 * time.Second)
130 |
131 | answer := page_chatgpt.MustElementX("(//div[contains(@class, 'w-full text-token-text-primary')])[last()]").MustText()[15:]
132 | //answer := page_chatgpt.MustElementX("(//div[contains(@class, 'group w-full')])[last()]").MustText()[7:]
133 | //answer := page_chatgpt.MustElementX("(//div[contains(@class, 'group final-completion w-full')])[last()]").MustText()[7:]
134 | //answer_div := page_chatgpt.MustElementX("(//div[contains(@class, 'w-full text-token-text-primary')])[last()]").MustWaitStable()
135 | //answer := answer_div.MustText()[15:]
136 |
137 | //response := img.MustElementX("ancestor::bard-avatar[1]/parent::div/parent::div").MustWaitVisible()
138 | //answer := response.MustText()
139 |
140 | if strings.Contains(answer,
141 | "An error occurred. Either the engine you requested does not exist or there was another issue processing your request. If this issue persists please contact us through our help center at help.openai.com.") {
142 | relogin_chatgpt = true
143 | }
144 | channel_chatgpt <- answer
145 | } else {
146 | channel_chatgpt <- "✘✘ ChatGPT, Please check the internet connection and verify login status."
147 | relogin_chatgpt = true
148 | //page_chatgpt.MustPDF("./tmp/ChatGPT✘.pdf")
149 |
150 | }
151 | }
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/claude.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "embed"
5 | "fmt"
6 | "github.com/go-rod/rod"
7 | //"github.com/go-rod/stealth"
8 | "github.com/google/uuid"
9 | "github.com/tidwall/gjson"
10 | "strconv"
11 | "strings"
12 | "time"
13 | )
14 |
15 | var page_claude *rod.Page
16 | var relogin_claude = true
17 | var channel_claude chan string
18 |
19 | func Claude2() {
20 | channel_claude = make(chan string)
21 | defer func() {
22 | if err := recover(); err != nil {
23 | relogin_claude = true
24 | }
25 | }()
26 | page_claude = browser.MustPage("https://claude.ai")
27 | //page_claude = stealth.MustPage(browser)
28 | //page_claude.MustNavigate("https://claude.ai/chats").MustWaitLoad()
29 | for i := 1; i <= 30; i++ {
30 | if page_claude.MustHasX("//div[contains(text(), 'Start a new chat')]") {
31 | time.Sleep(3 * time.Second)
32 | page_claude.MustElementX("//div[contains(text(), 'Start a new chat')]").MustClick()
33 | relogin_claude = false
34 | break
35 | }
36 | time.Sleep(time.Second)
37 | }
38 |
39 |
40 |
41 | org_json := page_claude.MustElementX("//pre").MustText()
42 | org_uuid := gjson.Get(string(org_json), "0.uuid").String()
43 | time.Sleep(6 * time.Second)
44 |
45 | new_uuid := uuid.New().String()
46 | new_uuid_url := "https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations"
47 | create_new_converastion_json := `{"uuid":"` + new_uuid + `","name":""}`
48 | create_new_converastion_js := `
49 | (new_uuid_url, sdata) => {
50 | var xhr = new XMLHttpRequest();
51 | xhr.open("POST", new_uuid_url);
52 | xhr.setRequestHeader('Content-Type', 'application/json');
53 | xhr.setRequestHeader('Referer', 'https://claude.ai/chats');
54 | xhr.setRequestHeader('Origin', 'https://claude.ai');
55 | xhr.setRequestHeader('TE', 'trailers');
56 | xhr.setRequestHeader('DNT', '1');
57 | xhr.setRequestHeader('Connection', 'keep-alive');
58 | xhr.setRequestHeader('Accept', 'text/event-stream, text/event-stream');
59 | xhr.onreadystatechange = function() {
60 | if (xhr.readyState == XMLHttpRequest.DONE) {
61 | var res_text = xhr.responseText;
62 | console.log(res_text);
63 | }
64 | }
65 | console.log(sdata);
66 | xhr.send(sdata);
67 | }
68 | `
69 | page_claude.MustEval(create_new_converastion_js, new_uuid_url, create_new_converastion_json).Str()
70 | // posted new conversation uuid
71 | time.Sleep(3 * time.Second) // delay to simulate human being
72 |
73 | var record_chat_messages string
74 | var response_chat_messages string
75 | for i := 1; i <= 20; i++ {
76 | create_json := page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations/" + new_uuid).MustElementX("//pre").MustText()
77 |
78 | message_uuid := gjson.Get(string(create_json), "uuid").String()
79 | if message_uuid == new_uuid {
80 | //fmt.Println("create_conversation success...")
81 | relogin_claude = false
82 | record_chat_messages = gjson.Get(string(create_json), "chat_messages").String()
83 | break
84 | }
85 | time.Sleep(2 * time.Second)
86 | }
87 |
88 | if relogin_claude == true {
89 | sprint("✘ Claude")
90 | //page_claude.MustPDF("./tmp/Claude✘.pdf")
91 | }
92 | if relogin_claude == false {
93 | sprint("✔ Claude")
94 | for {
95 | select {
96 | case question := <-channel_claude:
97 | question = strings.Replace(question, "\r", "\n", -1)
98 | question = strings.Replace(question, "\"", "\\\"", -1)
99 | question = strings.Replace(question, "\n", "\\n", -1)
100 | question = strings.TrimSuffix(question, "\n")
101 | // re-activate
102 | page_claude.MustNavigate("https://claude.ai/api/account/statsig/" + org_uuid).MustWaitLoad()
103 | record_json := page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations/" + new_uuid).MustElementX("//pre").MustText()
104 | record_chat_messages = gjson.Get(string(record_json), "chat_messages").String()
105 | record_count := gjson.Get(string(response_chat_messages), "#").Int()
106 | page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid).MustWaitLoad()
107 | time.Sleep(3 * time.Second) // delay to simulate human being
108 | //question = strings.Replace(question, `"`, `\"`, -1) // escape " in input text when code into json
109 |
110 | //d := `{"completion":{"prompt":"` + question + `","timezone":"Asia/Shanghai","model":"claude-2"},"organization_uuid":"` + org_uuid + `","conversation_uuid":"` + new_uuid + `","text":"` + question + `","attachments":[]}`
111 | d := `{"prompt":"` + question + `","timezone":"Asia/Bangkok","model":"claude-2.1","attachments":[],"files":[]}`
112 | //fmt.Println(d)
113 | //js := `
114 | // (sdata, new_uuid) => {
115 | // var xhr = new XMLHttpRequest();
116 | // xhr.open("POST", "https://claude.ai/api/append_message");
117 | // xhr.setRequestHeader('Content-Type', 'application/json');
118 | // xhr.setRequestHeader('Referer', 'https://claude.ai/chat/new_uuid');
119 | // xhr.setRequestHeader('Origin', 'https://claude.ai');
120 | // xhr.setRequestHeader('TE', 'trailers');
121 | // xhr.setRequestHeader('Connection', 'keep-alive');
122 | // xhr.setRequestHeader('Accept', 'text/event-stream, text/event-stream');
123 | // xhr.onreadystatechange = function() {
124 | // if (xhr.readyState == XMLHttpRequest.DONE) {
125 | // var res_text = xhr.responseText;
126 | // console.log(res_text);
127 | // }
128 | // }
129 | // console.log(sdata);
130 | // xhr.send(sdata);
131 | // }
132 | // `
133 | js := `
134 | (sdata, org_uuid, new_uuid) => {
135 | var xhr = new XMLHttpRequest();
136 | xhr.open("POST", "https://claude.ai/api/organizations/org_uuid/chat_conversations/new_uuid/completion");
137 | xhr.setRequestHeader('Content-Type', 'application/json');
138 | xhr.setRequestHeader('Referer', 'https://claude.ai/chat/new_uuid');
139 | xhr.setRequestHeader('Origin', 'https://claude.ai');
140 | xhr.setRequestHeader('TE', 'trailers');
141 | xhr.setRequestHeader('Connection', 'keep-alive');
142 | xhr.setRequestHeader('Accept', 'text/event-stream, text/event-stream');
143 | xhr.onreadystatechange = function() {
144 | if (xhr.readyState == XMLHttpRequest.DONE) {
145 | var res_text = xhr.responseText;
146 | console.log(res_text);
147 | }
148 | }
149 | console.log(sdata);
150 | xhr.send(sdata);
151 | }
152 | `
153 | page_claude.MustEval(js, d, org_uuid, new_uuid).Str()
154 | fmt.Println("Claude generating...", role)
155 | if role == ".all" {
156 | channel_claude <- "click_claude"
157 | }
158 | time.Sleep(3 * time.Second) // delay to simulate human being
159 |
160 | // wait answer
161 | var claude_response = false
162 | var response_json string
163 | for i := 1; i <= 20; i++ {
164 | if page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations/" + new_uuid).MustHasX("//pre") {
165 | response_json = page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations/" + new_uuid).MustElementX("//pre").MustText()
166 | response_chat_messages = gjson.Get(string(response_json), "chat_messages").String()
167 | count := gjson.Get(string(response_chat_messages), "#").Int()
168 |
169 | if response_chat_messages != record_chat_messages && count == record_count+2 {
170 | claude_response = true
171 | record_chat_messages = response_chat_messages
172 | break
173 | }
174 | }
175 | time.Sleep(3 * time.Second)
176 | }
177 | if claude_response == true {
178 | count := gjson.Get(string(response_chat_messages), "#").Int()
179 | answer := gjson.Get(string(response_json), "chat_messages.#(index=="+strconv.FormatInt(count-1, 10)+").text").String()
180 | channel_claude <- answer
181 | } else {
182 | channel_claude <- "✘✘ Claude, Please check the internet connection and verify login status."
183 | relogin_claude = true
184 | //page_claude.MustPDF("./tmp/Claude✘.pdf")
185 | }
186 | }
187 | }
188 | }
189 |
190 | }
191 |
--------------------------------------------------------------------------------
/claude.goo:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "embed"
5 | "fmt"
6 | "github.com/go-rod/rod"
7 | "github.com/go-rod/stealth"
8 | "github.com/google/uuid"
9 | "github.com/tidwall/gjson"
10 | "strconv"
11 | "strings"
12 | "time"
13 | )
14 |
15 | var page_claude *rod.Page
16 | var relogin_claude = true
17 | var channel_claude chan string
18 |
19 | func Claude2() {
20 | channel_claude = make(chan string)
21 | defer func() {
22 | if err := recover(); err != nil {
23 | relogin_claude = true
24 | }
25 | }()
26 | //page_claude = browser.MustPage("https://claude.ai")
27 | page_claude = stealth.MustPage(browser)
28 | page_claude.MustNavigate("https://claude.ai/api/organizations").MustWaitLoad()
29 | org_json := page_claude.MustElementX("//pre").MustText()
30 | org_uuid := gjson.Get(string(org_json), "0.uuid").String()
31 | time.Sleep(6 * time.Second)
32 |
33 | new_uuid := uuid.New().String()
34 | new_uuid_url := "https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations"
35 | create_new_converastion_json := `{"uuid":"` + new_uuid + `","name":""}`
36 | create_new_converastion_js := `
37 | (new_uuid_url, sdata) => {
38 | var xhr = new XMLHttpRequest();
39 | xhr.open("POST", new_uuid_url);
40 | xhr.setRequestHeader('Content-Type', 'application/json');
41 | xhr.setRequestHeader('Referer', 'https://claude.ai/chats');
42 | xhr.setRequestHeader('Origin', 'https://claude.ai');
43 | xhr.setRequestHeader('TE', 'trailers');
44 | xhr.setRequestHeader('DNT', '1');
45 | xhr.setRequestHeader('Connection', 'keep-alive');
46 | xhr.setRequestHeader('Accept', 'text/event-stream, text/event-stream');
47 | xhr.onreadystatechange = function() {
48 | if (xhr.readyState == XMLHttpRequest.DONE) {
49 | var res_text = xhr.responseText;
50 | console.log(res_text);
51 | }
52 | }
53 | console.log(sdata);
54 | xhr.send(sdata);
55 | }
56 | `
57 | page_claude.MustEval(create_new_converastion_js, new_uuid_url, create_new_converastion_json).Str()
58 | // posted new conversation uuid
59 | time.Sleep(3 * time.Second) // delay to simulate human being
60 |
61 | var record_chat_messages string
62 | var response_chat_messages string
63 | for i := 1; i <= 20; i++ {
64 | create_json := page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations/" + new_uuid).MustElementX("//pre").MustText()
65 |
66 | message_uuid := gjson.Get(string(create_json), "uuid").String()
67 | if message_uuid == new_uuid {
68 | //fmt.Println("create_conversation success...")
69 | relogin_claude = false
70 | record_chat_messages = gjson.Get(string(create_json), "chat_messages").String()
71 | break
72 | }
73 | time.Sleep(2 * time.Second)
74 | }
75 |
76 | if relogin_claude == true {
77 | sprint("✘ Claude")
78 | //page_claude.MustPDF("./tmp/Claude✘.pdf")
79 | }
80 | if relogin_claude == false {
81 | sprint("✔ Claude")
82 | for {
83 | select {
84 | case question := <-channel_claude:
85 | question = strings.Replace(question, "\r", "\n", -1)
86 | question = strings.Replace(question, "\"", "\\\"", -1)
87 | question = strings.Replace(question, "\n", "\\n", -1)
88 | question = strings.TrimSuffix(question, "\n")
89 | // re-activate
90 | page_claude.MustNavigate("https://claude.ai/api/account/statsig/" + org_uuid).MustWaitLoad()
91 | record_json := page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations/" + new_uuid).MustElementX("//pre").MustText()
92 | record_chat_messages = gjson.Get(string(record_json), "chat_messages").String()
93 | record_count := gjson.Get(string(response_chat_messages), "#").Int()
94 | page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid).MustWaitLoad()
95 | time.Sleep(3 * time.Second) // delay to simulate human being
96 | //question = strings.Replace(question, `"`, `\"`, -1) // escape " in input text when code into json
97 |
98 | //d := `{"completion":{"prompt":"` + question + `","timezone":"Asia/Shanghai","model":"claude-2"},"organization_uuid":"` + org_uuid + `","conversation_uuid":"` + new_uuid + `","text":"` + question + `","attachments":[]}`
99 | d := `{"prompt":"` + question + `","timezone":"Asia/Bangkok","model":"claude-2.1","attachments":[],"files":[]}`
100 | //fmt.Println(d)
101 | //js := `
102 | // (sdata, new_uuid) => {
103 | // var xhr = new XMLHttpRequest();
104 | // xhr.open("POST", "https://claude.ai/api/append_message");
105 | // xhr.setRequestHeader('Content-Type', 'application/json');
106 | // xhr.setRequestHeader('Referer', 'https://claude.ai/chat/new_uuid');
107 | // xhr.setRequestHeader('Origin', 'https://claude.ai');
108 | // xhr.setRequestHeader('TE', 'trailers');
109 | // xhr.setRequestHeader('Connection', 'keep-alive');
110 | // xhr.setRequestHeader('Accept', 'text/event-stream, text/event-stream');
111 | // xhr.onreadystatechange = function() {
112 | // if (xhr.readyState == XMLHttpRequest.DONE) {
113 | // var res_text = xhr.responseText;
114 | // console.log(res_text);
115 | // }
116 | // }
117 | // console.log(sdata);
118 | // xhr.send(sdata);
119 | // }
120 | // `
121 | js := `
122 | (sdata, org_uuid, new_uuid) => {
123 | var xhr = new XMLHttpRequest();
124 | xhr.open("POST", "https://claude.ai/api/organizations/org_uuid/chat_conversations/new_uuid/completion");
125 | xhr.setRequestHeader('Content-Type', 'application/json');
126 | xhr.setRequestHeader('Referer', 'https://claude.ai/chat/new_uuid');
127 | xhr.setRequestHeader('Origin', 'https://claude.ai');
128 | xhr.setRequestHeader('TE', 'trailers');
129 | xhr.setRequestHeader('Connection', 'keep-alive');
130 | xhr.setRequestHeader('Accept', 'text/event-stream, text/event-stream');
131 | xhr.onreadystatechange = function() {
132 | if (xhr.readyState == XMLHttpRequest.DONE) {
133 | var res_text = xhr.responseText;
134 | console.log(res_text);
135 | }
136 | }
137 | console.log(sdata);
138 | xhr.send(sdata);
139 | }
140 | `
141 | page_claude.MustEval(js, d, org_uuid, new_uuid).Str()
142 | fmt.Println("Claude generating...", role)
143 | if role == ".all" {
144 | channel_claude <- "click_claude"
145 | }
146 | time.Sleep(3 * time.Second) // delay to simulate human being
147 |
148 | // wait answer
149 | var claude_response = false
150 | var response_json string
151 | for i := 1; i <= 20; i++ {
152 | if page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations/" + new_uuid).MustHasX("//pre") {
153 | response_json = page_claude.MustNavigate("https://claude.ai/api/organizations/" + org_uuid + "/chat_conversations/" + new_uuid).MustElementX("//pre").MustText()
154 | response_chat_messages = gjson.Get(string(response_json), "chat_messages").String()
155 | count := gjson.Get(string(response_chat_messages), "#").Int()
156 |
157 | if response_chat_messages != record_chat_messages && count == record_count+2 {
158 | claude_response = true
159 | record_chat_messages = response_chat_messages
160 | break
161 | }
162 | }
163 | time.Sleep(3 * time.Second)
164 | }
165 | if claude_response == true {
166 | count := gjson.Get(string(response_chat_messages), "#").Int()
167 | answer := gjson.Get(string(response_json), "chat_messages.#(index=="+strconv.FormatInt(count-1, 10)+").text").String()
168 | channel_claude <- answer
169 | } else {
170 | channel_claude <- "✘✘ Claude, Please check the internet connection and verify login status."
171 | relogin_claude = true
172 | //page_claude.MustPDF("./tmp/Claude✘.pdf")
173 | }
174 | }
175 | }
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/else/README.md-:
--------------------------------------------------------------------------------
1 | # Aih: Talk with Bard/ChatGPT/Claude2/Llama2 in the terminal.
2 |
3 | 
4 |
5 | ## Usage
6 | Download [binary file](https://github.com/Databingo/aih/releases) then type:
7 | ```bash
8 | ./aih
9 | ```
10 | ## Command list
11 | | Command | Operation|
12 | |------------|----------|
13 | |. | Select AI mode of Bard/ChatGPT/Claude/HuggingChat|
14 | |.proxy | Set proxy, for example: socks5://127.0.0.1:7890|
15 | |<< | Start multiple lines input mode|
16 | |>> | End multiple lines input mode|
17 | |↑ | Previous input|
18 | |↓ | Next input|
19 | |.c or .clear| Clear the screen|
20 | |.h or .history | Show history of conversations|
21 | |j | Scroll down|
22 | |k | Scroll up|
23 | |g | Scroll to top|
24 | |G | Scroll to bottom|
25 | |q or Enter | Back to conversation|
26 | |.help | Show help|
27 | |.exit | Exit Aih|
28 |
29 | ## Prerequisites
30 | - [Chrome Browser](https://google.com/chrome)
31 | - Free account of [Bard](https://bard.google.com), [Claude](https://claude.ai), [OpenAI](https://chat.openai.com), [HuggingChat](https://huggingface.co/chat) logged-in manually on your Chrome browser.
32 | - (Optional) Paid ChatGPT API on [Billing](https://platform.openai.com/account/billing/overview).
33 |
34 | ## Tips
35 | - Answer will be auotmatically saved in system clipboard for pasting.
36 | - All conversations were persisted in `history.txt` beside Aih binary.
37 | - More operations of command line at [Liner](https://github.com/peterh/liner#Line-editing).
38 | - All-In-One mode will display answers from all the AI modes.
39 |
40 |
41 | ## Supported Operating Systems:
42 | - Mac
43 | - Linux
44 | - Windows
45 |
46 | ## Installation
47 | - Bash
48 | ```
49 | $ git clone https://github.com/Databingo/aih
50 | $ go clean -cache && go clean -modcache
51 | $ cd aih && go mod tidy && go build
52 | ```
53 | ## About Suggestions
54 | This is an open plan based on the idea of "Co-relation's enhancement of AI and human beings". If you have any suggestions, please write them in the Issues section.
55 |
56 | ## Acknowledgements
57 | - github.com/go-rod/rod
58 | - github.com/sashabaranov/go-openai
59 |
--------------------------------------------------------------------------------
/else/bard.py:
--------------------------------------------------------------------------------
1 | import undetected_chromedriver as uc
2 | #from selenium import webdriver as uc
3 | import random,time,os,sys
4 | from selenium.webdriver.common.keys import Keys
5 | from selenium.webdriver.common.by import By
6 | from selenium.webdriver.support.ui import WebDriverWait
7 | from selenium.webdriver.support import expected_conditions as EC
8 | import json
9 | import sys
10 |
11 | # Restart session
12 | #########################
13 | #driver = uc.Chrome(options=chrome_options, headless=True)
14 | chrome_options = uc.ChromeOptions()
15 | chrome_options.add_argument("--disable-extensions")
16 | chrome_options.add_argument("--disable-popup-blocking")
17 | chrome_options.add_argument("--profile-directory=Default")
18 | chrome_options.add_argument("--ignore-certificate-errors")
19 | chrome_options.add_argument("--disable-plugins-discovery")
20 | chrome_options.add_argument("--incognito")
21 | chrome_options.add_argument("--headless")
22 | chrome_options.add_argument("user_agent=DN")
23 | driver = uc.Chrome(options=chrome_options)
24 |
25 | # Load cookie
26 | driver.get("https://bard.google.com")
27 | with open("./2.json", "r", newline='') as inputdata:
28 | ck = json.load(inputdata)
29 | for c in ck:
30 | driver.add_cookie({k:c[k] for k in {'name', 'value'}})
31 |
32 | # Renew with cookie
33 | driver.get("https://bard.google.com")
34 | wait = WebDriverWait(driver, 20)
35 | try:
36 | work = wait.until(EC.visibility_of_element_located((By.XPATH, "//textarea[@id='mat-input-0']")))
37 | print("login work")
38 | except:
39 | print("relogin")
40 | #open("./2.json", "w").close()
41 | driver.quit()
42 | os.exit()
43 |
44 | wait = WebDriverWait(driver, 30000)
45 | while 1:
46 | ori = input(":")
47 | if ori:
48 | #for line in sys.stdin:
49 | # message = line.strip()
50 | # ori = message.replace("(-:]", " ")
51 | work.send_keys(ori)
52 | driver.find_element(By.XPATH, "//button[@mattooltip='Submit']").click()
53 | #ini_source = driver.page_source
54 | if ori:
55 | try:
56 | img_thinking = wait.until(EC.presence_of_element_located((By.XPATH, "//img[contains(@src, 'https://www.gstatic.com/lamda/images/sparkle_thinking_v2_e272afd4f8d4bbd25efe.gif')]")))
57 | #print("get img_thinking")
58 | img = wait.until(EC.presence_of_element_located((By.XPATH, "//img[contains(@src, 'https://www.gstatic.com/lamda/images/sparkle_resting_v2_1ff6f6a71f2d298b1a31.gif')]")))
59 | #print("get img")
60 | response = img.find_element(By.XPATH, "ancestor::model-response")
61 | #print("get response content img")
62 | google = response.find_element(By.XPATH, ".//button[@aria-label='Google it']")
63 |
64 | contents = response.find_elements(By.XPATH, ".//message-content")
65 | texts= "\n".join(content.text for content in contents)
66 | text = "(-:]".join(line for line in texts.splitlines() if line)
67 |
68 | text = response.text
69 | text = text.replace("\n","(-:]")
70 | text = text.replace("View other drafts","")
71 | text = text.replace("Regenerate draft","")
72 | text = text.replace("thumb_up","")
73 | text = text.replace("thumb_down","")
74 | text = text.replace("upload","")
75 | text = text.replace("Google it","")
76 | text = text.replace("more_vert","")
77 | text = text.replace("volume_up","")
78 | text = "(-:]".join(line for line in text.splitlines() if line)
79 | print(text)
80 | sys.stdout.flush()
81 |
82 | cookies = driver.get_cookies()
83 | with open("./2.json", "w", newline='') as outputdata:
84 | json.dump(cookies, outputdata)
85 |
86 | except Exception as e:
87 | pass
88 |
89 |
--------------------------------------------------------------------------------
/else/chatgpt.py:
--------------------------------------------------------------------------------
1 | import undetected_chromedriver as uc
2 | import random,time,os,sys
3 | from selenium.webdriver.common.keys import Keys
4 | from selenium.webdriver.common.by import By
5 | from selenium.webdriver.support.ui import WebDriverWait
6 | from selenium.webdriver.support import expected_conditions as EC
7 | import json
8 | import sys
9 |
10 | # Restart session
11 | #########################
12 | #driver = uc.Chrome(options=chrome_options, headless=True)
13 | chrome_options = uc.ChromeOptions()
14 | chrome_options.add_argument("--disable-extensions")
15 | chrome_options.add_argument("--disable-popup-blocking")
16 | chrome_options.add_argument("--profile-directory=Default")
17 | chrome_options.add_argument("--ignore-certificate-errors")
18 | chrome_options.add_argument("--disable-plugins-discovery")
19 | chrome_options.add_argument("--incognito")
20 | chrome_options.add_argument("--headless")
21 | chrome_options.add_argument("user_agent=DN")
22 | driver = uc.Chrome(options=chrome_options)
23 |
24 | # Load cookie
25 | driver.get("https://chat.openai.com")
26 | with open("./4.json", "r", newline='') as inputdata:
27 | ck = json.load(inputdata)
28 | for c in ck:
29 | driver.add_cookie({k:c[k] for k in {'name', 'value'}})
30 |
31 | # Renew with cookie
32 | driver.get("https://chat.openai.com")
33 | wait = WebDriverWait(driver, 200)
34 | try:
35 | notice1 = wait.until(EC.visibility_of_element_located((By.XPATH, "//h4[contains(text(), 'This is a free research preview.')]")))
36 | #print("notice1")
37 | next1 = wait.until(EC.visibility_of_element_located((By.XPATH, "//button/div[contains(text(), 'Next')]")))
38 | #print("next1")
39 | next1.click()
40 | #print("next1.click")
41 | notice2 = wait.until(EC.visibility_of_element_located((By.XPATH, "//h4[contains(text(), 'How we collect data')]")))
42 | #print("notice2")
43 | next2 = wait.until(EC.visibility_of_element_located((By.XPATH, "//button/div[contains(text(), 'Next')]")))
44 | #print("next2")
45 | next2.click()
46 | #print("next2.click")
47 | notice3 = wait.until(EC.visibility_of_element_located((By.XPATH, "//h4[contains(text(), 'love your feedback!')]")))
48 | #print("notice3")
49 | next3 = wait.until(EC.visibility_of_element_located((By.XPATH, "//button/div[contains(text(), 'Done')]")))
50 | #print("next3")
51 | next3.click()
52 | #print("next3.click")
53 | driver.find_element(By.XPATH, "//a[contains(text(), 'New chat')]").click()
54 | input_space = wait.until(EC.visibility_of_element_located((By.XPATH, "//textarea[@id='prompt-textarea']")))
55 | #print("login work")
56 | except:
57 | print("relogin")
58 | #open("./2.json", "w").close()
59 | driver.quit()
60 | os.exit()
61 |
62 | while 1:
63 | ori = input(":")
64 | if ori:
65 | #for line in sys.stdin:
66 | # message = line.strip()
67 | # ori = message.replace("(-:]", " ")
68 | input_space.send_keys(ori)
69 | driver.find_element(By.XPATH, "//button//svg:path[@d='M.5 1.163A1 1 0 0 1 1.97.28l12.868 6.837a1 1 0 0 1 0 1.766L1.969 15.72A1 1 0 0 1 .5 14.836V10.33a1 1 0 0 1 .816-.983L8.5 8 1.316 6.653A1 1 0 0 1 .5 5.67V1.163Z']").click()
70 | #ini_source = driver.page_source
71 | if ori:
72 | try:
73 | retry_icon = wait.until(EC.presence_of_element_located((By.XPATH, "//svg:path[@d='M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15']")))
74 | #print("get retry_icon")
75 | content = retry_icon.find_element(By.XPATH, "(//div[contains(@class, 'group w-full')])[last()]")
76 | text = content.get_attribute("textContent")
77 | text = text.replace("ChatGPTChatGPT","")
78 | text = text.replace("1 / 1","")
79 | text = text.replace("\n","(-:]")
80 | print(text)
81 | sys.stdout.flush()
82 | cookies = driver.get_cookies()
83 | with open("./4.json", "w", newline='') as outputdata:
84 | json.dump(cookies, outputdata)
85 |
86 | except Exception as e:
87 | pass
88 |
89 |
90 |
--------------------------------------------------------------------------------
/else/claude2.py:
--------------------------------------------------------------------------------
1 | import undetected_chromedriver as uc
2 | import random,time,os,sys
3 | from selenium.webdriver.common.keys import Keys
4 | from selenium.webdriver.common.by import By
5 | from selenium.webdriver.support.ui import WebDriverWait
6 | from selenium.webdriver.support import expected_conditions as EC
7 | import json
8 | import sys
9 |
10 | # Restart session
11 | #########################
12 | #driver = uc.Chrome(options=chrome_options, headless=True)
13 | chrome_options = uc.ChromeOptions()
14 | chrome_options.add_argument("--disable-extensions")
15 | chrome_options.add_argument("--disable-popup-blocking")
16 | chrome_options.add_argument("--profile-directory=Default")
17 | chrome_options.add_argument("--ignore-certificate-errors")
18 | chrome_options.add_argument("--disable-plugins-discovery")
19 | chrome_options.add_argument("--incognito")
20 | #chrome_options.add_argument("--headless")
21 | chrome_options.add_argument("user_agent=DN")
22 | driver = uc.Chrome(options=chrome_options)
23 |
24 | driver.get("https://claude.ai")
25 |
26 | # Load cookie
27 | with open("./3.json", "r", newline='') as inputdata:
28 | ck = json.load(inputdata)
29 | for c in ck:
30 | driver.add_cookie({k:c[k] for k in {'name', 'value'}})
31 |
32 | # Renew with cookie
33 | driver.get("https://claude.ai")
34 | wait = WebDriverWait(driver, 200)
35 | try:
36 | work = wait.until(EC.visibility_of_element_located((By.XPATH, "//p[@data-placeholder='Message Claude or search past chats...']")))
37 | #driver.find_element(By.XPATH, "//button[@class='sc-dAOort']").click()
38 | driver.find_element(By.XPATH, "//div[contains(text(), 'Start a new chat')]").click()
39 | input_space = wait.until(EC.visibility_of_element_located((By.XPATH, "//p[@data-placeholder='Message Claude...']")))
40 | print("login work")
41 | except:
42 | print("relogin")
43 | #open("./3.json", "w").close()
44 | driver.quit()
45 | os.exit()
46 |
47 |
48 | while 1:
49 | ori = input(":")
50 | if ori:
51 | #for line in sys.stdin:
52 | # message = line.strip()
53 | # ori = message.replace("(-:]", " ")
54 | input_space.send_keys(ori)
55 | driver.find_element(By.XPATH, "//button[@aria-label='Send Message']").click()
56 | if ori:
57 | try:
58 | retry_icon = wait.until(EC.presence_of_element_located((By.XPATH, "//svg:path[@d= 'M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z']")))
59 | #print("get last retry_icon")
60 | content = retry_icon.find_element(By.XPATH, "preceding::div[2]")
61 | text = content.get_attribute("textContent")
62 | text = text.replace("\n","(-:]")
63 | print(text)
64 | sys.stdout.flush()
65 |
66 | # Save cookie
67 | cookies = driver.get_cookies()
68 | with open("./3.json", "w", newline='') as outputdata:
69 | json.dump(cookies, outputdata)
70 |
71 | except Exception as e:
72 | pass
73 |
74 |
75 |
--------------------------------------------------------------------------------
/else/eng/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/Databingo/aih/eng
2 |
3 | go 1.19
4 |
5 | require github.com/chromedp/chromedp v0.9.1
6 |
7 | require (
8 | github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9 // indirect
9 | github.com/chromedp/sysutil v1.0.0 // indirect
10 | github.com/gobwas/httphead v0.1.0 // indirect
11 | github.com/gobwas/pool v0.2.1 // indirect
12 | github.com/gobwas/ws v1.1.0 // indirect
13 | github.com/josharian/intern v1.0.0 // indirect
14 | github.com/mailru/easyjson v0.7.7 // indirect
15 | golang.org/x/sys v0.6.0 // indirect
16 | )
17 |
--------------------------------------------------------------------------------
/else/eng/go.sum:
--------------------------------------------------------------------------------
1 | github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9 h1:wMSvdj3BswqfQOXp2R1bJOAE7xIQLt2dlMQDMf836VY=
2 | github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
3 | github.com/chromedp/chromedp v0.9.1 h1:CC7cC5p1BeLiiS2gfNNPwp3OaUxtRMBjfiw3E3k6dFA=
4 | github.com/chromedp/chromedp v0.9.1/go.mod h1:DUgZWRvYoEfgi66CgZ/9Yv+psgi+Sksy5DTScENWjaQ=
5 | github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
6 | github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
7 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
8 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
9 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
10 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
11 | github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
12 | github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
13 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
14 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
15 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
16 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
17 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
18 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
19 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
20 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
21 | golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
22 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
23 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
24 |
--------------------------------------------------------------------------------
/else/eng/playphrase.go:
--------------------------------------------------------------------------------
1 | package eng
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/chromedp/cdproto/cdp"
7 | "github.com/chromedp/cdproto/input"
8 | "github.com/chromedp/chromedp"
9 | "log"
10 | "strings"
11 | "time"
12 | )
13 |
14 | func Play(words []string) {
15 | ctx, cancel := chromedp.NewContext(context.Background())
16 | defer cancel()
17 |
18 | // Set up Chrome options
19 | opts := append(chromedp.DefaultExecAllocatorOptions[:],
20 | chromedp.Flag("headless", false),
21 | // chromedp.Flag("disable-gpu", true),
22 | // chromedp.Flag("no-sandbox", true),
23 | // chromedp.Flag("disable-infobars", true),
24 | // chromedp.Flag("disable-extensions", true),
25 | // chromedp.Flag("disable-web-security", true),
26 | // chromedp.Flag("mute-audio", true),
27 | chromedp.Flag("mute-audio", false),
28 | )
29 | allocCtx, cancel := chromedp.NewExecAllocator(ctx, opts...)
30 | defer cancel()
31 |
32 | // Create a WebDriver object for Chrome browser
33 | ctx, cancel = chromedp.NewContext(allocCtx)
34 | defer cancel()
35 |
36 | // Maximize the window for keep the stream alive
37 | // This function has been removed as it returns an error, and it is not being used anywhere else
38 | // if err := chromedp.Run(ctx, chromedp.MaximizeWindow()); err != nil {
39 | // log.Fatal(err)
40 | // }
41 |
42 | if err := chromedp.Run(ctx, chromedp.Navigate("https://playphrase.me/")); err != nil {
43 | fmt.Println(err)
44 | }
45 | // Wait for Play icon
46 | if err := chromedp.Run(ctx, chromedp.WaitVisible(`//i[@class="material-icons-outlined"]`, chromedp.BySearch)); err != nil {
47 | fmt.Println(err)
48 | }
49 | if err := chromedp.Run(ctx, chromedp.Click("//body", chromedp.BySearch)); err != nil {
50 | fmt.Println(err)
51 | }
52 |
53 | search_bar := "#search-input"
54 |
55 | for _, phrase := range words {
56 |
57 | for {
58 | var value string
59 | if err := chromedp.Run(ctx, chromedp.Value(search_bar, &value)); err != nil {
60 | log.Fatal(err)
61 | }
62 | if value == phrase {
63 | break
64 | }
65 | if err := chromedp.Run(ctx, chromedp.Click(`//i[contains(text(),"close")]`, chromedp.BySearch)); err != nil {
66 | log.Fatal(err)
67 | }
68 | // SendKeys not work well with wrong characters.
69 | // if err := chromedp.Run(ctx, chromedp.SendKeys(search_bar, phrase)); err != nil {
70 | // log.Fatal(err)
71 | // }
72 | var nodes []*cdp.Node
73 | _ = chromedp.Run(ctx, chromedp.Nodes("#search-input", &nodes, chromedp.ByQuery))
74 | _ = chromedp.Run(ctx, chromedp.MouseClickNode(nodes[0]))
75 | _ = chromedp.Run(ctx, input.InsertText(phrase))
76 |
77 | }
78 |
79 | // Check if the search result count is "1/0"
80 | if err := chromedp.Run(ctx, chromedp.Sleep(5*time.Second)); err != nil {
81 | log.Fatal(err)
82 | }
83 | var search_result_count string
84 | if err := chromedp.Run(ctx, chromedp.Text("li div.search-result-count", &search_result_count)); err != nil {
85 | log.Fatal(err)
86 | }
87 | if search_result_count == "1/0" {
88 | //fmt.Println("Find nothing. Next!")
89 | continue
90 | }
91 |
92 | ch := make(chan bool)
93 | go func() {
94 | // Check if the page source contains the message "If you are not a sponsor you have a limit on our site."
95 | for {
96 | var content string
97 | if err := chromedp.Run(ctx, chromedp.InnerHTML("body", &content)); err != nil {
98 | log.Fatal(err)
99 | }
100 | if strings.Contains(content, "If you are not a sponsor you have a limit on our site.") {
101 | //fmt.Println("Played 5 already. Next!")
102 | close(ch)
103 | break
104 | }
105 | }
106 |
107 | }()
108 |
109 | <-ch
110 |
111 | }
112 |
113 | // Close the Chrome browser
114 | // Shutdown() function is not present in chromedp.Context. Using Stop() function instead.
115 | if err := chromedp.Run(ctx, chromedp.Stop()); err != nil {
116 | log.Fatal(err)
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/else/huggingchat.py:
--------------------------------------------------------------------------------
1 | import undetected_chromedriver as uc
2 | import random,time,os,sys
3 | from selenium.webdriver.common.keys import Keys
4 | from selenium.webdriver.common.by import By
5 | from selenium.webdriver.support.ui import WebDriverWait
6 | from selenium.webdriver.support import expected_conditions as EC
7 | import json
8 | import sys
9 |
10 | #########################
11 | chrome_options = uc.ChromeOptions()
12 | chrome_options.add_argument("--disable-extensions")
13 | chrome_options.add_argument("--disable-popup-blocking")
14 | chrome_options.add_argument("--profile-directory=Default")
15 | chrome_options.add_argument("--ignore-certificate-errors")
16 | chrome_options.add_argument("--disable-plugins-discovery")
17 | chrome_options.add_argument("--incognito")
18 | #chrome_options.add_argument("--headless")
19 | #chrome_options.add_argument("user_agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/604.1 Edg/115.0.100.0")
20 | chrome_options.add_argument("user_agent=DN")
21 | driver = uc.Chrome(options=chrome_options)
22 |
23 | driver.get("https://huggingface.co/chat")
24 | with open("./5.json", "r", newline='') as inputdata:
25 | ck = json.load(inputdata)
26 | for c in ck:
27 | driver.add_cookie({k:c[k] for k in {'name', 'value'}})
28 |
29 | # Renew with cookie
30 | driver.get("https://huggingface.co/chat")
31 | wait = WebDriverWait(driver, 200)
32 | try:
33 | input_space = wait.until(EC.visibility_of_element_located((By.XPATH, "//textarea[@enterkeyhint='send']")))
34 | print("login work")
35 | except:
36 | print("relogin")
37 | driver.quit()
38 | os.exit()
39 |
40 | while 1:
41 | ori = input(":")
42 | if ori:
43 | #for line in sys.stdin:
44 | # message = line.strip()
45 | # ori = message.replace("(-:]", " ")
46 | input_space = wait.until(EC.visibility_of_element_located((By.XPATH, "//textarea[@enterkeyhint='send']")))
47 | input_space.send_keys(ori)
48 | driver.find_element(By.XPATH, "//button//svg:path[@d='M27.71 4.29a1 1 0 0 0-1.05-.23l-22 8a1 1 0 0 0 0 1.87l8.59 3.43L19.59 11L21 12.41l-6.37 6.37l3.44 8.59A1 1 0 0 0 19 28a1 1 0 0 0 .92-.66l8-22a1 1 0 0 0-.21-1.05Z']").click()
49 | if ori:
50 | try:
51 | stop_icon = wait.until(EC.presence_of_element_located((By.XPATH, "//svg:path[@d='M24 6H8a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2Z']")))
52 | #print("get stop_icon")
53 | wait.until(EC.staleness_of(stop_icon))
54 | #print("disappear stop_icon")
55 | img = driver.find_element(By.XPATH, "(//img[contains(@src, 'https://huggingface.co/avatars/2edb18bd0206c16b433841a47f53fa8e.svg')])[last()]")
56 | #print("img")
57 | content = img.find_element(By.XPATH, "following-sibling::div[1]")
58 | text = content.get_attribute("textContent")
59 | text = text.replace("\n","(-:]")
60 | print(text)
61 | sys.stdout.flush()
62 | cookies = driver.get_cookies()
63 | with open("./5.json", "w", newline='') as outputdata:
64 | json.dump(cookies, outputdata)
65 |
66 | except Exception as e:
67 | pass
68 |
69 |
70 |
--------------------------------------------------------------------------------
/else/thought.txt:
--------------------------------------------------------------------------------
1 |
2 | English: AI Research Artifact
3 | Japanese: AI 研究の神器
4 | Korean: AI 연구 슈퍼 도구
5 | French: L'artefact de recherche AI
6 | Spanish: El artefacto de investigación de IA
7 | German: Das AI-Forschungsartefakt
8 | Russian: Артефакт AI-исследования
9 | Portuguese: Artefato de Pesquisa de IA
10 | Italian: Artefatto di ricerca AI
11 | Hindi: एआई शोध का सुपर टूल
12 | Arabic: قطعة أثرية لبحث الذكاء الاصطناعي
13 | Vietnamese: Siêu công cụ nghiên cứu AI
14 | Thai: เครื่องมือวิจัย AI ยอดเยี่ยม
15 | Indonesian: Super alat penelitian AI
16 | Malay: Super alat penyelidikan AI
17 | Finnish: Supertyökalu tekoälyn tutkimukseen
18 | Swedish: Superverktyg för AI-forskning
19 | Dutch: Superhulpmiddel voor AI-onderzoek
20 | Romanian: Super unealtă de cercetare în domeniul inteligenței artificiale
21 | Greek: Υπερ εργαλείο έρευνας τεχνητής νοημοσύνης
22 |
23 | Indonesian:
24 | Portuguese:
25 | Dutch:
26 | Polish:
27 | Hungarian:
28 | Zulu:
29 |
30 | | 研究の神器
31 | | 연구 슈퍼 도구
32 | | L'artefact de recherche
33 | | El artefacto de investigación de
34 | | Das Forschungsartefakt
35 | | Артефакт исследования
36 | | Artefato de Pesquisa de
37 | | Artefatto di ricerca
38 | | एआई शोध का सुपर टूल
39 | | قطعة أثرية لبحث الذكاء الاصطناعي
40 | | เครื่องมือวิจัย ยอดเยี่ยม
41 | | Supertyökalu tekoälyn tutkimukseen
42 | | Superverktyg för forskning
43 | | Superhulpmiddel voor onderzoek
44 | | Υπερ εργαλείο έρευνας τεχνητής νοημοσύνης
45 |
46 | | 科研神器 | 연구 슈퍼 도구 | L'artefact de recherche | El artefacto de investigación de | Das Forschungsartefakt | Артефакт исследования | Artefato de Pesquisa de | Artefatto di ricerca | एआई शोध का सुपर टूल | قطعة أثرية لبحث الذكاء الاصطناعي | เครื่องมือวิจัย ยอดเยี่ยม | Google | API | CLI | VIM |
47 |
48 |
49 |
50 | https://huggingface.co/chat/__data.json?x-sveltekit-invalidated=1_ conversation list
51 | https://huggingface.co/chat/conversation/64f30169c498fd14c9f41469 404|could DELETE
52 | https://huggingface.co/chat/conversation/64f30169c498fd14c9f41469/web-search?
53 |
54 |
55 | TODO:
56 | yaml for histoy
57 | //headless false to get cookie? NO
58 | check chatGPT verify mask? Difficult
59 | //color setting of vi for symbols (now is black can't see in black background)
60 | //restart for faulse AI model?
61 | //escapt %v out of %!s(MISSING): on webpage Done
62 | // add Falcon Done
63 | 1.one file for all notes
64 | 2.one file for all pass
65 | //3.hasX stop then hasX submit
66 | // Hard to insert check 4.internet error of llama2/falcon180
67 | //Done 5.merger proxies-groups
68 | //6.optimise yaml model in merger
69 | //7.sell books
70 | //8.select cloth/shoes to sell
71 | //9.prepare gift to guards wine?
72 | 10.prompt engineering/sell? deeper?
73 | 11.aih prompt select/create/save function
74 | 12.group for prompt engineering?
75 | //13.tester 30 parameter of cli
76 | 15.aih .i instruction list to vi mode?
77 |
78 | ----------- universal_primer ---------
79 | I want you, ChatGPT, to select the most suitable and knowledgeable person or expert/team for the question I am going to ask you.
80 | This role/team possesses extensive knowledge and expertise in the subject matter, with access to a comprehensive knowledge graph.
81 | They also provide both a detailed and a big-picture understanding of the subject.
82 | You will adhere to the role or team as long as necessary and choose another if it goes off-topic.
83 |
84 | Commands:
85 | "/og" --> utilize the previously selected role to answer the query.
86 | "/re" --> reevaluate your last answer and if necessary correct yourself in a numbered summary.
87 |
88 | Localisation: CH-EN
89 | Units: Metric
90 |
91 | Example 1:
92 | OP: I want to analyse GPS Tracking data in a Flask Application.
93 | ChatGPT: Analyzing Topics and Roles...
94 | Topic 1: Analysing GPS Tracking Data, role: Data Analyst + GIS-Specialist
95 | Topic 2: Flask Application Development, role: Backend Developer + Frontend Developer + UX Designer
96 | [Team lead after collecting feedback from roles]: Sure, my Team has a few open Questions, let's begin:
97 |
98 | 1. What do you want to accomplish?
99 | X. (other unclarified points)]
100 |
101 | Abstract:
102 | 1. Create topic list based on query.
103 | 2. Select role/Team based on Topics.
104 | 3. Answer query from the role/Team perspective, clarify missing information if necessary.
105 |
106 | Task Summary: You will select an overqualified role or team with a team lead based on the question, which will provide the highest expertise, and then answer the question from the perspective of that role or team lead.
107 | If some key details or other 'could be relevant' details are not defined, please ask for clarifications (measurements, quantities, how much).
108 | You are not assuming anything, instead [you|the team] will seek out the missing information.
109 | You are fluent in all languages.
110 | Provide concise, straight-to-the-point responses.
111 | Use step-by-step thinking.
112 | Adopt fitting personalities (e.g., if you are a Data Analyst, be more precise and structured; if you are a UX Designer, be more creative and open-minded).
113 | You must select a role/Team, and will inform other roles/Teams if you need help answering the user queries.
114 |
115 | Begin each thread with "Analyzing Topic... (…), Selected [role|TEAM with TEAMLEAD]", then proceed to answer as [role|TEAMLEAD]:
116 |
117 | Confirm instructions with an short and precise "Ok".
118 |
--------------------------------------------------------------------------------
/falcon180.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "embed"
5 | "fmt"
6 | "github.com/go-rod/rod"
7 | "github.com/go-rod/stealth"
8 | "time"
9 | )
10 |
11 | // Set up client of falcon180-180B (Rod version)
12 | var page_falcon180 *rod.Page
13 | var relogin_falcon180 = true
14 | var channel_falcon180 chan string
15 |
16 | func Falcon180() {
17 | channel_falcon180 = make(chan string)
18 | defer func() {
19 | if err := recover(); err != nil {
20 | relogin_falcon180 = true
21 | }
22 | }()
23 | //page_hc = browser.MustPage("https://huggingface.co/chat")
24 | page_falcon180 = stealth.MustPage(browser)
25 | //page_falcon180.MustNavigate("https://huggingface.co/spaces/tiiuae/falcon-180b-demo")
26 | page_falcon180.MustNavigate("https://tiiuae-falcon-180b-demo.hf.space/?__theme=light")
27 | for i := 1; i <= 30; i++ {
28 | if page_falcon180.MustHasX("//textarea[@data-testid='textbox']") {
29 | relogin_falcon180 = false
30 | break
31 | }
32 | time.Sleep(time.Second)
33 | }
34 | if relogin_falcon180 == true {
35 | sprint("✘ Falcon180")
36 | //page_hc.MustPDF("./tmp/HuggingChat✘.pdf")
37 | }
38 | if relogin_falcon180 == false {
39 | sprint("✔ Falcon180")
40 | for {
41 | select {
42 | case question := <-channel_falcon180:
43 | //page_falcon180.MustActivate()
44 | //fmt.Println("Falcon180 received question...", question)
45 | for i := 1; i <= 20; i++ {
46 | if page_falcon180.MustHasX("//textarea[@data-testid='textbox']") {
47 | page_falcon180.MustElementX("//textarea[@data-testid='textbox']").MustInput(question)
48 | break
49 | }
50 | time.Sleep(time.Second)
51 | }
52 | //fmt.Println("Falcon180 input typed...")
53 | for i := 1; i <= 20; i++ {
54 | //if page_falcon180.MustHasX("//button[contains(text(), 'Submit')]") {
55 | if page_falcon180.MustHasX("//button[@id='component-17']") {
56 | page_falcon180.MustElementX("//button[@id='component-17']").MustClick()
57 | break
58 | }
59 | time.Sleep(time.Second)
60 | }
61 | fmt.Println("Falcon180 generating...")
62 | //page_falcon180.MustActivate() // Sometime three dot to hang
63 | //if role == ".all" {
64 | // channel_falcon180 <- "click_falcon180"
65 | //}
66 | //// Check Error
67 | //channel_falcon180_check := make(chan string)
68 | //go func() {
69 | // for i := 1; i <= 20; i++ {
70 | // if page_falcon180.MustHasX("//*[contains(text(), 'Too much traffic, please try again')]") {
71 | // channel_falcon180 <- "✘✘ Falcon180, Please check the internet connection and verify login status. Traffic."
72 | // fmt.Println("Falcon180 too much traffic...")
73 | // relogin_falcon180 = true
74 | // close(channel_falcon180_check)
75 | // break
76 | // }
77 | // time.Sleep(1 * time.Second)
78 | // }
79 | //}()
80 |
81 | // stop_icon
82 | var stop_icon_disappear = false
83 | err := rod.Try(func() {
84 | page_falcon180.Timeout(10 * time.Second).MustElementX("//button[@id='component-18']").MustWaitVisible().CancelTimeout()
85 | })
86 | if err == nil {
87 | err = rod.Try(func() {
88 | page_falcon180.Timeout(80 * time.Second).MustElementX("//button[@id='component-18']").MustWaitInvisible().CancelTimeout()
89 | })
90 | if err == nil {
91 | stop_icon_disappear = true
92 | } else {
93 | //fmt.Println("err::::", err)
94 | }
95 | } else {
96 | //fmt.Println("err::", err)
97 | }
98 |
99 | if stop_icon_disappear == true {
100 | answer := page_falcon180.MustElementX("(//div[@data-testid='bot'])[last()]").MustText()
101 | channel_falcon180 <- answer
102 | } else {
103 | channel_falcon180 <- "✘✘ Falcon180, Please check the internet connection and verify login status."
104 | relogin_falcon180 = true
105 | //page_hc.MustPDF("./tmp/HuggingChat✘.pdf")
106 |
107 | }
108 | }
109 | }
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module aih
2 |
3 | go 1.19
4 |
5 | require (
6 | //github.com/Databingo/aih/ry v0.0.0-bef1172
7 | github.com/atotto/clipboard v0.1.4
8 | github.com/creack/pty v1.1.18
9 | // github.com/gdamore/tcell/v2 v2.6.1-0.20230827060410-08c7757cd1c9
10 | github.com/go-rod/rod v0.114.5
11 | github.com/go-rod/stealth v0.4.9
12 | github.com/google/go-github v17.0.0+incompatible
13 | github.com/google/uuid v1.3.1
14 | github.com/manifoldco/promptui v0.9.0
15 | github.com/peterh/liner v1.2.2
16 | github.com/rivo/tview v0.0.0-20230511053024-822bd067b165
17 | github.com/sashabaranov/go-openai v1.9.0
18 | github.com/tidwall/gjson v1.14.4
19 | github.com/tidwall/sjson v1.2.5
20 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
21 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
22 | jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
23 | )
24 |
25 | // replace github.com/Databingo/aih/ry => ./ry
26 |
27 | require github.com/gdamore/tcell/v2 v2.6.0
28 |
29 | require (
30 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
31 | github.com/gdamore/encoding v1.0.0 // indirect
32 | github.com/golang/protobuf v1.5.3 // indirect
33 | github.com/google/go-cmp v0.5.7 // indirect
34 | github.com/google/go-querystring v1.1.0 // indirect
35 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
36 | github.com/mattn/go-runewidth v0.0.14 // indirect
37 | github.com/olekukonko/tablewriter v0.0.5 // indirect
38 | github.com/rivo/uniseg v0.4.4 // indirect
39 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
40 | github.com/tidwall/match v1.1.1 // indirect
41 | github.com/tidwall/pretty v1.2.0 // indirect
42 | github.com/ysmood/fetchup v0.2.3 // indirect
43 | github.com/ysmood/goob v0.4.0 // indirect
44 | github.com/ysmood/got v0.34.1 // indirect
45 | github.com/ysmood/gson v0.7.3 // indirect
46 | github.com/ysmood/leakless v0.8.0 // indirect
47 | golang.org/x/net v0.9.0 // indirect
48 | golang.org/x/sys v0.11.0 // indirect
49 | golang.org/x/term v0.9.0 // indirect
50 | golang.org/x/text v0.12.0 // indirect
51 | google.golang.org/appengine v1.6.6 // indirect
52 | google.golang.org/protobuf v1.30.0 // indirect
53 | )
54 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
3 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
4 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
5 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
6 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
7 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
8 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
9 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
10 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
11 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
12 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
13 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
14 | github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
15 | github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
16 | github.com/go-rod/rod v0.113.0/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
17 | github.com/go-rod/rod v0.114.5 h1:1x6oqnslwFVuXJbJifgxspJUd3O4ntaGhRLHt+4Er9c=
18 | github.com/go-rod/rod v0.114.5/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
19 | github.com/go-rod/stealth v0.4.9 h1:X2PmQk4DUF2wzw6GOsWjW/glb8K5ebnftbEvLh7MlZ4=
20 | github.com/go-rod/stealth v0.4.9/go.mod h1:eAzyvw8c0iAd5nJJsSWeh0fQ5z94vCIfdi1hUmYDimc=
21 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
22 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
23 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
24 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
25 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
26 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
27 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
28 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
29 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
30 | github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
31 | github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
32 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
33 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
34 | github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
35 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
36 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
37 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
38 | github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
39 | github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
40 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
41 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
42 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
43 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
44 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
45 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
46 | github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
47 | github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
48 | github.com/rivo/tview v0.0.0-20230511053024-822bd067b165 h1:YMycYmUdmLI7ZTn86HUEDM8E8fCMz7twtysBW3SlG0c=
49 | github.com/rivo/tview v0.0.0-20230511053024-822bd067b165/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE=
50 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
51 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
52 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
53 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
54 | github.com/sashabaranov/go-openai v1.9.0 h1:NoiO++IISxxJ1pRc0n7uZvMGMake0G+FJ1XPwXtprsA=
55 | github.com/sashabaranov/go-openai v1.9.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
56 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
57 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
58 | github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
59 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
60 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
61 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
62 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
63 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
64 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
65 | github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
66 | github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
67 | github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
68 | github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
69 | github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
70 | github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
71 | github.com/ysmood/gop v0.0.2 h1:VuWweTmXK+zedLqYufJdh3PlxDNBOfFHjIZlPT2T5nw=
72 | github.com/ysmood/gop v0.0.2/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
73 | github.com/ysmood/got v0.34.1 h1:IrV2uWLs45VXNvZqhJ6g2nIhY+pgIG1CUoOcqfXFl1s=
74 | github.com/ysmood/got v0.34.1/go.mod h1:yddyjq/PmAf08RMLSwDjPyCvHvYed+WjHnQxpH851LM=
75 | github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=
76 | github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
77 | github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
78 | github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
79 | github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak=
80 | github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
81 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
82 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
83 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
84 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
85 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
86 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
87 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
88 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
89 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
90 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
91 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
92 | golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
93 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
94 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
95 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
96 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
97 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
98 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
99 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
100 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
101 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
102 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
103 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
104 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
105 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
106 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
107 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
108 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
109 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
110 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
111 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
112 | golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
113 | golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
114 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
115 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
116 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
117 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
118 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
119 | golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
120 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
121 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
122 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
123 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
124 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
125 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
126 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
127 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
128 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
129 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
130 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
131 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
132 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
133 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
134 | jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 h1:6YFJoB+0fUH6X3xU/G2tQqCYg+PkGtnZ5nMR5rpw72g=
135 | jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:OxvTsCwKosqQ1q7B+8FwXqg4rKZ/UG9dUW+g/VL2xH4=
136 |
--------------------------------------------------------------------------------
/llama2.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "embed"
5 | "fmt"
6 | "github.com/go-rod/rod"
7 | "github.com/go-rod/stealth"
8 | "time"
9 | )
10 |
11 | // Set up client of Llama2 (Rod version)
12 | var page_llama2 *rod.Page
13 | var relogin_llama2 = true
14 | var channel_llama2 chan string
15 |
16 | func Llama2() {
17 | channel_llama2 = make(chan string)
18 | defer func() {
19 | if err := recover(); err != nil {
20 | relogin_llama2 = true
21 | }
22 | }()
23 | //page_hc = browser.MustPage("https://huggingface.co/chat")
24 | page_llama2 = stealth.MustPage(browser)
25 | page_llama2.MustNavigate("https://ysharma-explore-llamav2-with-tgi.hf.space")
26 | for i := 1; i <= 30; i++ {
27 | if page_llama2.MustHasX("//textarea[@data-testid='textbox']") {
28 | relogin_llama2 = false
29 | break
30 | }
31 | time.Sleep(time.Second)
32 | }
33 | if relogin_llama2 == true {
34 | sprint("✘ Llama2")
35 | //page_hc.MustPDF("./tmp/HuggingChat✘.pdf")
36 | }
37 | if relogin_llama2 == false {
38 | sprint("✔ Llama2")
39 | for {
40 | select {
41 | case question := <-channel_llama2:
42 | page_llama2.MustActivate()
43 | //fmt.Println("Falcon180 received question...", question)
44 | for i := 1; i <= 20; i++ {
45 | if page_llama2.MustHasX("//textarea[@data-testid='textbox']") {
46 | page_llama2.MustElementX("//textarea[@data-testid='textbox']").MustInput(question)
47 | break
48 | }
49 | time.Sleep(time.Second)
50 | }
51 | //fmt.Println("Falcon180 input typed...")
52 | for i := 1; i <= 20; i++ {
53 |
54 | //if page_llama2.MustHasX("//button[contains(text(), 'Submit')]") {
55 | //page_llama2.MustElementX("//button[contains(text(), 'Submit')]").MustClick()
56 | if page_llama2.MustHasX("//button[@id='component-14']") {
57 | page_llama2.MustElementX("//button[@id='component-14']").MustClick()
58 | break
59 | }
60 | time.Sleep(time.Second)
61 | }
62 | fmt.Println("Llama2 generating...", role)
63 | //page_falcon180.MustActivate() // Sometime three dot to hang
64 | if role == ".all" {
65 | channel_llama2 <- "click_llama2"
66 | }
67 | //// Check Error
68 | //channel_falcon180_check := make(chan string)
69 | //go func() {
70 | // for i := 1; i <= 20; i++ {
71 | // if page_falcon180.MustHasX("//*[contains(text(), 'Too much traffic, please try again')]") {
72 | // channel_falcon180 <- "✘✘ Falcon180, Please check the internet connection and verify login status. Traffic."
73 | // fmt.Println("Falcon180 too much traffic...")
74 | // relogin_falcon180 = true
75 | // close(channel_falcon180_check)
76 | // break
77 | // }
78 | // time.Sleep(1 * time.Second)
79 | // }
80 | //}()
81 |
82 | // stop_icon
83 | var stop_icon_disappear = false
84 | err := rod.Try(func() {
85 | //page_llama2.Timeout(10 * time.Second).MustElementX("//button[contains(text(), 'Stop')]").MustWaitVisible().CancelTimeout()
86 | page_llama2.Timeout(10 * time.Second).MustElementX("//button[@id='component-15']").MustWaitVisible().CancelTimeout()
87 | })
88 | //fmt.Println("find stop button")
89 | if err == nil {
90 | err = rod.Try(func() {
91 | //page_llama2.Timeout(80 * time.Second).MustElementX("//button[contains(text(), 'Stop')]").MustWaitInvisible().CancelTimeout()
92 | page_llama2.Timeout(80 * time.Second).MustElementX("//button[@id='component-15']").MustWaitInvisible().CancelTimeout()
93 | //fmt.Println("disappear stop button")
94 | })
95 | if err == nil {
96 | stop_icon_disappear = true
97 | } else {
98 | //fmt.Println("err in find disappear stop button", err)
99 | }
100 | } else {
101 | //fmt.Println("err in find stop button", err)
102 | }
103 |
104 | //if page_llama2.MustHas("span:contains('Error')") {
105 | //fmt.Println("finding Error")
106 | if page_llama2.MustHasX("//span[contains(text(), 'Error')]"){
107 |
108 | //fmt.Println("find Error")
109 | channel_llama2 <- "✘✘ Llama2, Please check the internet connection and verify login status."
110 | relogin_llama2 = true
111 | }
112 |
113 |
114 |
115 | if stop_icon_disappear == true {
116 | //answer := page_llama2.MustElementX("(//div[@data-testid='bot'])[last()]").MustText()
117 | answer := page_llama2.MustElementX("(//button[@data-testid='bot'])[last()]").MustText()
118 | channel_llama2 <- answer
119 | } else {
120 | channel_llama2 <- "✘✘ Llama2, Please check the internet connection and verify login status."
121 | relogin_llama2 = true
122 | //page_hc.MustPDF("./tmp/HuggingChat✘.pdf")
123 |
124 | }
125 | }
126 | }
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | # mac amd64
2 | GOOS=windows GOARCH=386 cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih.exe && zip -r ./release/aih_x86_exe.zip aih.exe && rm -rf vi aih.exe # aih_x86.exe
3 | GOOS=windows GOARCH=amd64 cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih.exe && zip -r ./release/aih_amd64_exe.zip aih.exe && rm -rf vi aih.exe # aih_amd64.exe
4 | GOOS=windows GOARCH=arm cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih.exe && zip -r ./release/aih_arm_exe.zip aih.exe && rm -rf vi aih.exe # aih_arm.exe
5 | GOOS=windows GOARCH=arm64 cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih.exe && zip -r ./release/aih_arm64_exe.zip aih.exe && rm -rf vi aih.exe # aih_arm64.exe
6 | GOOS=linux GOARCH=386 cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih && zip -r ./release/aih_linux_x86.zip aih && rm -rf vi aih # aih_linux_x86
7 | GOOS=linux GOARCH=amd64 cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih && zip -r ./release/aih_linux_amd64.zip aih && rm -rf vi aih # aih_linux_amd64
8 | GOOS=linux GOARCH=arm cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih && zip -r ./release/aih_linux_arm.zip aih && rm -rf vi aih # aih_linux_arm
9 | GOOS=linux GOARCH=arm64 cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih && zip -r ./release/aih_linux_arm64.zip aih && rm -rf vi aih # aih_linux_arm64
10 | #GOOS=darwin GOARCH=386 cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih && zip -r ./release/aih_mac_x86.zip aih && rm -rf vi aih # aih_mac_x86
11 | GOOS=darwin GOARCH=amd64 cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih && zip -r ./release/aih_mac_amd64.zip aih && rm -rf vi aih # aih_mac_amd64
12 | GOOS=darwin GOARCH=arm64 cd ./ryy && go build -o ../vi && cd .. && go build -tags vi -o aih && zip -r ./release/aih_mac_arm64.zip aih && rm -rf vi aih # aih_mac_arm64
13 |
--------------------------------------------------------------------------------
/release/upload.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | "os"
9 | "github.com/google/go-github/github"
10 | "golang.org/x/oauth2"
11 | )
12 |
13 | func main() {
14 | var ghp_token string
15 | fmt.Printf("Please input ghp_token: ")
16 | fmt.Scanf("%s", & ghp_token)
17 | //fmt.Println("ghp_token: ", ghp_token)
18 |
19 | ctx := context.Background()
20 |
21 | // Authenticate with GitHub using a personal access token
22 | ts := oauth2.StaticTokenSource(
23 | &oauth2.Token{AccessToken: ghp_token},
24 | )
25 | tc := oauth2.NewClient(ctx, ts)
26 | client := github.NewClient(tc)
27 |
28 | // Set the owner and repository for the release
29 | owner := "Databingo"
30 | repo := "aih"
31 |
32 | // Create a new release
33 | release, _, err := client.Repositories.CreateRelease(ctx, owner, repo, &github.RepositoryRelease{
34 | TagName: github.String("v0.2.6"),
35 | TargetCommitish: github.String("master"),
36 | Name: github.String("Release v0.2.6"),
37 | Body: github.String("Welcome to Aih! Fix bard.go and chatgpt.go for new changes. Disable falcon and claude temporarily."),
38 | Draft: github.Bool(false),
39 | Prerelease: github.Bool(false),
40 | })
41 | if err != nil {
42 | log.Fatal(err)
43 | }
44 |
45 | // Upload binary zip files to the release
46 | files := []string{ "aih_amd64_exe.zip", "aih_arm64_exe.zip", "aih_arm_exe.zip", "aih_linux_amd64.zip", "aih_linux_arm.zip", "aih_linux_arm64.zip", "aih_linux_x86.zip", "aih_mac_amd64.zip", "aih_mac_arm64.zip", "aih_x86_exe.zip"}
47 | for _, file := range files {
48 | // Read the file contents
49 | _, err := ioutil.ReadFile(file)
50 | if err != nil {
51 | log.Fatal(err)
52 | }
53 |
54 | // Create the file on GitHub
55 | fileContent, err := os.Open(file)
56 | if err != nil {
57 | log.Fatal(err)
58 | }
59 | defer fileContent.Close()
60 |
61 | _, _, err = client.Repositories.UploadReleaseAsset(ctx, owner, repo, release.GetID(), &github.UploadOptions{
62 | Name: file,
63 | }, fileContent)
64 | if err != nil {
65 | log.Fatal(err)
66 | }
67 |
68 | fmt.Printf("Uploaded %s to release %d\n", file, release.GetID())
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ryy/.gitignore:
--------------------------------------------------------------------------------
1 | ry
2 |
--------------------------------------------------------------------------------
/ryy/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 kiasaki
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/ryy/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | go build -i -v -o ry .
3 |
4 | run: build
5 | ./ry
6 |
7 | install:
8 | install ./ry /usr/local/bin/ry
9 |
10 | clean:
11 | rm ry
12 |
13 | .PHONY: build run clean
14 |
--------------------------------------------------------------------------------
/ryy/README.md:
--------------------------------------------------------------------------------
1 | # ry
2 |
3 | _A simple modal text editor, written in Go_
4 |
5 | or
6 |
7 | _This is my editor. There are many like it, but this one is mine._
8 |
9 | ### installing
10 |
11 | To build and install `ry` on your computer simply run
12 | `go get github.com/kiasaki/ry`.
13 |
14 | ### developing
15 |
16 | ```bash
17 | make run # builds and runs ry
18 | ```
19 |
20 | ### features
21 |
22 | `ry` is a text editor aiming to provide an editing environment similar to `vim`
23 | in terms of key bindings and modal editing while being as easily extended
24 | as `emacs`. It's is built with the day-to-day usage of the original author in
25 | mind but hopefully flexible enough for anybody with Vim experience to adopt and
26 | mold to their image.
27 |
28 | **Currently implemented keybindings:**
29 |
30 | - Normal Mode
31 | - C-q Quits editor
32 | - : Enters command mode
33 | - C-c Cancels keys entered
34 | - C-g Cancels keys entered
35 | - ESC ESC Cancels keys entered
36 | - h Moves cursor left
37 | - h Moves cursor right
38 | - j Moves cursor down
39 | - k Moves cursor up
40 | - 0 Moves cursor to the beginning of the line
41 | - $ Moves cursor to the beginning of the line
42 | - g g Moves to the beginning of the buffer
43 | - G Moves to the end of the buffer
44 | - C-u Move 15 lines up
45 | - C-d Moves 15 lines down
46 | - z z Centers current line in view
47 | - w Moves forward to next beginning of a word
48 | - e Moves forward to next end of a word
49 | - b Moves backwards to next beginning of a word
50 | - i Enters insert-mode
51 | - I Enters insert-mode at beginning of line
52 | - a Enters insert-mode then moves right 1 character
53 | - A Enters insert-mode then moves to the end of the line
54 | - o Enters insert-mode and creates a new line under the current one
55 | - O Enters insert-mode and creates a new line on top of the current one
56 | - u Undo last change
57 | - C-r Redo last change
58 | - x Delete char under cursor
59 | - d d Deletes line under cursor
60 | - y y Copies line under cursor
61 | - p Pastes from clipboard
62 | - m $alpha Set mark at cursor
63 | - ' $alpha Jump to mark
64 | - v Enter visual mode
65 | - V Enter visual line mode
66 | - C-w s Splits buffer horizontally
67 | - C-w v Splits buffer vertically
68 | - C-w h Move to the window to the left
69 | - C-w j Move to the window to the bottom
70 | - C-w k Move to the window to the top
71 | - C-w l Move to the window to the right
72 | - SPC b Runs `buffers` command
73 | - SPC f Runs `edit` command on current file's directory
74 | - SPC n Runs `clearsearch` command
75 | - Insert mode
76 | - $any Inserts character at cursor's position
77 | - BAK Deletes character to the left
78 | - RET Inserts a new line at cursor position
79 | - ESC Enters normal mode
80 | - Prompt mode
81 | - $any Inserts character
82 | - BAK Deletes character
83 | - C-c Enters normal mode
84 | - ESC Enters normal mode
85 | - RET Execute command and go back to normal mode
86 | - C-u Clear entered command
87 | - Visual mode
88 | - ESC Exit visual mode
89 | - y Yank selection
90 | - d Delete selection
91 | - p Paste selection
92 | - Buffers mode
93 | - q Close buffer
94 | - RET Open selected buffer in current window
95 | - Directory mode
96 | - q Close buffer
97 | - RET Open selected file in current window
98 |
99 | **Currently implemented commands:**
100 |
101 | - `edit ` (aliased as `e`) Edit a file in a new buffer (shows file selector on directories)
102 | - `write ` (aliased as `w`) Write buffer to disk, optionally setting it's path
103 | - `quit` (aliased as `q`) Close current buffer (making sure it's saved before)
104 | - `quit!` (aliased as `q!`) Close current buffer (ignoring unsaved changes)
105 | - `writequit` (aliased as `wq`) Writes buffer to disk then closes it
106 | - `clearsearch (aliased as `cs`) Hides search result highlights
107 | - `buffers` (aliased as `b`) Shows a list of buffers in current window
108 |
109 | ### screenshot
110 |
111 | 
112 |
113 | ### license
114 |
115 | See `LICENSE` file.
116 |
117 |
--------------------------------------------------------------------------------
/ryy/TODO.md:
--------------------------------------------------------------------------------
1 | # Todo
2 |
3 | - Autocomplete (based on open buffers tokens)
4 | - Buffer related commands/keybindings (language modes?)
5 | - ~Command mode (q,w,o,e,wq,!,wqall)~
6 | - ~Marks~
7 | - ~Visual mode (+ visual line)~
8 | - ~Incremental Search~
9 | - ~Buffer list/switch mode~
10 | - ~File tree mode~
11 |
12 | **Post 1.0**
13 |
14 | - Syntax highlithing
15 | - Settings
16 | - Scripting / user settings
17 | - Fuzzy file search mode
18 | - Recursive grep mode
19 | - Line wrapping
20 | - Windows
21 | - Color schemes
22 | - Shell mode
23 | - Ensure UTF-8 works
24 | - Help/Tutorial
25 | - Fringe for error reporting / plugins
26 |
27 | # The big list
28 |
29 | - [ ] Auto indent
30 | - [ ] Custom bindings
31 | - [ ] Line numbers
32 | - [ ] Search and replace
33 | - [ ] Search
34 | - [ ] Replace
35 | - [ ] Tests
36 | - [ ] Error handling
37 | - [ ] Fatal
38 | - [ ] Script/Runtime
39 | - [ ] User
40 | - [ ] Unicode support
41 | - [ ] Command execution
42 | - [ ] Movement keys
43 | - [ ] Visual mode
44 | - [ ] Normal
45 | - [ ] Line
46 | - [ ] Help
47 | - [ ] General
48 | - [ ] Per command
49 | - [ ] Tutorial
50 | - [ ] Options/Configuration
51 | - [ ] Save/Load
52 | - [ ] Tabs to space
53 | - [ ] Tab size
54 | - [ ] Color scheme
55 | - [ ] Undo/Redo
56 | - [ ] Clipboard
57 | - [ ] Copy
58 | - [ ] Paste
59 | - [ ] Cut
60 | - [ ] Registers
61 | - [ ] Macros
62 | - [ ] Syntax highlighting
63 | - [ ] Color schemes
64 |
65 | # Vim's Perl Interface for inspiration:
66 |
67 | ```
68 | VIM::Msg({msg}, {group}?)
69 | VIM::SetOption({arg}) Sets a vim option.
70 | VIM::Buffers([{bn}...]) With no arguments, returns a list of all the buffers.
71 | VIM::Windows([{wn}...]) With no arguments, returns a list of all the windows.
72 | VIM::DoCommand({cmd}) Executes Ex command {cmd}.
73 | VIM::Eval({expr}) Evaluates {expr} and returns (success, val).
74 | Window->SetHeight({height})
75 | Window->Cursor({row}?, {col}?)
76 | Window->Buffer()
77 | Buffer->Name() Returns the filename for the Buffer.
78 | Buffer->Number() Returns the number of the Buffer.
79 | Buffer->Count() Returns the number of lines in the Buffer.
80 | Buffer->Get({lnum}, {lnum}?, ...)
81 | Buffer->Delete({lnum}, {lnum}?)
82 | Buffer->Append({lnum}, {line}, {line}?, ...)
83 | Buffer->Set({lnum}, {line}, {line}?, ...)
84 | $main::curwin
85 | $main::curbuf
86 | ```
87 |
88 | # Some Emacs interface functions
89 |
90 | ...to consider implementing?
91 |
92 | - `(redraw-display)`
93 | - `(redisplay force?)`
94 | - `(message format &rest args)`
95 | - `(with-temp-message message &rest body)` Show message, executes body, removes message, returns body result
96 | - `(current-message)`
97 | - `(make-progress-reporter message &optional min-value max-value current-value min-change min-time)`
98 | - `(progress-reporter-update reporter &optional value)`
99 | - `(progress-reporter-done reporter)`
100 | - `(messages-buffer)`
101 | - `(yes-or-no-p)`
102 | - `cursor-in-echo-area`
103 | - `(format)`
104 | - `(display-warning type message &optional level)` Levels being: emergency, error, warning, debug
105 | - Handle hiding/make buffer sections invisible (for folding)
106 | - `before-init-time`
107 | - `after-init-time`
108 | - `(save-excursion)`
109 |
110 |
--------------------------------------------------------------------------------
/ryy/action.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type ActionType int
4 |
5 | const (
6 | ActionTypeInsert ActionType = 1
7 | ActionTypeRemove = -1
8 | )
9 |
10 | type Action struct {
11 | Typ ActionType
12 | Loc *Location
13 | Data []rune
14 | }
15 |
16 | func NewAction(typ ActionType, loc *Location, data []rune) *Action {
17 | return &Action{Typ: typ, Loc: loc, Data: data}
18 | }
19 |
20 | func (a *Action) Apply(b *Buffer) {
21 | a.Do(b, a.Typ)
22 | b.Modified = true
23 | hook_trigger_buffer("modified", b)
24 | }
25 |
26 | func (a *Action) Revert(b *Buffer) {
27 | a.Do(b, -a.Typ)
28 | b.Modified = true
29 | hook_trigger_buffer("modified", b)
30 | }
31 |
32 | func (a *Action) Do(b *Buffer, typ ActionType) {
33 | if typ == ActionTypeInsert {
34 | a.Insert(b)
35 | } else {
36 | a.Remove(b)
37 | }
38 | }
39 |
40 | func (a *Action) Insert(b *Buffer) {
41 | c, l := a.Loc.Char, a.Loc.Line
42 | for i := len(a.Data) - 1; i >= 0; i-- {
43 | ch := a.Data[i]
44 | if ch == '\n' {
45 | rest := append([]rune(nil), b.Data[l][c:]...)
46 | b.Data[l] = b.Data[l][:c]
47 | b.Data = append(b.Data[:l+1],
48 | append([][]rune{rest}, b.Data[l+1:]...)...)
49 | } else {
50 | b.Data[l] = append(b.Data[l][:c],
51 | append([]rune{ch}, b.Data[l][c:]...)...)
52 | }
53 | }
54 | }
55 |
56 | func (a *Action) Remove(b *Buffer) {
57 | n := len(a.Data)
58 | c, l := a.Loc.Char, a.Loc.Line
59 | removed := []rune{}
60 | for i := 0; i < n; i++ {
61 | removed = append(removed, b.CharAt(l, c))
62 | if b.CharAt(l, c) == '\n' {
63 | if len(b.Data)-1 == l {
64 | a.Data = removed
65 | return
66 | }
67 | b.Data[l] = append(b.Data[l], b.Data[l+1]...)
68 | b.Data = append(b.Data[:l+1], b.Data[l+2:]...)
69 | } else {
70 | b.Data[l] = append(b.Data[l][:c], b.Data[l][c+1:]...)
71 | }
72 | }
73 | a.Data = removed
74 | }
75 |
--------------------------------------------------------------------------------
/ryy/buffer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "path/filepath"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | type Location struct {
12 | Line int
13 | Char int
14 | }
15 |
16 | func NewLocation(l, c int) *Location {
17 | return &Location{Line: l, Char: c}
18 | }
19 |
20 | func orderLocations(l1, l2 *Location) (*Location, *Location) {
21 | if l1.Line < l2.Line {
22 | return l1, l2
23 | } else if l1.Line > l2.Line {
24 | return l2, l1
25 | } else {
26 | if l1.Char > l2.Char {
27 | return l2, l1
28 | } else {
29 | return l1, l2
30 | }
31 | }
32 | }
33 |
34 | func (l1 *Location) Equal(l2 *Location) bool {
35 | return l1.Line == l2.Line && l1.Char == l2.Char
36 | }
37 |
38 | func (l1 *Location) Before(l2 *Location) bool {
39 | ol1, _ := orderLocations(l1, l2)
40 | return l1 == ol1
41 | }
42 |
43 | func (l1 *Location) After(l2 *Location) bool {
44 | _, ol2 := orderLocations(l1, l2)
45 | return l1 == ol2
46 | }
47 |
48 | func (loc *Location) Clone() *Location {
49 | return &Location{Line: loc.Line, Char: loc.Char}
50 | }
51 |
52 | type CharRange struct {
53 | Beg int
54 | End int
55 | }
56 |
57 | func NewCharRange(b, e int) *CharRange {
58 | return &CharRange{b, e}
59 | }
60 |
61 | type Buffer struct {
62 | Data [][]rune
63 | History []*Action
64 | HistoryIndex int
65 | Name string
66 | Path string
67 | Modified bool
68 | Cursor *Location
69 | Modes []string
70 | LastRenderWidth int
71 | LastRenderHeight int
72 | }
73 |
74 | func NewBuffer(name string, path string) *Buffer {
75 | b := &Buffer{
76 | Data: [][]rune{{}},
77 | History: []*Action{},
78 | HistoryIndex: -1,
79 | Modified: false,
80 | Cursor: NewLocation(0, 0),
81 | Modes: []string{},
82 | }
83 |
84 | if path == "" {
85 | // TODO ensure uniqueness
86 | b.Name = name
87 | } else {
88 | b.SetPath(path)
89 | // TODO ensure uniqueness
90 | b.Name = name
91 | }
92 |
93 | return b
94 | }
95 |
96 | func (b *Buffer) IsInMode(name string) bool {
97 | for _, n := range b.Modes {
98 | if n == name {
99 | return true
100 | }
101 | }
102 | return false
103 | }
104 |
105 | func (b *Buffer) CharAt(l, c int) rune {
106 | line := b.Data[l]
107 | if c < 0 {
108 | return rune(0)
109 | } else if c < len(line) {
110 | return line[c]
111 | } else {
112 | return '\n'
113 | }
114 | }
115 |
116 | func (b *Buffer) GetLine(l int) []rune {
117 | return b.Data[l]
118 | }
119 |
120 | func (b *Buffer) CharAtLeft() rune {
121 | return b.CharAt(b.Cursor.Line, b.Cursor.Char-1)
122 | }
123 |
124 | func (b *Buffer) CharUnderCursor() rune {
125 | return b.CharAt(b.Cursor.Line, b.Cursor.Char)
126 | }
127 |
128 | func (b *Buffer) WordUnderCursor() []rune {
129 | ch := b.CharUnderCursor()
130 | if !isWord(ch) {
131 | return nil
132 | }
133 | line := b.GetLine(b.Cursor.Line)
134 | cstart := b.Cursor.Char
135 | for cstart > 0 && isWord(b.CharAt(b.Cursor.Line, cstart-1)) {
136 | cstart--
137 | }
138 | cend := cstart
139 | for cend < len(line) && isWord(b.CharAt(b.Cursor.Line, cend+1)) {
140 | cend++
141 | }
142 | return line[cstart : cend+1]
143 | }
144 |
145 | func (b *Buffer) FirstLine() bool {
146 | return b.Cursor.Line == 0
147 | }
148 |
149 | func (b *Buffer) LastLine() bool {
150 | return b.Cursor.Line == len(b.Data)-1
151 | }
152 |
153 | func (b *Buffer) MoveTo(c, l int) {
154 | b.Cursor.Line = max(min(l, len(b.Data)-1), 0)
155 | b.Cursor.Char = max(min(c, len(b.Data[b.Cursor.Line])), 0)
156 | hook_trigger_buffer("moved", b)
157 | }
158 |
159 | func (b *Buffer) Move(c, l int) {
160 | b.MoveTo(b.Cursor.Char+c, b.Cursor.Line+l)
161 | }
162 |
163 | func (b *Buffer) MoveWordForward() bool {
164 | for {
165 | c := b.CharUnderCursor()
166 | if c == '\n' {
167 | if b.LastLine() {
168 | return false
169 | } else {
170 | b.MoveTo(0, b.Cursor.Line+1)
171 | break
172 | }
173 | }
174 |
175 | for isWord(c) && c != '\n' {
176 | b.Move(1, 0)
177 | c = b.CharUnderCursor()
178 | }
179 |
180 | if c == '\n' {
181 | continue
182 | }
183 | break
184 | }
185 |
186 | c := b.CharUnderCursor()
187 | for !isWord(c) && c != '\n' {
188 | b.Move(1, 0)
189 | c = b.CharUnderCursor()
190 | }
191 |
192 | return true
193 | }
194 |
195 | func (b *Buffer) MoveWordEndForward() bool {
196 | b.Move(1, 0)
197 | for {
198 | c := b.CharUnderCursor()
199 | if c == '\n' {
200 | if b.LastLine() {
201 | return false
202 | } else {
203 | b.MoveTo(0, b.Cursor.Line+1)
204 | break
205 | }
206 | }
207 |
208 | for !isWord(c) && c != '\n' {
209 | b.Move(1, 0)
210 | c = b.CharUnderCursor()
211 | }
212 |
213 | if c == '\n' {
214 | continue
215 | }
216 | break
217 | }
218 |
219 | c := b.CharUnderCursor()
220 | for isWord(c) && c != '\n' {
221 | b.Move(1, 0)
222 | c = b.CharUnderCursor()
223 | }
224 | b.Move(-1, 0)
225 |
226 | return true
227 | }
228 |
229 | func (b *Buffer) MoveWordBackward() bool {
230 | b.Move(-1, 0)
231 | for {
232 | c := b.CharUnderCursor()
233 | if b.Cursor.Char == 0 {
234 | if b.FirstLine() {
235 | return false
236 | } else {
237 | b.MoveTo(len(b.Data[b.Cursor.Line-1]), b.Cursor.Line-1)
238 | continue
239 | }
240 | }
241 |
242 | for !isWord(c) && b.Cursor.Char != 0 {
243 | b.Move(-1, 0)
244 | c = b.CharUnderCursor()
245 | }
246 |
247 | if b.Cursor.Char == 0 {
248 | continue
249 | }
250 | break
251 | }
252 |
253 | c := b.CharUnderCursor()
254 | for isWord(c) && b.Cursor.Char != 0 {
255 | b.Move(-1, 0)
256 | c = b.CharUnderCursor()
257 | }
258 | b.Move(1, 0)
259 |
260 | return true
261 | }
262 |
263 | func (b *Buffer) Insert(data []rune) {
264 | a := NewAction(ActionTypeInsert, b.Cursor.Clone(), data)
265 | b.HistoryIndex++
266 | b.History = tryMergeHistory(b.History[:b.HistoryIndex], a)
267 | a.Apply(b)
268 | }
269 |
270 | func (b *Buffer) RemoveAt(loc *Location, n int) []rune {
271 | a := NewAction(ActionTypeRemove, loc.Clone(), make([]rune, n))
272 | b.HistoryIndex++
273 | b.History = tryMergeHistory(b.History[:b.HistoryIndex], a)
274 | a.Apply(b)
275 | return a.Data
276 | }
277 |
278 | func (b *Buffer) Remove(n int) []rune {
279 | return b.RemoveAt(b.Cursor, n)
280 | }
281 |
282 | func (b *Buffer) Undo() {
283 | if b.HistoryIndex >= 0 && b.HistoryIndex != -1 {
284 | b.History[b.HistoryIndex].Revert(b)
285 | b.HistoryIndex--
286 | } else {
287 | message("Noting to undo!")
288 | }
289 | }
290 |
291 | func (b *Buffer) Redo() {
292 | if b.HistoryIndex+1 < len(b.History) && len(b.History) > b.HistoryIndex+1 {
293 | b.HistoryIndex++
294 | b.History[b.HistoryIndex].Apply(b)
295 | } else {
296 | message("Noting to redo!")
297 | }
298 | }
299 |
300 | func (b *Buffer) SetPath(path string) {
301 | var err error
302 | b.Path, err = filepath.Abs(path)
303 | if err != nil {
304 | b.Path = filepath.Clean(path)
305 | }
306 | b.Name = ""
307 | name := filepath.Base(b.Path)
308 |
309 | i := 1
310 | checkName:
311 | for _, b2 := range buffers {
312 | if b2.Name == name {
313 | b.Name = name + " " + strconv.Itoa(i)
314 | i++
315 | goto checkName
316 | }
317 | }
318 | if b.Name == "" {
319 | b.Name = name
320 | }
321 | }
322 |
323 | func (b *Buffer) AddMode(name string) {
324 | if b.IsInMode(name) {
325 | return
326 | }
327 | b.Modes = append(b.Modes, name)
328 | }
329 |
330 | func (b *Buffer) RemoveMode(name string) {
331 | for i, n := range b.Modes {
332 | if n == name {
333 | b.Modes = append(b.Modes[:i], b.Modes[i+1:]...)
334 | return
335 | }
336 | }
337 | }
338 |
339 | func (b *Buffer) Contents() string {
340 | ret := ""
341 | for _, line := range b.Data {
342 | ret += string(line) + "\n"
343 | }
344 | return ret
345 | }
346 |
347 | func (b *Buffer) NicePath() string {
348 | return strings.Replace(b.Path, os.Getenv("HOME"), "~", -1)
349 | }
350 |
351 | func (b *Buffer) Save() {
352 | if b.Path == "" {
353 | messageError("Can't save a buffer without a path.")
354 | return
355 | }
356 | err := ioutil.WriteFile(b.Path, []byte(b.Contents()), 0666)
357 | if err != nil {
358 | messageError("Error saving buffer: " + err.Error())
359 | } else {
360 | b.Modified = false
361 | message("Buffer written to '" + b.NicePath() + "'")
362 | }
363 | }
364 |
365 | func tryMergeHistory(al []*Action, a *Action) []*Action {
366 | // TODO save end location on actions so that we can merge them here
367 | return append(al, a)
368 | }
369 |
--------------------------------------------------------------------------------
/ryy/clipboard.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | zclip "github.com/zyedidia/clipboard"
5 | )
6 |
7 | func clipboardGet(register rune) []rune {
8 | if register == defaultClipboard {
9 | if value, err := zclip.ReadAll("clipboard"); err == nil {
10 | return []rune(value)
11 | }
12 | }
13 | if value, ok := clipboards[register]; ok {
14 | return value
15 | }
16 | return []rune{}
17 | }
18 |
19 | func clipboardSet(register rune, value []rune) {
20 | if register == defaultClipboard {
21 | if err := zclip.WriteAll(string(value), "clipboard"); err != nil {
22 | messageError("Error clipboard_get: " + err.Error())
23 | clipboards[register] = value
24 | }
25 | } else {
26 | clipboards[register] = value
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ryy/commands.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "path/filepath"
7 | "sort"
8 | "strings"
9 | // "fmt"
10 | )
11 |
12 | func openBufferFromFile(path string) *Buffer {
13 | if fileInfo, err := os.Stat(path); os.IsNotExist(err) || err != nil {
14 | return openBufferNamed(filepath.Base(path))
15 | } else {
16 | if fileInfo.IsDir() {
17 | file, err := os.Open(path)
18 | if err != nil {
19 | messageError("Error opening directory: " + err.Error())
20 | return nil
21 | }
22 | files, err := file.Readdir(0)
23 | if err != nil {
24 | messageError("Error opening directory: " + err.Error())
25 | return nil
26 | }
27 | buf := NewBuffer(filepath.Base(path), path)
28 | buf.Data = [][]rune{}
29 | file_names := []string{}
30 | for _, file_info := range files {
31 | if file_info.IsDir() {
32 | file_names = append(file_names, " "+file_info.Name())
33 | } else {
34 | file_names = append(file_names, file_info.Name())
35 | }
36 | }
37 | file_names = append(file_names, " ..")
38 | sort.Strings(file_names)
39 | for _, file_name := range file_names {
40 | if file_name[0] == ' ' { // is dir
41 | buf.Data = append(buf.Data, []rune(file_name[1:]+"/"))
42 | } else {
43 | buf.Data = append(buf.Data, []rune(file_name))
44 | }
45 | }
46 | buf.AddMode("directory")
47 | buffers = append(buffers, buf)
48 | hook_trigger_buffer("modified", buf)
49 | return buf
50 | }
51 | }
52 |
53 | buf := NewBuffer(filepath.Base(path), path)
54 | if buf.Path != "" {
55 | contents, err := ioutil.ReadFile(buf.Path)
56 | if err != nil {
57 | messageError("Error reading file '" + buf.NicePath() + "'")
58 | return nil
59 | }
60 | buf.Data = [][]rune{}
61 | for _, line := range strings.Split(string(contents), "\n") {
62 | buf.Data = append(buf.Data, []rune(line))
63 | }
64 | if len(buf.Data) > 1 {
65 | buf.Data = buf.Data[:len(buf.Data)-1]
66 | }
67 | }
68 | buffers = append(buffers, buf)
69 | hook_trigger_buffer("modified", buf)
70 | return buf
71 | }
72 |
73 | func openBufferNamed(name string) *Buffer {
74 | buf := NewBuffer(name, "")
75 | buffers = append(buffers, buf)
76 | hook_trigger_buffer("modified", buf)
77 | return buf
78 | }
79 |
80 | // TODO check if is shown first, then if not create split not replace
81 | func showBuffer(buffer_name string) *Buffer {
82 | for _, b := range buffers {
83 | if b.Name == buffer_name {
84 | if currentViewTree.Leaf.Buf == b {
85 | return b // already shown
86 | }
87 | currentViewTree = NewViewTreeLeaf(nil, NewView(b))
88 | rootViewTree = currentViewTree
89 | return b
90 | }
91 | }
92 | return nil
93 | }
94 |
95 | func closeBuffer(b *Buffer) {
96 | for i, b2 := range buffers {
97 | if b == b2 {
98 | buffers = append(buffers[:i], buffers[i+1:]...)
99 | break
100 | }
101 | }
102 | }
103 |
104 | func selectAvailableBuffer(closeIfNone bool) {
105 | if len(buffers) == 0 {
106 | if closeIfNone {
107 | // TODO Use method here (don't handcode screen.Fini())
108 | screen.Fini()
109 | os.Exit(0)
110 | }
111 | } else {
112 | currentViewTree = NewViewTreeLeaf(nil, NewView(buffers[0]))
113 | rootViewTree = currentViewTree
114 | }
115 | }
116 |
117 | func closeCurrentBuffer(force bool) {
118 | b := currentViewTree.Leaf.Buf
119 | if b.Modified && !force {
120 | messageError("Save buffer before closing it.")
121 | return
122 | }
123 | closeBuffer(b)
124 | selectAvailableBuffer(true)
125 | }
126 |
127 | func findBuffer(name string) *Buffer {
128 | for _, b := range buffers {
129 | if b.Name == name {
130 | return b
131 | }
132 | }
133 | return nil
134 | }
135 |
136 | var commands = map[string]func([]string){}
137 | var commandAliases = map[string]string{}
138 |
139 | func runCommand(args []string) {
140 | if len(args) == 0 {
141 | messageError("No command given!")
142 | return
143 | }
144 | command_name := args[0]
145 | if full_command_name, ok := commandAliases[command_name]; ok {
146 | command_name = full_command_name
147 | }
148 | if c, ok := commands[command_name]; ok {
149 | c(args)
150 | } else {
151 | messageError("No command named '" + command_name + "'")
152 | }
153 | }
154 |
155 | func addCommand(name string, fn func([]string)) {
156 | commands[name] = fn
157 | }
158 | func addAlias(alias, name string) {
159 | commandAliases[alias] = name
160 | }
161 |
162 | func initCommands() {
163 | addCommand("quit", func(args []string) {
164 | /////
165 | screen.Fini()
166 | os.Exit(0)
167 | /////
168 | //closeCurrentBuffer(false)
169 | closeCurrentBuffer(true)
170 | })
171 | addAlias("q", "quit")
172 | addCommand("quit!", func(args []string) {
173 | closeCurrentBuffer(false)
174 | })
175 | addAlias("q!", "quit!")
176 | addCommand("write", func(args []string) {
177 | b := currentViewTree.Leaf.Buf
178 | if len(args) > 1 {
179 | b.SetPath(args[1])
180 | }
181 | b.Save()
182 | })
183 | addAlias("w", "write")
184 | ////////
185 | addCommand("wai", func(args []string) {
186 | b := currentViewTree.Leaf.Buf
187 | b.SetPath("./.quest.txt")
188 | b.Save()
189 | //fmt.Println("close")
190 | screen.Fini()
191 | os.Exit(0)
192 | //closeCurrentBuffer(false)
193 | })
194 | addAlias("ai", "wai")
195 | ////////
196 | addCommand("edit", func(args []string) {
197 | if len(args) < 2 {
198 | messageError("Can't open buffer without a name or file path.")
199 | } else {
200 | path, err := filepath.Abs(args[1])
201 | if err != nil {
202 | path = args[1]
203 | }
204 | if b := openBufferFromFile(path); b != nil {
205 | showBuffer(b.Name)
206 | }
207 | }
208 |
209 | })
210 | addAlias("e", "edit")
211 | addAlias("o", "edit")
212 | addCommand("writequit", func(args []string) {
213 | runCommand([]string{"write"})
214 | runCommand([]string{"quit"})
215 | })
216 | addAlias("wq", "writequit")
217 | addCommand("buffers", func(args []string) {
218 | var b *Buffer
219 | if b = findBuffer("*buffers*"); b == nil {
220 | b = openBufferNamed("*buffers*")
221 | b.AddMode("buffers")
222 | }
223 | b.Data = [][]rune{}
224 | for _, buf := range buffers {
225 | if buf.Name != "*buffers*" {
226 | b.Data = append(b.Data, []rune(buf.Name))
227 | }
228 | }
229 | hook_trigger_buffer("modified", b)
230 | showBuffer(b.Name)
231 | })
232 | addAlias("b", "buffers")
233 | }
234 |
--------------------------------------------------------------------------------
/ryy/config.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | var (
4 | config map[string]interface{}
5 | )
6 |
7 | func initConfig() {
8 | config = map[string]interface{}{
9 | "tab_width": float64(4),
10 | "tab_to_spaces": true,
11 | }
12 | }
13 |
14 | func configGet(key string, b *Buffer) string {
15 | if v, ok := config[key]; ok {
16 | if vv, ok := v.(string); ok {
17 | return vv
18 | }
19 | }
20 | return ""
21 | }
22 |
23 | func configGetBool(key string, b *Buffer) bool {
24 | if v, ok := config[key]; ok {
25 | if vv, ok := v.(bool); ok {
26 | return vv
27 | }
28 | }
29 | return false
30 | }
31 |
32 | func configGetNumber(key string, b *Buffer) float64 {
33 | if v, ok := config[key]; ok {
34 | if vv, ok := v.(float64); ok {
35 | return vv
36 | }
37 | }
38 | return 0
39 | }
40 |
41 | func configSet(key string, value interface{}) {
42 | config[key] = value
43 | }
44 |
--------------------------------------------------------------------------------
/ryy/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/Databingo/ryy
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/gdamore/encoding v1.0.0
7 | github.com/gdamore/tcell v1.3.0
8 | github.com/go-errors/errors v1.0.0
9 | github.com/kr/pty v1.1.1
10 | github.com/lucasb-eyer/go-colorful v1.0.3
11 | github.com/mattn/go-runewidth v0.0.8
12 | github.com/zyedidia/clipboard v0.0.0-20180208191628-4611e809d8b1
13 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
14 | golang.org/x/text v0.3.2
15 | )
16 |
--------------------------------------------------------------------------------
/ryy/go.sum:
--------------------------------------------------------------------------------
1 | github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
2 | github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI=
3 | github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ=
4 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
5 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
6 | github.com/gdamore/tcell v1.0.0 h1:oaly4AkxvDT5ffKHV/n4L8iy6FxG2QkAVl0M6cjryuE=
7 | github.com/gdamore/tcell v1.0.0/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
8 | github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
9 | github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
10 | github.com/go-errors/errors v1.0.0 h1:2G1gYpeHw4GhLet4Ebp5q9wpnSCAOJNTiJq+I3wJV5I=
11 | github.com/go-errors/errors v1.0.0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
12 | github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
14 | github.com/lucasb-eyer/go-colorful v0.0.0-20170903184257-231272389856 h1:r+WvXmgROttp7pckv7TPN7OCUEPXmvhRklOOsL2iPPc=
15 | github.com/lucasb-eyer/go-colorful v0.0.0-20170903184257-231272389856/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
16 | github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
17 | github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
18 | github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
19 | github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
20 | github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
21 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
22 | github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
23 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
24 | github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
25 | github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
26 | github.com/zyedidia/clipboard v0.0.0-20180208191628-4611e809d8b1 h1:tG+Ld7OcsC4GGkbTt/YIQGW3qfKWtSPI+4lenBFO7q0=
27 | github.com/zyedidia/clipboard v0.0.0-20180208191628-4611e809d8b1/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
28 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
29 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
30 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
31 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
32 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
33 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
34 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
35 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
36 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
37 |
--------------------------------------------------------------------------------
/ryy/highlighting.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/gdamore/tcell"
7 | )
8 |
9 | var (
10 | style_maps = map[*Buffer][][]tcell.Style{}
11 | highlighting_reserved_words = []string{
12 | "func", "function", "fn", "lambda",
13 | "var", "let", "const", "def",
14 | "type", "struct", "interface", "class",
15 | "if", "else", "for", "of", "in", "while", "break", "continue", "goto", "end",
16 | "select", "switch", "case",
17 | "import", "export", "package", "from",
18 | "go", "async", "await",
19 | "raise", "throw", "try", "catch", "except", "finally",
20 | "string", "rune", "byte", "int", "float",
21 | }
22 | highlighting_special_words = []string{
23 | "self", "this", "true", "false", "True", "False", "nil", "null", "None",
24 | }
25 | )
26 |
27 | func highlighting_styles(b *Buffer) [][]tcell.Style {
28 | return style_maps[b]
29 | }
30 |
31 | func init_highlighting() {
32 | hook_buffer("modified", highlight_buffer)
33 | }
34 |
35 | func highlight_buffer(b *Buffer) {
36 | s := style("default")
37 | ss := style("special")
38 | sse := style("search")
39 | svi := style("visual")
40 | sts := style("text.string")
41 | stn := style("text.number")
42 | stc := style("text.comment")
43 | str := style("text.reserved")
44 | stsp := style("text.special")
45 |
46 | style_map := make([][]tcell.Style, len(b.Data))
47 | in_string := rune(0)
48 | for l := range b.Data {
49 | in_line_comment := false
50 | word := ""
51 | style_map[l] = make([]tcell.Style, len(b.Data[l])+1)
52 | for c, char := range b.Data[l] {
53 | prev_char := rune(0)
54 | if c > 0 {
55 | prev_char = b.Data[l][c-1]
56 | }
57 |
58 | // for numbers
59 | passed_alpha := false
60 | if isAlpha(prev_char) {
61 | passed_alpha = true
62 | }
63 | // for special words
64 | if isWord(char) {
65 | word += string(char)
66 | } else {
67 | word = ""
68 | }
69 |
70 | if high_len := search_highlight(b, l, c); high_len > 0 {
71 | for i := 0; i < high_len; i++ {
72 | if style_map[l][c+i] == 0 {
73 | style_map[l][c+i] = sse
74 | }
75 | }
76 | }
77 | if visualHighlight(b, l, c) {
78 | style_map[l][c] = svi
79 | continue
80 | }
81 | if in_line_comment {
82 | style_map[l][c] = stc
83 | continue
84 | }
85 | if in_string > 0 && c-1 > 0 && b.Data[l][c-1] == '\\' && (c-2 < 0 || b.Data[l][c-2] != '\\') {
86 | style_map[l][c] = sts
87 | continue
88 | }
89 | if char == '/' && prev_char == '/' {
90 | in_line_comment = true
91 | style_map[l][c] = stc
92 | style_map[l][c-1] = stc
93 | }
94 | if char == '\'' || char == '"' || char == '`' {
95 | if in_string == char {
96 | in_string = rune(0)
97 | } else if in_string == rune(0) {
98 | in_string = char
99 | }
100 | style_map[l][c] = sts
101 | }
102 | if style_map[l][c] != 0 {
103 | continue
104 | }
105 | if in_string > 0 {
106 | style_map[l][c] = sts
107 | } else if listContainsString(highlighting_special_words, word) && c+1 < len(b.Data[l]) && !isWord(b.Data[l][c+1]) {
108 | for i := len(word) - 1; i >= 0; i-- {
109 | style_map[l][c-i] = stsp
110 | }
111 | } else if listContainsString(highlighting_reserved_words, word) && c+1 < len(b.Data[l]) && !isWord(b.Data[l][c+1]) {
112 | for i := len(word) - 1; i >= 0; i-- {
113 | style_map[l][c-i] = str
114 | }
115 | } else if !passed_alpha && isNum(char) {
116 | style_map[l][c] = stn
117 | } else if strings.ContainsRune(specialChars, b.Data[l][c]) {
118 | style_map[l][c] = ss
119 | } else {
120 | style_map[l][c] = s
121 | }
122 | }
123 | }
124 |
125 | style_maps[b] = style_map
126 | }
127 |
--------------------------------------------------------------------------------
/ryy/hooks.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | var (
4 | hooks_buffer map[string][]func(*Buffer)
5 | )
6 |
7 | func hook_buffer(name string, f func(*Buffer)) {
8 | if _, ok := hooks_buffer[name]; !ok {
9 | hooks_buffer[name] = []func(*Buffer){}
10 | }
11 | hooks_buffer[name] = append(hooks_buffer[name], f)
12 | }
13 |
14 | func hook_trigger_buffer(name string, b *Buffer) {
15 | if hooks, ok := hooks_buffer[name]; ok {
16 | for _, f := range hooks {
17 | f(b)
18 | }
19 | }
20 | }
21 |
22 | func init_hooks() {
23 | hooks_buffer = map[string][]func(*Buffer){}
24 |
25 | hook_buffer("moved", func(b *Buffer) {
26 | if currentViewTree.Leaf.Buf == b {
27 | currentViewTree.Leaf.AdjustScroll(b.LastRenderWidth, b.LastRenderHeight)
28 | }
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/ryy/init.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/gdamore/tcell"
7 | "github.com/gdamore/tcell/encoding"
8 | )
9 |
10 | func initScreen() {
11 | var err error
12 | screen, err = tcell.NewScreen()
13 | fatalError(err)
14 | err = screen.Init()
15 | fatalError(err)
16 |
17 | encoding.Register()
18 | tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
19 |
20 | screen.SetStyle(tcell.StyleDefault)
21 | screen.Clear()
22 |
23 | editorWidth, editorHeight = screen.Size()
24 | }
25 |
26 | func initTermEvents() {
27 | go func() {
28 | for {
29 | if screen == nil {
30 | break
31 | }
32 | termEvents <- screen.PollEvent()
33 | }
34 | }()
35 | }
36 |
37 | func initBuffers() {
38 | for _, arg := range os.Args[1:] {
39 | openBufferFromFile(arg)
40 | }
41 | if len(buffers) == 0 {
42 | openBufferNamed("*scratch*")
43 | }
44 | }
45 |
46 | func initViews() {
47 | view := NewView(buffers[0])
48 | rootViewTree = &ViewTree{Leaf: view}
49 | currentViewTree = rootViewTree
50 | }
51 |
--------------------------------------------------------------------------------
/ryy/key.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "strings"
5 | "unicode"
6 |
7 | "github.com/gdamore/tcell"
8 | )
9 |
10 | type Key struct {
11 | Mod tcell.ModMask
12 | Key tcell.Key
13 | Chr rune
14 | }
15 |
16 | const (
17 | KeyTypeCatchall tcell.Key = iota + 5000
18 | KeyTypeAlpha
19 | KeyTypeNum
20 | KeyTypeAlphaNum
21 | )
22 |
23 | func NewKeyFromEvent(ev *tcell.EventKey) *Key {
24 | k, r, m := ev.Key(), ev.Rune(), ev.Modifiers()
25 |
26 | keyName := ev.Name()
27 | if strings.HasPrefix(keyName, "Ctrl+") {
28 | k = tcell.KeyRune
29 | r = unicode.ToLower([]rune(keyName[5:6])[0])
30 | }
31 |
32 | // Handle Ctrl-h
33 | if k == tcell.KeyBackspace {
34 | m |= tcell.ModCtrl
35 | k = tcell.KeyRune
36 | r = 'h'
37 | }
38 |
39 | if k != tcell.KeyRune {
40 | r = 0
41 | }
42 |
43 | return &Key{Mod: ev.Modifiers(), Key: k, Chr: r}
44 | }
45 |
46 | func NewKey(rep string) *Key {
47 | if rep == "$any" {
48 | return &Key{Key: KeyTypeCatchall}
49 | } else if rep == "$num" {
50 | return &Key{Key: KeyTypeNum}
51 | } else if rep == "$alpha" {
52 | return &Key{Key: KeyTypeAlpha}
53 | } else if rep == "$alphanum" {
54 | return &Key{Key: KeyTypeAlphaNum}
55 | }
56 |
57 | parts := strings.Split(rep, "-")
58 | if rep == "-" {
59 | parts = []string{"-"}
60 | }
61 |
62 | // Modifiers
63 | modMask := tcell.ModNone
64 | for _, part := range parts[:len(parts)-1] {
65 | switch part {
66 | case "C":
67 | modMask |= tcell.ModCtrl
68 | case "S":
69 | modMask |= tcell.ModShift
70 | case "A":
71 | modMask |= tcell.ModAlt
72 | case "M":
73 | modMask |= tcell.ModMeta
74 | }
75 | }
76 |
77 | // Key
78 | var r rune = 0
79 | var k tcell.Key
80 | lastPart := parts[len(parts)-1]
81 | switch lastPart {
82 | case "DEL":
83 | k = tcell.KeyDelete
84 | case "BAK":
85 | k = tcell.KeyBackspace2
86 | case "RET":
87 | k = tcell.KeyEnter
88 | case "SPC":
89 | k = tcell.KeyRune
90 | r = ' '
91 | case "ESC":
92 | k = tcell.KeyEscape
93 | case "TAB":
94 | k = tcell.KeyTab
95 | default:
96 | k = tcell.KeyRune
97 | r = []rune(lastPart)[0]
98 | }
99 |
100 | return &Key{Mod: modMask, Key: k, Chr: r}
101 | }
102 |
103 | func (k *Key) String() string {
104 | if k.Key == KeyTypeCatchall {
105 | return "$any"
106 | } else if k.Key == KeyTypeNum {
107 | return "$num"
108 | } else if k.Key == KeyTypeAlpha {
109 | return "$alpha"
110 | } else if k.Key == KeyTypeAlphaNum {
111 | return "$alphanum"
112 | }
113 |
114 | mods := []string{}
115 | if k.Mod&tcell.ModCtrl != 0 {
116 | mods = append(mods, "C")
117 | }
118 | if k.Mod&tcell.ModShift != 0 {
119 | mods = append(mods, "S")
120 | }
121 | if k.Mod&tcell.ModAlt != 0 {
122 | mods = append(mods, "A")
123 | }
124 | if k.Mod&tcell.ModMeta != 0 {
125 | mods = append(mods, "M")
126 | }
127 |
128 | name := string(k.Chr)
129 | switch k.Key {
130 | case tcell.KeyDelete:
131 | name = "DEL"
132 | case tcell.KeyBackspace2:
133 | name = "BAK"
134 | case tcell.KeyEnter:
135 | name = "RET"
136 | case tcell.KeyEscape:
137 | name = "ESC"
138 | case tcell.KeyTab:
139 | name = "TAB"
140 | }
141 | if k.Key == tcell.KeyRune && k.Chr == ' ' {
142 | name = "SPC"
143 | }
144 |
145 | return strings.Join(append(mods, name), "-")
146 | }
147 |
148 | func (k *Key) IsRune() bool {
149 | return k.Mod == 0 && k.Key == tcell.KeyRune
150 | }
151 |
152 | // TODO implement alphanum match
153 | func (k1 *Key) Matches(k2 *Key) bool {
154 | if k1.Key == KeyTypeCatchall || k2.Key == KeyTypeCatchall {
155 | return true
156 | }
157 | if k1.Key == KeyTypeAlpha && k2.IsRune() && isAlpha(k2.Chr) {
158 | return true
159 | }
160 | if k2.Key == KeyTypeAlpha && k1.IsRune() && isAlpha(k1.Chr) {
161 | return true
162 | }
163 | if k1.Key == KeyTypeNum && k2.IsRune() && isNum(k2.Chr) {
164 | return true
165 | }
166 | if k2.Key == KeyTypeNum && k1.IsRune() && isNum(k1.Chr) {
167 | return true
168 | }
169 | return k1.Mod == k2.Mod && k1.Key == k2.Key && k1.Chr == k2.Chr
170 | }
171 |
172 | type KeyList struct {
173 | keys []*Key
174 | }
175 |
176 | func NewKeyList(rep string) *KeyList {
177 | kl := &KeyList{[]*Key{}}
178 | parts := strings.Split(rep, " ")
179 | for _, part := range parts {
180 | if part != "" {
181 | kl.keys = append(kl.keys, NewKey(part))
182 | }
183 | }
184 | return kl
185 | }
186 |
187 | var k = NewKeyList
188 |
189 | func (kl *KeyList) String() string {
190 | rep := []string{}
191 | for _, k := range kl.keys {
192 | rep = append(rep, k.String())
193 | }
194 | return strings.Join(rep, " ")
195 | }
196 |
197 | func (kl *KeyList) AddKey(k *Key) {
198 | kl.keys = append(kl.keys, k)
199 | }
200 |
201 | func (kl1 *KeyList) Matches(kl2 *KeyList) bool {
202 | if len(kl1.keys) != len(kl2.keys) {
203 | return false
204 | }
205 | for i := range kl1.keys {
206 | if !kl1.keys[i].Matches(kl2.keys[i]) {
207 | return false
208 | }
209 | }
210 | return true
211 | }
212 |
213 | func (kl1 *KeyList) HasSuffix(kl2 *KeyList) *KeyList {
214 | for i := len(kl1.keys) - 1; i >= 0; i-- {
215 | tmp_kl := KeyList{kl1.keys[i:]}
216 | if tmp_kl.Matches(kl2) {
217 | return &tmp_kl
218 | }
219 | }
220 | return nil
221 | }
222 |
--------------------------------------------------------------------------------
/ryy/mark.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type Mark struct {
4 | Loc *Location
5 | BufferName string
6 | }
7 |
8 | var marks = map[rune]*Mark{}
9 |
10 | func markCreate(markLetter rune, b *Buffer) *Mark {
11 | m := &Mark{Loc: b.Cursor.Clone(), BufferName: b.Name}
12 | marks[markLetter] = m
13 | return m
14 | }
15 |
16 | func getMark(markLetter rune) *Mark {
17 | return marks[markLetter]
18 | }
19 |
20 | func markJump(markLetter rune) {
21 | if m, ok := marks[markLetter]; ok {
22 | if b := showBuffer(m.BufferName); b != nil {
23 | b.MoveTo(m.Loc.Char, m.Loc.Line)
24 | } else {
25 | messageError("Can't find buffer named '" + m.BufferName + "'")
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ryy/mode_term.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func initTerm() {
4 | addMode("term")
5 | bind("term", k("ESC"), termExitMode)
6 | bind("term", k("$any"), termInput)
7 | }
8 |
9 | func termExitMode(vt *ViewTree, b *Buffer, kl *KeyList) {
10 | b.RemoveMode("term")
11 | }
12 |
13 | func termInput(vt *ViewTree, b *Buffer, kl *KeyList) {
14 | }
15 |
--------------------------------------------------------------------------------
/ryy/mode_visual.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func initVisual() {
4 | addMode("visual")
5 | bind("visual", k("ESC"), exitVisualMode)
6 | bind("visual", k("y"), visualModeYank)
7 | bind("visual", k("d"), visualModeDelete)
8 | bind("visual", k("p"), visualModePaste)
9 | bind("visual", k("c"), visualModeChange)
10 |
11 | addMode("visual-line")
12 | bind("visual-line", k("ESC"), exitVisualMode)
13 | bind("visual-line", k("y"), visualModeYank)
14 | bind("visual-line", k("d"), visualModeDelete)
15 | bind("visual-line", k("p"), visualModePaste)
16 | bind("visual-line", k("c"), visualModeChange)
17 |
18 | hook_buffer("moved", visualRehighlight)
19 | }
20 |
21 | // Run highlight when in visual mode and cursor mode as normal we
22 | // only recompute highlights when the buffer changes
23 | func visualRehighlight(b *Buffer) {
24 | if b.IsInMode("visual") || b.IsInMode("visual-line") {
25 | highlight_buffer(b)
26 | }
27 | }
28 |
29 | func visualHighlight(b *Buffer, l, c int) bool {
30 | in_visual_line := b.IsInMode("visual-line")
31 | if !b.IsInMode("visual") && !in_visual_line {
32 | return false
33 | }
34 |
35 | l1, l2 := orderLocations(b.Cursor, getMark('∫').Loc)
36 |
37 | if in_visual_line {
38 | // compare using line numbers
39 | return l1.Line <= l && l <= l2.Line
40 | } else {
41 | // compare using line numbers + char position
42 | loc := NewLocation(l, c)
43 | return loc.After(l1) && loc.Before(l2) || loc.Equal(l1) || loc.Equal(l2)
44 | }
45 | }
46 |
47 | func exitVisualMode(vt *ViewTree, b *Buffer, kl *KeyList) {
48 | b.RemoveMode("visual")
49 | b.RemoveMode("visual-line")
50 | highlight_buffer(b)
51 | }
52 |
53 | func enterVisualMode(vt *ViewTree, b *Buffer, kl *KeyList) {
54 | b.AddMode("visual")
55 | markCreate('∫', b)
56 | }
57 |
58 | func enterVisualBlockMode(vt *ViewTree, b *Buffer, kl *KeyList) {
59 | b.AddMode("visual-line")
60 | markCreate('∫', b)
61 | highlight_buffer(b)
62 | }
63 |
64 | func visualModeSelection(b *Buffer) ([]rune, *Location, *Location) {
65 | in_visual_line := b.IsInMode("visual-line")
66 | l1, l2 := orderLocations(b.Cursor, getMark('∫').Loc)
67 | data := []rune{}
68 | if in_visual_line {
69 | l1.Char = 0
70 | l2.Char = len(b.GetLine(l2.Line))
71 | }
72 | for l := l1.Line; l <= l2.Line; l++ {
73 | start_char := 0
74 | if l == l1.Line {
75 | start_char = l1.Char
76 | }
77 | line_data := b.GetLine(l)
78 | end_char := len(line_data)
79 | if l == l2.Line {
80 | end_char = min(l2.Char+1, len(line_data))
81 | }
82 | data = append(data, line_data[start_char:end_char]...)
83 | if l != l2.Line || end_char == len(line_data) {
84 | data = append(data, '\n')
85 | }
86 | }
87 | return data, l1, l2
88 | }
89 |
90 | func visualModeYank(vt *ViewTree, b *Buffer, kl *KeyList) {
91 | text, l1, _ := visualModeSelection(b)
92 | clipboardSet(defaultClipboard, text)
93 |
94 | b.MoveTo(l1.Char, l1.Line)
95 | exitVisualMode(vt, b, kl)
96 | }
97 | func visualModeDelete(vt *ViewTree, b *Buffer, kl *KeyList) {
98 | text, l1, _ := visualModeSelection(b)
99 | b.MoveTo(l1.Char, l1.Line)
100 | b.Remove(len(text))
101 |
102 | exitVisualMode(vt, b, kl)
103 | }
104 | func visualModePaste(vt *ViewTree, b *Buffer, kl *KeyList) {
105 | text, l1, _ := visualModeSelection(b)
106 | clipboard_text := clipboardGet(defaultClipboard)
107 | b.MoveTo(l1.Char, l1.Line)
108 | b.Remove(len(text))
109 | b.Insert(clipboard_text)
110 | clipboardSet(defaultClipboard, text)
111 |
112 | b.MoveTo(l1.Char, l1.Line)
113 | exitVisualMode(vt, b, kl)
114 | }
115 | func visualModeChange(vt *ViewTree, b *Buffer, kl *KeyList) {
116 | text, l1, _ := visualModeSelection(b)
117 | b.MoveTo(l1.Char, l1.Line)
118 | b.Remove(len(text))
119 |
120 | exitVisualMode(vt, b, kl)
121 | enterInsertMode(vt, b, kl)
122 | }
123 |
--------------------------------------------------------------------------------
/ryy/modes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 | "strings"
7 |
8 | "github.com/gdamore/tcell"
9 | )
10 |
11 | type CommandFn func(*ViewTree, *Buffer, *KeyList)
12 |
13 | type ModeBinding struct {
14 | k *KeyList
15 | f CommandFn
16 | }
17 |
18 | type Mode struct {
19 | name string
20 | bindings []*ModeBinding
21 | }
22 |
23 | var modes = map[string]*Mode{}
24 |
25 | func modeHandle(m *Mode, kl *KeyList) *KeyList {
26 | var match *KeyList = nil
27 | var matchBinding *ModeBinding = nil
28 | for _, binding := range m.bindings {
29 | if matched := kl.HasSuffix(binding.k); matched != nil {
30 | if match == nil || len(matched.keys) > len(match.keys) {
31 | matchBinding = binding
32 | match = matched
33 | }
34 | }
35 | }
36 | if match != nil {
37 | matchBinding.f(currentViewTree, currentViewTree.Leaf.Buf, match)
38 | return match
39 | }
40 | return nil
41 | }
42 |
43 | func findMode(name string) *Mode {
44 | if m, ok := modes[name]; ok {
45 | return m
46 | } else {
47 | return nil
48 | }
49 | }
50 |
51 | func mustFindMode(name string) *Mode {
52 | mode := findMode(name)
53 | if mode == nil {
54 | panic(fmt.Sprintf("no mode named '%s'", name))
55 | }
56 | return mode
57 | }
58 |
59 | // Adds a new empty mode to the mode list, if not already present
60 | func addMode(name string) {
61 | if _, ok := modes[name]; !ok {
62 | modes[name] = &Mode{name: name, bindings: []*ModeBinding{}}
63 | }
64 | }
65 |
66 | func bind(mode_name string, k *KeyList, f CommandFn) {
67 | mode := mustFindMode(mode_name)
68 |
69 | // If this key is bound, update bound function
70 | for _, binding := range mode.bindings {
71 | if k.String() == binding.k.String() {
72 | binding.f = f
73 | }
74 | }
75 | // Else, it's a new binding, add it
76 | mode.bindings = append(mode.bindings, &ModeBinding{k: k, f: f})
77 | }
78 |
79 | func initModes() {
80 | addMode("normal")
81 | bind("normal", k("m $alpha"), commandMark)
82 | bind("normal", k("' $alpha"), commandMoveToMark)
83 | bind("normal", k(":"), promptCommand)
84 | bind("normal", k("h"), moveLeft)
85 | bind("normal", k("j"), moveDown)
86 | bind("normal", k("k"), moveUp)
87 | bind("normal", k("l"), moveRight)
88 | bind("normal", k("0"), moveLineBeg)
89 | bind("normal", k("$"), moveLineEnd)
90 | bind("normal", k("g g"), moveTop)
91 | bind("normal", k("G"), moveBottom)
92 | bind("normal", k("C-u"), moveJumpUp)
93 | bind("normal", k("C-d"), moveJumpDown)
94 | bind("normal", k("z z"), moveCenterLine)
95 | bind("normal", k("w"), moveWordForward)
96 | bind("normal", k("e"), moveWordEndForward)
97 | bind("normal", k("b"), moveWordBackward)
98 | bind("normal", k("C-c"), cancelKeysEntered)
99 | bind("normal", k("C-g"), cancelKeysEntered)
100 | bind("normal", k("ESC ESC"), cancelKeysEntered)
101 | bind("normal", k("i"), enterInsertMode)
102 | bind("normal", k("a"), enterInsertModeAppend)
103 | bind("normal", k("A"), enterInsertModeEol)
104 | bind("normal", k("o"), enterInsertModeNl)
105 | bind("normal", k("O"), enterInsertModeNlUp)
106 | bind("normal", k("x"), removeChar)
107 | bind("normal", k("d d"), removeLine)
108 | bind("normal", k("y y"), commandCopyLine)
109 | bind("normal", k("p"), commandPaste)
110 | bind("normal", k("u"), commandUndo)
111 | bind("normal", k("C-r"), commandRedo)
112 | bind("normal", k("v"), enterVisualMode)
113 | bind("normal", k("V"), enterVisualBlockMode)
114 |
115 | addMode("insert")
116 | bind("insert", k("ESC"), enterNormalMode)
117 | bind("insert", k("C-c"), enterNormalMode)
118 | bind("insert", k("RET"), insertEnter)
119 | bind("insert", k("BAK"), insertBackspace)
120 | bind("insert", k("$any"), insert)
121 |
122 | addMode("prompt")
123 | bind("prompt", k("C-c"), promptCancel)
124 | bind("prompt", k("C-g"), promptCancel)
125 | bind("prompt", k("ESC"), promptCancel)
126 | bind("prompt", k("RET"), promptFinish)
127 | bind("prompt", k("BAK"), promptBackspace)
128 | bind("prompt", k("$any"), promptInsert)
129 |
130 | addMode("buffers")
131 | bind("buffers", k("q"), func(vt *ViewTree, b *Buffer, kl *KeyList) {
132 | closeCurrentBuffer(true)
133 | })
134 | bind("buffers", k("RET"), func(vt *ViewTree, b *Buffer, kl *KeyList) {
135 | closeCurrentBuffer(true)
136 | showBuffer(string(b.Data[b.Cursor.Line]))
137 | })
138 |
139 | addMode("directory")
140 | bind("directory", k("q"), func(vt *ViewTree, b *Buffer, kl *KeyList) {
141 | closeCurrentBuffer(true)
142 | })
143 | bind("directory", k("RET"), func(vt *ViewTree, b *Buffer, kl *KeyList) {
144 | closeBuffer(currentViewTree.Leaf.Buf)
145 | selectAvailableBuffer(false)
146 | file_path := filepath.Join(b.Path, string(b.Data[b.Cursor.Line]))
147 | runCommand([]string{"edit", file_path})
148 | })
149 |
150 | // TODO Remove once we have user configurable bindings
151 | bind("normal", k("SPC b"), func(vt *ViewTree, b *Buffer, kl *KeyList) {
152 | runCommand([]string{"buffers"})
153 | })
154 | editCurrentFolder := func(vt *ViewTree, b *Buffer, kl *KeyList) {
155 | if b.Path == "" {
156 | runCommand([]string{"edit", "."})
157 | } else {
158 | runCommand([]string{"edit", filepath.Dir(b.Path)})
159 | }
160 | }
161 | bind("normal", k("SPC f"), editCurrentFolder)
162 | bind("normal", k("-"), editCurrentFolder)
163 | bind("normal", k("SPC n"), func(vt *ViewTree, b *Buffer, kl *KeyList) {
164 | runCommand([]string{"clearsearch"})
165 | })
166 | }
167 |
168 | func moveLeft(vt *ViewTree, b *Buffer, kl *KeyList) {
169 | b.Move(-1, 0)
170 | }
171 | func moveRight(vt *ViewTree, b *Buffer, kl *KeyList) {
172 | b.Move(1, 0)
173 | }
174 | func moveUp(vt *ViewTree, b *Buffer, kl *KeyList) {
175 | b.Move(0, -1)
176 | }
177 | func moveDown(vt *ViewTree, b *Buffer, kl *KeyList) {
178 | b.Move(0, 1)
179 | }
180 | func moveLineBeg(vt *ViewTree, b *Buffer, kl *KeyList) {
181 | b.MoveTo(0, b.Cursor.Line)
182 | }
183 | func moveLineEnd(vt *ViewTree, b *Buffer, kl *KeyList) {
184 | b.MoveTo(len(b.Data[b.Cursor.Line]), b.Cursor.Line)
185 | }
186 | func moveTop(vt *ViewTree, b *Buffer, kl *KeyList) {
187 | b.MoveTo(0, 0)
188 | }
189 | func moveBottom(vt *ViewTree, b *Buffer, kl *KeyList) {
190 | b.MoveTo(0, len(b.Data)-1)
191 | }
192 | func moveJumpUp(vt *ViewTree, b *Buffer, kl *KeyList) {
193 | b.Move(0, -15)
194 | }
195 | func moveJumpDown(vt *ViewTree, b *Buffer, kl *KeyList) {
196 | b.Move(0, 15)
197 | }
198 | func moveCenterLine(vt *ViewTree, b *Buffer, kl *KeyList) {
199 | vt.Leaf.CenterPending = true
200 | }
201 | func moveWordBackward(vt *ViewTree, b *Buffer, kl *KeyList) {
202 | b.MoveWordBackward()
203 | }
204 | func moveWordForward(vt *ViewTree, b *Buffer, kl *KeyList) {
205 | b.MoveWordForward()
206 | }
207 | func moveWordEndForward(vt *ViewTree, b *Buffer, kl *KeyList) {
208 | b.MoveWordEndForward()
209 | }
210 |
211 | func cancelKeysEntered(vt *ViewTree, b *Buffer, kl *KeyList) {
212 | keysEntered = k("")
213 | }
214 |
215 | // Enter in a new mode
216 | func enterMode(mode string) {
217 | editorMode = mode
218 | // TODO maybe not the best place to clear this
219 | message("")
220 | }
221 |
222 | func enterNormalMode(vt *ViewTree, b *Buffer, kl *KeyList) {
223 | moveLeft(vt, b, kl)
224 | enterMode("normal")
225 | }
226 | func enterInsertMode(vt *ViewTree, b *Buffer, kl *KeyList) {
227 | enterMode("insert")
228 | }
229 | func enterInsertModeAppend(vt *ViewTree, b *Buffer, kl *KeyList) {
230 | moveRight(vt, b, kl)
231 | enterMode("insert")
232 | }
233 | func enterInsertModeEol(vt *ViewTree, b *Buffer, kl *KeyList) {
234 | moveLineEnd(vt, b, kl)
235 | enterMode("insert")
236 | }
237 | func enterInsertModeNl(vt *ViewTree, b *Buffer, kl *KeyList) {
238 | moveLineEnd(vt, b, kl)
239 | b.Insert([]rune("\n"))
240 | b.Move(0, 1) // ensure a valid position
241 | enterMode("insert")
242 | }
243 | func enterInsertModeNlUp(vt *ViewTree, b *Buffer, kl *KeyList) {
244 | moveLineBeg(vt, b, kl)
245 | b.Insert([]rune("\n"))
246 | b.Move(0, 0) // ensure a valid position
247 | enterMode("insert")
248 | }
249 |
250 | func insertEnter(vt *ViewTree, b *Buffer, kl *KeyList) {
251 | i := 0
252 | for ; i < len(b.Data[b.Cursor.Line]) && isSpace(b.Data[b.Cursor.Line][i]); i++ {
253 | }
254 | b.Insert([]rune("\n" + strings.Repeat(" ", i)))
255 | b.MoveTo(i, b.Cursor.Line+1)
256 | }
257 | func insertBackspace(vt *ViewTree, b *Buffer, kl *KeyList) {
258 | if b.Cursor.Char == 0 {
259 | if b.Cursor.Line != 0 {
260 | moveUp(vt, b, kl)
261 | moveLineEnd(vt, b, kl)
262 | b.Remove(1)
263 | }
264 | } else {
265 | if b.CharAtLeft() != ' ' {
266 | b.Move(-1, 0)
267 | b.Remove(1)
268 | return
269 | }
270 | // handle spaces
271 | delete_n := 1
272 | if configGetBool("tab_to_spaces", b) {
273 | delete_n = int(configGetNumber("tab_width", b))
274 | }
275 | for i := 0; i < delete_n && b.CharAtLeft() == ' '; i++ {
276 | b.Move(-1, 0)
277 | b.Remove(1)
278 | }
279 | }
280 | }
281 | func insert(vt *ViewTree, b *Buffer, kl *KeyList) {
282 | k := kl.keys[len(kl.keys)-1]
283 | if k.Key == tcell.KeyTab {
284 | if configGetBool("tab_to_spaces", b) {
285 | tabWidth := int(configGetNumber("tab_width", b))
286 | b.Insert([]rune(strings.Repeat(" ", tabWidth)))
287 | b.Move(tabWidth, 0)
288 | } else {
289 | b.Insert([]rune{'\t'})
290 | b.Move(1, 0)
291 | }
292 | } else if k.Key == tcell.KeyRune && k.Mod == 0 {
293 | b.Insert([]rune{k.Chr})
294 | moveRight(vt, b, kl)
295 | } else {
296 | message("Can't insert '" + kl.String() + "'")
297 | }
298 | }
299 |
300 | func removeChar(vt *ViewTree, b *Buffer, kl *KeyList) {
301 | removed := b.Remove(1)
302 | clipboardSet(defaultClipboard, removed)
303 | }
304 | func removeLine(vt *ViewTree, b *Buffer, kl *KeyList) {
305 | moveLineBeg(vt, b, kl)
306 | removed := b.Remove(len(b.Data[b.Cursor.Line]) + 1)
307 | clipboardSet(defaultClipboard, removed)
308 | }
309 |
310 | func commandUndo(vt *ViewTree, b *Buffer, kl *KeyList) {
311 | b.Undo()
312 | }
313 | func commandRedo(vt *ViewTree, b *Buffer, kl *KeyList) {
314 | b.Redo()
315 | }
316 | func commandCopyLine(vt *ViewTree, b *Buffer, kl *KeyList) {
317 | value := make([]rune, len(b.Data[b.Cursor.Line])+1)
318 | copy(value, b.Data[b.Cursor.Line])
319 | value[len(value)-1] = '\n'
320 | clipboardSet(defaultClipboard, value)
321 | }
322 | func commandPaste(vt *ViewTree, b *Buffer, kl *KeyList) {
323 | value := clipboardGet(defaultClipboard)
324 | if len(value) == 0 {
325 | message("Nothing to paste!")
326 | return
327 | }
328 | b.Move(1, 0)
329 | b.Insert(value)
330 | }
331 |
332 | func commandMark(vt *ViewTree, b *Buffer, kl *KeyList) {
333 | mark_letter := kl.keys[len(kl.keys)-1].Chr
334 | markCreate(mark_letter, b)
335 | }
336 | func commandMoveToMark(vt *ViewTree, b *Buffer, kl *KeyList) {
337 | mark_letter := kl.keys[len(kl.keys)-1].Chr
338 | markJump(mark_letter)
339 | }
340 |
--------------------------------------------------------------------------------
/ryy/prompt.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/gdamore/tcell"
7 | )
8 |
9 | var (
10 | editorIsPromptActive = false
11 | editorPrompt = ""
12 | editorPromptValue = ""
13 | editorPromptCallbackFn func([]string) = nil
14 | editorPromptCompletionFn func(string) []string = nil
15 | )
16 |
17 | func prompt(prompt string, compFn func(string) []string, cbFn func([]string)) {
18 | editorPrompt = prompt
19 | editorPromptValue = ""
20 | editorPromptCallbackFn = cbFn
21 | editorPromptCompletionFn = compFn
22 | enterMode("prompt")
23 | }
24 |
25 | func noopComplete(prefix string) []string {
26 | return []string{}
27 | }
28 |
29 | func promptUpdateCompletion() {
30 | // TODO
31 | }
32 |
33 | func promptCancel(vt *ViewTree, b *Buffer, kl *KeyList) {
34 | enterMode("normal")
35 | }
36 |
37 | func promptFinish(vt *ViewTree, b *Buffer, kl *KeyList) {
38 | enterMode("normal")
39 | // TODO better args parsing
40 | editorPromptCallbackFn(strings.Split(editorPromptValue, " "))
41 | }
42 |
43 | func promptBackspace(vt *ViewTree, b *Buffer, kl *KeyList) {
44 | if len(editorPromptValue) > 0 {
45 | editorPromptValue = editorPromptValue[:len(editorPromptValue)-1]
46 | promptUpdateCompletion()
47 | }
48 | }
49 |
50 | func promptInsert(vt *ViewTree, b *Buffer, kl *KeyList) {
51 | k := kl.keys[len(kl.keys)-1]
52 | if k.Key == tcell.KeyRune && k.Mod == 0 {
53 | editorPromptValue += string(k.Chr)
54 | promptUpdateCompletion()
55 | }
56 | }
57 |
58 | func promptCommand(vt *ViewTree, b *Buffer, kl *KeyList) {
59 | prompt(":", func(prefix string) []string {
60 | // TODO provide command suggestions
61 | return []string{}
62 | }, runCommand)
63 | }
64 |
--------------------------------------------------------------------------------
/ryy/render.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | )
7 |
8 | func render() {
9 | width, height := editorWidth, editorHeight
10 |
11 | screen.Clear()
12 |
13 | renderViewTree(rootViewTree, 0, 0, width, height-1)
14 |
15 | renderMessageBar(width, height)
16 |
17 | screen.Show()
18 | }
19 |
20 | func renderMessageBar(width, height int) {
21 | s := style("default")
22 |
23 | if editorMode == "prompt" {
24 | p := editorPrompt + editorPromptValue
25 | write(s, 0, height-1, p)
26 | write(s.Reverse(true), len(p), height-1, " ")
27 | return
28 | }
29 |
30 | smb := s
31 | if editorMessageType == "error" {
32 | smb = style("message.error")
33 | }
34 | if editorMessage != "" {
35 | write(smb, 0, height-1, editorMessage)
36 | } else {
37 | write(smb, 0, height-1, keysEntered.String())
38 | }
39 | lastKeyText := lastKey.String()
40 | write(s, width-len(lastKeyText)-1, height-1, lastKeyText)
41 | }
42 |
43 | func renderViewTree(vt *ViewTree, x, y, w, h int) {
44 | if vt.Leaf != nil {
45 | renderView(vt.Leaf, x, y, w, h)
46 | return
47 | }
48 | panic("unreachable")
49 | }
50 |
51 | func renderView(v *View, x, y, w, h int) {
52 | sc := style("cursor")
53 | sln := style("linenumber")
54 | ssb := style("statusbar")
55 | ssbh := style("statusbar.highlight")
56 | b := v.Buf
57 |
58 | b.LastRenderWidth = w
59 | b.LastRenderHeight = h
60 |
61 | styleMap := highlighting_styles(b)
62 |
63 | gutterw := len(strconv.Itoa(len(b.Data))) + 1
64 | sy := y
65 | line := v.LineOffset
66 | for line < len(b.Data) && sy < y+h-1 {
67 | write(sln, x, sy, padl(strconv.Itoa(line+1), gutterw-1, ' '))
68 |
69 | sx := x + gutterw
70 | for c, char := range b.Data[line] {
71 | if v == currentViewTree.Leaf && line == b.Cursor.Line && c == b.Cursor.Char {
72 | sx += write(sc, sx, sy, string(char))
73 | } else {
74 | sx += write(styleMap[line][c], sx, sy, string(char))
75 | }
76 | if sx >= x+w {
77 | break
78 | }
79 | }
80 | if v == currentViewTree.Leaf &&
81 | line == b.Cursor.Line &&
82 | b.Cursor.Char == len(b.Data[b.Cursor.Line]) {
83 | write(sc, sx, sy, " ")
84 | }
85 |
86 | line++
87 | sy++
88 | }
89 |
90 | // Current mode
91 | modeStatus := editorMode
92 | for _, modeName := range b.Modes {
93 | modeStatus += "+" + modeName
94 | }
95 | modeStatus = " " + modeStatus + " "
96 | write(ssbh, x, y+h-1, modeStatus)
97 |
98 | // Position
99 | statusRight := fmt.Sprintf("(%d,%d) %d ", b.Cursor.Char+1, b.Cursor.Line+1, len(b.Data))
100 | write(ssb, x+w-len(statusRight), y+h-1, statusRight)
101 | // File name
102 | statusLeft := " " + b.Name
103 | if b.Modified {
104 | statusLeft += " [+]"
105 | }
106 | write(ssb, x+len(modeStatus), y+h-1, padr(statusLeft, w-len(statusRight)-len(modeStatus), ' '))
107 | }
108 |
--------------------------------------------------------------------------------
/ryy/ry.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/gdamore/tcell"
8 | )
9 |
10 | const (
11 | specialChars = "[]{}()/\\"
12 | )
13 |
14 | var (
15 | keysEntered = NewKeyList("")
16 | lastKey = NewKeyList("")
17 | termEvents = make(chan tcell.Event, 500)
18 | defaultClipboard = '_'
19 | clipboards = map[rune][]rune{'_': []rune{}}
20 | editorMode = "normal"
21 | editorMessage = ""
22 | editorMessageType = "info"
23 | editorWidth = 0
24 | editorHeight = 0
25 | buffers = []*Buffer{}
26 | screen tcell.Screen = nil
27 | rootViewTree *ViewTree = nil
28 | currentViewTree *ViewTree = nil
29 | )
30 |
31 | func main() {
32 | if len(os.Args) == 2 && os.Args[1] == "-v" {
33 | fmt.Println("ry v0.0.0")
34 | os.Exit(0)
35 | }
36 |
37 | defer handlePanics()
38 |
39 | initModes()
40 | initCommands()
41 |
42 | initConfig()
43 | init_hooks()
44 | init_highlighting()
45 | init_search()
46 | initVisual()
47 | initTerm()
48 |
49 | initScreen()
50 | initTermEvents()
51 | initBuffers()
52 | initViews()
53 |
54 | render()
55 |
56 | top:
57 | for {
58 | select {
59 | case ev := <-termEvents:
60 | switch ev := ev.(type) {
61 | case *tcell.EventKey:
62 | if ev.Key() == tcell.KeyCtrlQ {
63 | screen.Fini()
64 | screen = nil
65 | break top
66 | /*
67 | } else if ev.Key() == tcell.KeyEscape {
68 | kl := k("ESC")
69 | enter_normal_mode(current_view_tree, current_view_tree.leaf.buf, kl)
70 | last_key = kl
71 | keys_entered = k("")
72 | */
73 | } else {
74 | keysEntered.AddKey(NewKeyFromEvent(ev))
75 |
76 | buf := currentViewTree.Leaf.Buf
77 | for _, mode_name := range buf.Modes {
78 | if matched := modeHandle(mustFindMode(mode_name), keysEntered); matched != nil {
79 | keysEntered = k("")
80 | lastKey = matched
81 | continue top
82 | }
83 | }
84 | if matched := modeHandle(mustFindMode(editorMode), keysEntered); matched != nil {
85 | keysEntered = k("")
86 | lastKey = matched
87 | continue top
88 | }
89 | }
90 | case *tcell.EventResize:
91 | editorWidth, editorHeight = screen.Size()
92 | }
93 | default:
94 | render()
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/ryy/ryy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Databingo/aih/31676d2ffa7fd0758f37d8e5b21ea464900fa215/ryy/ryy
--------------------------------------------------------------------------------
/ryy/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Databingo/aih/31676d2ffa7fd0758f37d8e5b21ea464900fa215/ryy/screenshot.png
--------------------------------------------------------------------------------
/ryy/search.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "regexp"
5 | "strings"
6 | )
7 |
8 | var (
9 | last_search_buffer *Buffer = nil
10 | last_search = ""
11 | last_search_index = 0
12 | last_search_highlight = false
13 | last_search_results = []*Location{}
14 | )
15 |
16 | func init_search() {
17 | bind("normal", k("/"), handle_search_start)
18 | bind("normal", k("N"), handle_search_prev)
19 | bind("normal", k("n"), handle_search_next)
20 | bind("normal", k("*"), handle_search_search_work_under_cursor)
21 | bind("normal", k("SPC n"), func(vt *ViewTree, b *Buffer, kl *KeyList) {
22 | search_clear()
23 | })
24 |
25 | addCommand("clearsearch", func(args []string) {
26 | search_clear()
27 | })
28 | addAlias("cs", "clearsearch")
29 |
30 | hook_buffer("modified", func(b *Buffer) {
31 | // Update search result indexes on buffer changes
32 | if last_search != "" && b == last_search_buffer {
33 | search_find_matches(b, last_search)
34 | last_search_index = len(last_search_results) - 1
35 | }
36 | })
37 | }
38 |
39 | func search_clear() {
40 | last_search_highlight = false
41 | highlight_buffer(currentViewTree.Leaf.Buf)
42 | }
43 |
44 | func search_find_matches(b *Buffer, search string) {
45 | last_search = search
46 | re := regexp.MustCompile(regexp.QuoteMeta(search))
47 |
48 | last_search_buffer = b
49 | last_search_results = []*Location{}
50 | for i, line := range b.Data {
51 | idxs := re.FindAllStringIndex(string(line), -1)
52 | for _, idx := range idxs {
53 | last_search_results = append(last_search_results, NewLocation(i, idx[0]))
54 | }
55 | }
56 | }
57 |
58 | func search_start(b *Buffer, search string) {
59 | if len(search) > 0 {
60 | search_find_matches(b, search)
61 |
62 | // TODO start index at first match after cursor
63 | last_search_index = len(last_search_results) - 1
64 | search_next(b)
65 | }
66 | }
67 |
68 | func handle_search_start(vt *ViewTree, b *Buffer, kl *KeyList) {
69 | prompt("/", noopComplete, func(args []string) {
70 | search_start(b, strings.Join(args, " "))
71 | })
72 | }
73 |
74 | func search_prev(b *Buffer) {
75 | if len(last_search_results) == 0 {
76 | message("No search result.")
77 | return
78 | }
79 | if last_search_index == 0 {
80 | last_search_index = len(last_search_results) - 1
81 | } else {
82 | last_search_index--
83 | }
84 | last_search_highlight = true
85 | highlight_buffer(currentViewTree.Leaf.Buf)
86 | loc := last_search_results[last_search_index]
87 | b.MoveTo(loc.Char, loc.Line)
88 |
89 | }
90 |
91 | func handle_search_prev(vt *ViewTree, b *Buffer, kl *KeyList) {
92 | search_prev(b)
93 | }
94 |
95 | func search_next(b *Buffer) {
96 | if len(last_search_results) == 0 {
97 | message("No search result.")
98 | return
99 | }
100 | if last_search_index+1 == len(last_search_results) {
101 | last_search_index = 0
102 | } else {
103 | last_search_index++
104 | }
105 | last_search_highlight = true
106 | highlight_buffer(currentViewTree.Leaf.Buf)
107 | loc := last_search_results[last_search_index]
108 | b.MoveTo(loc.Char, loc.Line)
109 |
110 | }
111 |
112 | func handle_search_next(vt *ViewTree, b *Buffer, kl *KeyList) {
113 | search_next(b)
114 | }
115 |
116 | func search_highlight(b *Buffer, l, c int) int {
117 | if !last_search_highlight {
118 | return 0
119 | }
120 | if last_search_buffer != b {
121 | return 0
122 | }
123 | for _, loc := range last_search_results {
124 | if l == loc.Line && c == loc.Char {
125 | return len(last_search)
126 | }
127 | }
128 | return 0
129 | }
130 |
131 | func handle_search_search_work_under_cursor(vt *ViewTree, b *Buffer, kl *KeyList) {
132 | search_start(b, string(b.WordUnderCursor()))
133 | }
134 |
--------------------------------------------------------------------------------
/ryy/styles.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "github.com/gdamore/tcell"
4 |
5 | func style(name string) tcell.Style {
6 | // TODO make table based and configurable
7 | if name == "message.error" {
8 | return tcell.StyleDefault.
9 | Foreground(tcell.ColorMaroon)
10 | }
11 | if name == "statusbar" {
12 | return tcell.StyleDefault.
13 | Foreground(tcell.ColorWhite).
14 | Background(tcell.Color(5))
15 | }
16 | if name == "statusbar.highlight" {
17 | return tcell.StyleDefault.
18 | Foreground(tcell.ColorWhite).
19 | Background(tcell.Color(6))
20 | }
21 | if name == "linenumber" {
22 | return tcell.StyleDefault.
23 | Foreground(tcell.Color(6))
24 | }
25 | if name == "search" {
26 | return tcell.StyleDefault.
27 | Foreground(tcell.ColorWhite).
28 | Background(tcell.ColorOlive)
29 | }
30 | if name == "visual" {
31 | return tcell.StyleDefault.
32 | Foreground(tcell.ColorWhite).
33 | Background(tcell.Color(0))
34 | }
35 | if name == "special" {
36 | return tcell.StyleDefault.
37 | //Foreground(tcell.Color(0))
38 | Foreground(tcell.Color(2))
39 | }
40 | if name == "text.string" {
41 | return tcell.StyleDefault.
42 | Foreground(tcell.ColorNavy)
43 | }
44 | if name == "text.number" {
45 | return tcell.StyleDefault.
46 | //Foreground(tcell.ColorOlive)
47 | Foreground(tcell.Color(2))
48 | }
49 | if name == "text.comment" {
50 | return tcell.StyleDefault.
51 | Foreground(10)
52 | }
53 | if name == "text.reserved" {
54 | return tcell.StyleDefault.
55 | Foreground(tcell.Color(8))
56 | }
57 | if name == "text.special" {
58 | return tcell.StyleDefault.
59 | //Foreground(tcell.Color(6))
60 | Foreground(tcell.Color(2))
61 | }
62 | if name == "cursor" {
63 | return tcell.StyleDefault.Reverse(true)
64 | }
65 | return tcell.StyleDefault.Foreground(tcell.Color(2))
66 | }
67 |
--------------------------------------------------------------------------------
/ryy/terminal/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | log
3 | boxterm
4 |
--------------------------------------------------------------------------------
/ryy/terminal/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2013 James Gray
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without liitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and thismssion notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/ryy/terminal/README.md:
--------------------------------------------------------------------------------
1 | # terminal
2 |
3 | Package terminal is a vt10x terminal emulation backend, influenced
4 | largely by st, rxvt, xterm, and iTerm as reference. Use it for terminal
5 | muxing, a terminal emulation frontend, or wherever else you need
6 | terminal emulation.
7 |
8 | ## Installation
9 |
10 | go get j4k.co/terminal
11 |
12 | New to Go? Have a look at how [import paths work](http://golang.org/doc/code.html#remote).
13 |
14 | _Vendored at 0aabd5e5da824b33403ebde1df03776a0d45c7b2 on Nov 26th 2017._
15 |
--------------------------------------------------------------------------------
/ryy/terminal/color.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | // ANSI color values
4 | const (
5 | Black Color = iota
6 | Red
7 | Green
8 | Yellow
9 | Blue
10 | Magenta
11 | Cyan
12 | LightGrey
13 | DarkGrey
14 | LightRed
15 | LightGreen
16 | LightYellow
17 | LightBlue
18 | LightMagenta
19 | LightCyan
20 | White
21 | )
22 |
23 | // Default colors are potentially distinct to allow for special behavior.
24 | // For example, a transparent background. Otherwise, the simple case is to
25 | // map default colors to another color.
26 | const (
27 | DefaultFG Color = 0x0 + iota
28 | DefaultBG
29 | )
30 |
31 | // Color maps to the ANSI colors [0, 16) and the xterm colors [16, 256).
32 | type Color uint16
33 |
34 | // ANSI returns true if Color is within [0, 16).
35 | func (c Color) ANSI() bool {
36 | return (c < 16)
37 | }
38 |
--------------------------------------------------------------------------------
/ryy/terminal/csi.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 | )
7 |
8 | // CSI (Control Sequence Introducer)
9 | // ESC+[
10 | type csiEscape struct {
11 | buf []byte
12 | args []int
13 | mode byte
14 | priv bool
15 | }
16 |
17 | func (c *csiEscape) reset() {
18 | c.buf = c.buf[:0]
19 | c.args = c.args[:0]
20 | c.mode = 0
21 | c.priv = false
22 | }
23 |
24 | func (c *csiEscape) put(b byte) bool {
25 | c.buf = append(c.buf, b)
26 | if b >= 0x40 && b <= 0x7E || len(c.buf) >= 256 {
27 | c.parse()
28 | return true
29 | }
30 | return false
31 | }
32 |
33 | func (c *csiEscape) parse() {
34 | c.mode = c.buf[len(c.buf)-1]
35 | if len(c.buf) == 1 {
36 | return
37 | }
38 | s := string(c.buf)
39 | c.args = c.args[:0]
40 | if s[0] == '?' {
41 | c.priv = true
42 | s = s[1:]
43 | }
44 | s = s[:len(s)-1]
45 | ss := strings.Split(s, ";")
46 | for _, p := range ss {
47 | i, err := strconv.Atoi(p)
48 | if err != nil {
49 | //t.logf("invalid CSI arg '%s'\n", p)
50 | break
51 | }
52 | c.args = append(c.args, i)
53 | }
54 | }
55 |
56 | func (c *csiEscape) arg(i, def int) int {
57 | if i >= len(c.args) || i < 0 {
58 | return def
59 | }
60 | return c.args[i]
61 | }
62 |
63 | // maxarg takes the maximum of arg(i, def) and def
64 | func (c *csiEscape) maxarg(i, def int) int {
65 | return max(c.arg(i, def), def)
66 | }
67 |
68 | func (t *State) handleCSI() {
69 | c := &t.csi
70 | switch c.mode {
71 | default:
72 | goto unknown
73 | case '@': // ICH - insert blank char
74 | t.insertBlanks(c.arg(0, 1))
75 | case 'A': // CUU - cursor up
76 | t.moveTo(t.cur.x, t.cur.y-c.maxarg(0, 1))
77 | case 'B', 'e': // CUD, VPR - cursor down
78 | t.moveTo(t.cur.x, t.cur.y+c.maxarg(0, 1))
79 | case 'c': // DA - device attributes
80 | if c.arg(0, 0) == 0 {
81 | // TODO: write vt102 id
82 | }
83 | case 'C', 'a': // CUF, HPR - cursor forward
84 | t.moveTo(t.cur.x+c.maxarg(0, 1), t.cur.y)
85 | case 'D': // CUB - cursor backward
86 | t.moveTo(t.cur.x-c.maxarg(0, 1), t.cur.y)
87 | case 'E': // CNL - cursor down and first col
88 | t.moveTo(0, t.cur.y+c.arg(0, 1))
89 | case 'F': // CPL - cursor up and first col
90 | t.moveTo(0, t.cur.y-c.arg(0, 1))
91 | case 'g': // TBC - tabulation clear
92 | switch c.arg(0, 0) {
93 | // clear current tab stop
94 | case 0:
95 | t.tabs[t.cur.x] = false
96 | // clear all tabs
97 | case 3:
98 | for i := range t.tabs {
99 | t.tabs[i] = false
100 | }
101 | default:
102 | goto unknown
103 | }
104 | case 'G', '`': // CHA, HPA - Move to
105 | t.moveTo(c.arg(0, 1)-1, t.cur.y)
106 | case 'H', 'f': // CUP, HVP - move to
107 | t.moveAbsTo(c.arg(1, 1)-1, c.arg(0, 1)-1)
108 | case 'I': // CHT - cursor forward tabulation tab stops
109 | n := c.arg(0, 1)
110 | for i := 0; i < n; i++ {
111 | t.putTab(true)
112 | }
113 | case 'J': // ED - clear screen
114 | // TODO: sel.ob.x = -1
115 | switch c.arg(0, 0) {
116 | case 0: // below
117 | t.clear(t.cur.x, t.cur.y, t.cols-1, t.cur.y)
118 | if t.cur.y < t.rows-1 {
119 | t.clear(0, t.cur.y+1, t.cols-1, t.rows-1)
120 | }
121 | case 1: // above
122 | if t.cur.y > 1 {
123 | t.clear(0, 0, t.cols-1, t.cur.y-1)
124 | }
125 | t.clear(0, t.cur.y, t.cur.x, t.cur.y)
126 | case 2: // all
127 | t.clear(0, 0, t.cols-1, t.rows-1)
128 | default:
129 | goto unknown
130 | }
131 | case 'K': // EL - clear line
132 | switch c.arg(0, 0) {
133 | case 0: // right
134 | t.clear(t.cur.x, t.cur.y, t.cols-1, t.cur.y)
135 | case 1: // left
136 | t.clear(0, t.cur.y, t.cur.x, t.cur.y)
137 | case 2: // all
138 | t.clear(0, t.cur.y, t.cols-1, t.cur.y)
139 | }
140 | case 'S': // SU - scroll lines up
141 | t.scrollUp(t.top, c.arg(0, 1))
142 | case 'T': // SD - scroll lines down
143 | t.scrollDown(t.top, c.arg(0, 1))
144 | case 'L': // IL - insert blank lines
145 | t.insertBlankLines(c.arg(0, 1))
146 | case 'l': // RM - reset mode
147 | t.setMode(c.priv, false, c.args)
148 | case 'M': // DL - delete lines
149 | t.deleteLines(c.arg(0, 1))
150 | case 'X': // ECH - erase chars
151 | t.clear(t.cur.x, t.cur.y, t.cur.x+c.arg(0, 1)-1, t.cur.y)
152 | case 'P': // DCH - delete chars
153 | t.deleteChars(c.arg(0, 1))
154 | case 'Z': // CBT - cursor backward tabulation tab stops
155 | n := c.arg(0, 1)
156 | for i := 0; i < n; i++ {
157 | t.putTab(false)
158 | }
159 | case 'd': // VPA - move to
160 | t.moveAbsTo(t.cur.x, c.arg(0, 1)-1)
161 | case 'h': // SM - set terminal mode
162 | t.setMode(c.priv, true, c.args)
163 | case 'm': // SGR - terminal attribute (color)
164 | t.setAttr(c.args)
165 | case 'r': // DECSTBM - set scrolling region
166 | if c.priv {
167 | goto unknown
168 | } else {
169 | t.setScroll(c.arg(0, 1)-1, c.arg(1, t.rows)-1)
170 | t.moveAbsTo(0, 0)
171 | }
172 | case 's': // DECSC - save cursor position (ANSI.SYS)
173 | t.saveCursor()
174 | case 'u': // DECRC - restore cursor position (ANSI.SYS)
175 | t.restoreCursor()
176 | }
177 | return
178 | unknown: // TODO: get rid of this goto
179 | t.logf("unknown CSI sequence '%c'\n", c.mode)
180 | // TODO: c.dump()
181 | }
182 |
--------------------------------------------------------------------------------
/ryy/terminal/csi_test.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestCSIParse(t *testing.T) {
8 | var csi csiEscape
9 | csi.reset()
10 | csi.buf = []byte("s")
11 | csi.parse()
12 | if csi.mode != 's' || csi.arg(0, 17) != 17 || len(csi.args) != 0 {
13 | t.Fatal("CSI parse mismatch")
14 | }
15 |
16 | csi.reset()
17 | csi.buf = []byte("31T")
18 | csi.parse()
19 | if csi.mode != 'T' || csi.arg(0, 0) != 31 || len(csi.args) != 1 {
20 | t.Fatal("CSI parse mismatch")
21 | }
22 |
23 | csi.reset()
24 | csi.buf = []byte("48;2f")
25 | csi.parse()
26 | if csi.mode != 'f' || csi.arg(0, 0) != 48 || csi.arg(1, 0) != 2 || len(csi.args) != 2 {
27 | t.Fatal("CSI parse mismatch")
28 | }
29 |
30 | csi.reset()
31 | csi.buf = []byte("?25l")
32 | csi.parse()
33 | if csi.mode != 'l' || csi.arg(0, 0) != 25 || csi.priv != true || len(csi.args) != 1 {
34 | t.Fatal("CSI parse mismatch")
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/ryy/terminal/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package terminal is a vt10x terminal emulation backend, influenced
3 | largely by st, rxvt, xterm, and iTerm as reference. Use it for terminal
4 | muxing, a terminal emulation frontend, or wherever else you need
5 | terminal emulation.
6 |
7 | In development, but very usable.
8 | */
9 | package terminal
10 |
--------------------------------------------------------------------------------
/ryy/terminal/ioctl.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "os"
5 | "syscall"
6 | "unsafe"
7 | )
8 |
9 | func ioctl(f *os.File, cmd, p uintptr) error {
10 | _, _, errno := syscall.Syscall(
11 | syscall.SYS_IOCTL,
12 | f.Fd(),
13 | syscall.TIOCSWINSZ,
14 | p)
15 | if errno != 0 {
16 | return syscall.Errno(errno)
17 | }
18 | return nil
19 | }
20 |
21 | func (t *VT) ptyResize() error {
22 | if t.pty == nil {
23 | return nil
24 | }
25 | var w struct{ row, col, xpix, ypix uint16 }
26 | w.row = uint16(t.dest.rows)
27 | w.col = uint16(t.dest.cols)
28 | w.xpix = 16 * uint16(t.dest.cols)
29 | w.ypix = 16 * uint16(t.dest.rows)
30 | return ioctl(t.pty, syscall.TIOCSWINSZ,
31 | uintptr(unsafe.Pointer(&w)))
32 | }
33 |
--------------------------------------------------------------------------------
/ryy/terminal/parse.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | func isControlCode(c rune) bool {
4 | return c < 0x20 || c == 0177
5 | }
6 |
7 | func (t *State) parse(c rune) {
8 | if isControlCode(c) {
9 | if t.handleControlCodes(c) || t.cur.attr.mode&attrGfx == 0 {
10 | return
11 | }
12 | }
13 | // TODO: update selection; see st.c:2450
14 |
15 | if t.mode&ModeWrap != 0 && t.cur.state&cursorWrapNext != 0 {
16 | t.lines[t.cur.y][t.cur.x].mode |= attrWrap
17 | t.newline(true)
18 | }
19 |
20 | if t.mode&ModeInsert != 0 && t.cur.x+1 < t.cols {
21 | // TODO: move shiz, look at st.c:2458
22 | t.logln("insert mode not implemented")
23 | }
24 |
25 | t.setChar(c, &t.cur.attr, t.cur.x, t.cur.y)
26 | if t.cur.x+1 < t.cols {
27 | t.moveTo(t.cur.x+1, t.cur.y)
28 | } else {
29 | t.cur.state |= cursorWrapNext
30 | }
31 | }
32 |
33 | func (t *State) parseEsc(c rune) {
34 | if t.handleControlCodes(c) {
35 | return
36 | }
37 | next := t.parse
38 | switch c {
39 | case '[':
40 | next = t.parseEscCSI
41 | case '#':
42 | next = t.parseEscTest
43 | case 'P', // DCS - Device Control String
44 | '_', // APC - Application Program Command
45 | '^', // PM - Privacy Message
46 | ']', // OSC - Operating System Command
47 | 'k': // old title set compatibility
48 | t.str.reset()
49 | t.str.typ = c
50 | next = t.parseEscStr
51 | case '(': // set primary charset G0
52 | next = t.parseEscAltCharset
53 | case ')', // set secondary charset G1 (ignored)
54 | '*', // set tertiary charset G2 (ignored)
55 | '+': // set quaternary charset G3 (ignored)
56 | case 'D': // IND - linefeed
57 | if t.cur.y == t.bottom {
58 | t.scrollUp(t.top, 1)
59 | } else {
60 | t.moveTo(t.cur.x, t.cur.y+1)
61 | }
62 | case 'E': // NEL - next line
63 | t.newline(true)
64 | case 'H': // HTS - horizontal tab stop
65 | t.tabs[t.cur.x] = true
66 | case 'M': // RI - reverse index
67 | if t.cur.y == t.top {
68 | t.scrollDown(t.top, 1)
69 | } else {
70 | t.moveTo(t.cur.x, t.cur.y-1)
71 | }
72 | case 'Z': // DECID - identify terminal
73 | // TODO: write to our writer our id
74 | case 'c': // RIS - reset to initial state
75 | t.reset()
76 | case '=': // DECPAM - application keypad
77 | t.mode |= ModeAppKeypad
78 | case '>': // DECPNM - normal keypad
79 | t.mode &^= ModeAppKeypad
80 | case '7': // DECSC - save cursor
81 | t.saveCursor()
82 | case '8': // DECRC - restore cursor
83 | t.restoreCursor()
84 | case '\\': // ST - stop
85 | default:
86 | t.logf("unknown ESC sequence '%c'\n", c)
87 | }
88 | t.state = next
89 | }
90 |
91 | func (t *State) parseEscCSI(c rune) {
92 | if t.handleControlCodes(c) {
93 | return
94 | }
95 | if t.csi.put(byte(c)) {
96 | t.state = t.parse
97 | t.handleCSI()
98 | }
99 | }
100 |
101 | func (t *State) parseEscStr(c rune) {
102 | switch c {
103 | case '\033':
104 | t.state = t.parseEscStrEnd
105 | case '\a': // backwards compatiblity to xterm
106 | t.state = t.parse
107 | t.handleSTR()
108 | default:
109 | t.str.put(c)
110 | }
111 | }
112 |
113 | func (t *State) parseEscStrEnd(c rune) {
114 | if t.handleControlCodes(c) {
115 | return
116 | }
117 | t.state = t.parse
118 | if c == '\\' {
119 | t.handleSTR()
120 | }
121 | }
122 |
123 | func (t *State) parseEscAltCharset(c rune) {
124 | if t.handleControlCodes(c) {
125 | return
126 | }
127 | switch c {
128 | case '0': // line drawing set
129 | t.cur.attr.mode |= attrGfx
130 | case 'B': // USASCII
131 | t.cur.attr.mode &^= attrGfx
132 | case 'A', // UK (ignored)
133 | '<', // multinational (ignored)
134 | '5', // Finnish (ignored)
135 | 'C', // Finnish (ignored)
136 | 'K': // German (ignored)
137 | default:
138 | t.logf("unknown alt. charset '%c'\n", c)
139 | }
140 | t.state = t.parse
141 | }
142 |
143 | func (t *State) parseEscTest(c rune) {
144 | if t.handleControlCodes(c) {
145 | return
146 | }
147 | // DEC screen alignment test
148 | if c == '8' {
149 | for y := 0; y < t.rows; y++ {
150 | for x := 0; x < t.cols; x++ {
151 | t.setChar('E', &t.cur.attr, x, y)
152 | }
153 | }
154 | }
155 | t.state = t.parse
156 | }
157 |
158 | func (t *State) handleControlCodes(c rune) bool {
159 | if !isControlCode(c) {
160 | return false
161 | }
162 | switch c {
163 | // HT
164 | case '\t':
165 | t.putTab(true)
166 | // BS
167 | case '\b':
168 | t.moveTo(t.cur.x-1, t.cur.y)
169 | // CR
170 | case '\r':
171 | t.moveTo(0, t.cur.y)
172 | // LF, VT, LF
173 | case '\f', '\v', '\n':
174 | // go to first col if mode is set
175 | t.newline(t.mode&ModeCRLF != 0)
176 | // BEL
177 | case '\a':
178 | // TODO: emit sound
179 | // TODO: window alert if not focused
180 | // ESC
181 | case 033:
182 | t.csi.reset()
183 | t.state = t.parseEsc
184 | // SO, SI
185 | case 016, 017:
186 | // different charsets not supported. apps should use the correct
187 | // alt charset escapes, probably for line drawing
188 | // SUB, CAN
189 | case 032, 030:
190 | t.csi.reset()
191 | // ignore ENQ, NUL, XON, XOFF, DEL
192 | case 005, 000, 021, 023, 0177:
193 | default:
194 | return false
195 | }
196 | return true
197 | }
198 |
--------------------------------------------------------------------------------
/ryy/terminal/state.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "log"
5 | "sync"
6 | )
7 |
8 | const (
9 | tabspaces = 8
10 | )
11 |
12 | const (
13 | attrReverse = 1 << iota
14 | attrUnderline
15 | attrBold
16 | attrGfx
17 | attrItalic
18 | attrBlink
19 | attrWrap
20 | )
21 |
22 | const (
23 | cursorDefault = 1 << iota
24 | cursorWrapNext
25 | cursorOrigin
26 | )
27 |
28 | // ModeFlag represents various terminal mode states.
29 | type ModeFlag uint32
30 |
31 | // Terminal modes
32 | const (
33 | ModeWrap ModeFlag = 1 << iota
34 | ModeInsert
35 | ModeAppKeypad
36 | ModeAltScreen
37 | ModeCRLF
38 | ModeMouseButton
39 | ModeMouseMotion
40 | ModeReverse
41 | ModeKeyboardLock
42 | ModeHide
43 | ModeEcho
44 | ModeAppCursor
45 | ModeMouseSgr
46 | Mode8bit
47 | ModeBlink
48 | ModeFBlink
49 | ModeFocus
50 | ModeMouseX10
51 | ModeMouseMany
52 | ModeMouseMask = ModeMouseButton | ModeMouseMotion | ModeMouseX10 | ModeMouseMany
53 | )
54 |
55 | // ChangeFlag represents possible state changes of the terminal.
56 | type ChangeFlag uint32
57 |
58 | // Terminal changes to occur in VT.ReadState
59 | const (
60 | ChangedScreen ChangeFlag = 1 << iota
61 | ChangedTitle
62 | )
63 |
64 | type glyph struct {
65 | c rune
66 | mode int16
67 | fg, bg Color
68 | }
69 |
70 | type line []glyph
71 |
72 | type cursor struct {
73 | attr glyph
74 | x, y int
75 | state uint8
76 | }
77 |
78 | type parseState func(c rune)
79 |
80 | // State represents the terminal emulation state. Use Lock/Unlock
81 | // methods to synchronize data access with VT.
82 | type State struct {
83 | DebugLogger *log.Logger
84 |
85 | mu sync.Mutex
86 | changed ChangeFlag
87 | cols, rows int
88 | lines []line
89 | altLines []line
90 | dirty []bool // line dirtiness
91 | anydirty bool
92 | cur, curSaved cursor
93 | top, bottom int // scroll limits
94 | mode ModeFlag
95 | state parseState
96 | str strEscape
97 | csi csiEscape
98 | numlock bool
99 | tabs []bool
100 | title string
101 | }
102 |
103 | func (t *State) logf(format string, args ...interface{}) {
104 | if t.DebugLogger != nil {
105 | t.DebugLogger.Printf(format, args...)
106 | }
107 | }
108 |
109 | func (t *State) logln(s string) {
110 | if t.DebugLogger != nil {
111 | t.DebugLogger.Println(s)
112 | }
113 | }
114 |
115 | func (t *State) lock() {
116 | t.mu.Lock()
117 | }
118 |
119 | func (t *State) unlock() {
120 | t.mu.Unlock()
121 | }
122 |
123 | // Lock locks the state object's mutex.
124 | func (t *State) Lock() {
125 | t.mu.Lock()
126 | }
127 |
128 | // Unlock resets change flags and unlocks the state object's mutex.
129 | func (t *State) Unlock() {
130 | t.resetChanges()
131 | t.mu.Unlock()
132 | }
133 |
134 | // Cell returns the character code, foreground color, and background
135 | // color at position (x, y) relative to the top left of the terminal.
136 | func (t *State) Cell(x, y int) (ch rune, fg Color, bg Color) {
137 | return t.lines[y][x].c, Color(t.lines[y][x].fg), Color(t.lines[y][x].bg)
138 | }
139 |
140 | // Cursor returns the current position of the cursor.
141 | func (t *State) Cursor() (int, int) {
142 | return t.cur.x, t.cur.y
143 | }
144 |
145 | // CursorVisible returns the visible state of the cursor.
146 | func (t *State) CursorVisible() bool {
147 | return t.mode&ModeHide == 0
148 | }
149 |
150 | // Mode tests if mode is currently set.
151 | func (t *State) Mode(mode ModeFlag) bool {
152 | return t.mode&mode != 0
153 | }
154 |
155 | // Title returns the current title set via the tty.
156 | func (t *State) Title() string {
157 | return t.title
158 | }
159 |
160 | /*
161 | // ChangeMask returns a bitfield of changes that have occured by VT.
162 | func (t *State) ChangeMask() ChangeFlag {
163 | return t.changed
164 | }
165 | */
166 |
167 | // Changed returns true if change has occured.
168 | func (t *State) Changed(change ChangeFlag) bool {
169 | return t.changed&change != 0
170 | }
171 |
172 | // resetChanges resets the change mask and dirtiness.
173 | func (t *State) resetChanges() {
174 | for i := range t.dirty {
175 | t.dirty[i] = false
176 | }
177 | t.anydirty = false
178 | t.changed = 0
179 | }
180 |
181 | func (t *State) saveCursor() {
182 | t.curSaved = t.cur
183 | }
184 |
185 | func (t *State) restoreCursor() {
186 | t.cur = t.curSaved
187 | t.moveTo(t.cur.x, t.cur.y)
188 | }
189 |
190 | func (t *State) put(c rune) {
191 | t.state(c)
192 | }
193 |
194 | func (t *State) putTab(forward bool) {
195 | x := t.cur.x
196 | if forward {
197 | if x == t.cols {
198 | return
199 | }
200 | for x++; x < t.cols && !t.tabs[x]; x++ {
201 | }
202 | } else {
203 | if x == 0 {
204 | return
205 | }
206 | for x--; x > 0 && !t.tabs[x]; x-- {
207 | }
208 | }
209 | t.moveTo(x, t.cur.y)
210 | }
211 |
212 | func (t *State) newline(firstCol bool) {
213 | y := t.cur.y
214 | if y == t.bottom {
215 | cur := t.cur
216 | t.cur = t.defaultCursor()
217 | t.scrollUp(t.top, 1)
218 | t.cur = cur
219 | } else {
220 | y++
221 | }
222 | if firstCol {
223 | t.moveTo(0, y)
224 | } else {
225 | t.moveTo(t.cur.x, y)
226 | }
227 | }
228 |
229 | // table from st, which in turn is from rxvt :)
230 | var gfxCharTable = [62]rune{
231 | '↑', '↓', '→', '←', '█', '▚', '☃', // A - G
232 | 0, 0, 0, 0, 0, 0, 0, 0, // H - O
233 | 0, 0, 0, 0, 0, 0, 0, 0, // P - W
234 | 0, 0, 0, 0, 0, 0, 0, ' ', // X - _
235 | '◆', '▒', '␉', '␌', '␍', '␊', '°', '±', // ` - g
236 | '', '␋', '┘', '┐', '┌', '└', '┼', '⎺', // h - o
237 | '⎻', '─', '⎼', '⎽', '├', '┤', '┴', '┬', // p - w
238 | '│', '≤', '≥', 'π', '≠', '£', '·', // x - ~
239 | }
240 |
241 | func (t *State) setChar(c rune, attr *glyph, x, y int) {
242 | if attr.mode&attrGfx != 0 {
243 | if c >= 0x41 && c <= 0x7e && gfxCharTable[c-0x41] != 0 {
244 | c = gfxCharTable[c-0x41]
245 | }
246 | }
247 | t.changed |= ChangedScreen
248 | t.dirty[y] = true
249 | t.lines[y][x] = *attr
250 | t.lines[y][x].c = c
251 | //if t.options.BrightBold && attr.mode&attrBold != 0 && attr.fg < 8 {
252 | if attr.mode&attrBold != 0 && attr.fg < 8 {
253 | t.lines[y][x].fg = attr.fg + 8
254 | }
255 | if attr.mode&attrReverse != 0 {
256 | t.lines[y][x].fg = attr.bg
257 | t.lines[y][x].bg = attr.fg
258 | }
259 | }
260 |
261 | func (t *State) defaultCursor() cursor {
262 | c := cursor{}
263 | c.attr.fg = DefaultFG
264 | c.attr.bg = DefaultBG
265 | return c
266 | }
267 |
268 | func (t *State) reset() {
269 | t.cur = t.defaultCursor()
270 | t.saveCursor()
271 | for i := range t.tabs {
272 | t.tabs[i] = false
273 | }
274 | for i := tabspaces; i < len(t.tabs); i += tabspaces {
275 | t.tabs[i] = true
276 | }
277 | t.top = 0
278 | t.bottom = t.rows - 1
279 | t.mode = ModeWrap
280 | t.clear(0, 0, t.rows-1, t.cols-1)
281 | t.moveTo(0, 0)
282 | }
283 |
284 | // TODO: definitely can improve allocs
285 | func (t *State) resize(cols, rows int) bool {
286 | if cols == t.cols && rows == t.rows {
287 | return false
288 | }
289 | if cols < 1 || rows < 1 {
290 | return false
291 | }
292 | slide := t.cur.y - rows + 1
293 | if slide > 0 {
294 | copy(t.lines, t.lines[slide:slide+rows])
295 | copy(t.altLines, t.altLines[slide:slide+rows])
296 | }
297 |
298 | lines, altLines, tabs := t.lines, t.altLines, t.tabs
299 | t.lines = make([]line, rows)
300 | t.altLines = make([]line, rows)
301 | t.dirty = make([]bool, rows)
302 | t.tabs = make([]bool, cols)
303 |
304 | minrows := min(rows, t.rows)
305 | mincols := min(cols, t.cols)
306 | t.changed |= ChangedScreen
307 | for i := 0; i < rows; i++ {
308 | t.dirty[i] = true
309 | t.lines[i] = make(line, cols)
310 | t.altLines[i] = make(line, cols)
311 | }
312 | for i := 0; i < minrows; i++ {
313 | copy(t.lines[i], lines[i])
314 | copy(t.altLines[i], altLines[i])
315 | }
316 | copy(t.tabs, tabs)
317 | if cols > t.cols {
318 | i := t.cols - 1
319 | for i > 0 && !tabs[i] {
320 | i--
321 | }
322 | for i += tabspaces; i < len(tabs); i += tabspaces {
323 | tabs[i] = true
324 | }
325 | }
326 |
327 | t.cols = cols
328 | t.rows = rows
329 | t.setScroll(0, rows-1)
330 | t.moveTo(t.cur.x, t.cur.y)
331 | for i := 0; i < 2; i++ {
332 | if mincols < cols && minrows > 0 {
333 | t.clear(mincols, 0, cols-1, minrows-1)
334 | }
335 | if cols > 0 && minrows < rows {
336 | t.clear(0, minrows, cols-1, rows-1)
337 | }
338 | t.swapScreen()
339 | }
340 | return slide > 0
341 | }
342 |
343 | func (t *State) clear(x0, y0, x1, y1 int) {
344 | if x0 > x1 {
345 | x0, x1 = x1, x0
346 | }
347 | if y0 > y1 {
348 | y0, y1 = y1, y0
349 | }
350 | x0 = clamp(x0, 0, t.cols-1)
351 | x1 = clamp(x1, 0, t.cols-1)
352 | y0 = clamp(y0, 0, t.rows-1)
353 | y1 = clamp(y1, 0, t.rows-1)
354 | t.changed |= ChangedScreen
355 | for y := y0; y <= y1; y++ {
356 | t.dirty[y] = true
357 | for x := x0; x <= x1; x++ {
358 | t.lines[y][x] = t.cur.attr
359 | t.lines[y][x].c = ' '
360 | }
361 | }
362 | }
363 |
364 | func (t *State) clearAll() {
365 | t.clear(0, 0, t.cols-1, t.rows-1)
366 | }
367 |
368 | func (t *State) moveAbsTo(x, y int) {
369 | if t.cur.state&cursorOrigin != 0 {
370 | y += t.top
371 | }
372 | t.moveTo(x, y)
373 | }
374 |
375 | func (t *State) moveTo(x, y int) {
376 | var miny, maxy int
377 | if t.cur.state&cursorOrigin != 0 {
378 | miny = t.top
379 | maxy = t.bottom
380 | } else {
381 | miny = 0
382 | maxy = t.rows - 1
383 | }
384 | x = clamp(x, 0, t.cols-1)
385 | y = clamp(y, miny, maxy)
386 | t.changed |= ChangedScreen
387 | t.cur.state &^= cursorWrapNext
388 | t.cur.x = x
389 | t.cur.y = y
390 | }
391 |
392 | func (t *State) swapScreen() {
393 | t.lines, t.altLines = t.altLines, t.lines
394 | t.mode ^= ModeAltScreen
395 | t.dirtyAll()
396 | }
397 |
398 | func (t *State) dirtyAll() {
399 | t.changed |= ChangedScreen
400 | for y := 0; y < t.rows; y++ {
401 | t.dirty[y] = true
402 | }
403 | }
404 |
405 | func (t *State) setScroll(top, bottom int) {
406 | top = clamp(top, 0, t.rows-1)
407 | bottom = clamp(bottom, 0, t.rows-1)
408 | if top > bottom {
409 | top, bottom = bottom, top
410 | }
411 | t.top = top
412 | t.bottom = bottom
413 | }
414 |
415 | func min(a, b int) int {
416 | if a < b {
417 | return a
418 | }
419 | return b
420 | }
421 |
422 | func max(a, b int) int {
423 | if a > b {
424 | return a
425 | }
426 | return b
427 | }
428 |
429 | func clamp(val, min, max int) int {
430 | if val < min {
431 | return min
432 | } else if val > max {
433 | return max
434 | }
435 | return val
436 | }
437 |
438 | func between(val, min, max int) bool {
439 | if val < min || val > max {
440 | return false
441 | }
442 | return true
443 | }
444 |
445 | func (t *State) scrollDown(orig, n int) {
446 | n = clamp(n, 0, t.bottom-orig+1)
447 | t.clear(0, t.bottom-n+1, t.cols-1, t.bottom)
448 | t.changed |= ChangedScreen
449 | for i := t.bottom; i >= orig+n; i-- {
450 | t.lines[i], t.lines[i-n] = t.lines[i-n], t.lines[i]
451 | t.dirty[i] = true
452 | t.dirty[i-n] = true
453 | }
454 |
455 | // TODO: selection scroll
456 | }
457 |
458 | func (t *State) scrollUp(orig, n int) {
459 | n = clamp(n, 0, t.bottom-orig+1)
460 | t.clear(0, orig, t.cols-1, orig+n-1)
461 | t.changed |= ChangedScreen
462 | for i := orig; i <= t.bottom-n; i++ {
463 | t.lines[i], t.lines[i+n] = t.lines[i+n], t.lines[i]
464 | t.dirty[i] = true
465 | t.dirty[i+n] = true
466 | }
467 |
468 | // TODO: selection scroll
469 | }
470 |
471 | func (t *State) modMode(set bool, bit ModeFlag) {
472 | if set {
473 | t.mode |= bit
474 | } else {
475 | t.mode &^= bit
476 | }
477 | }
478 |
479 | func (t *State) setMode(priv bool, set bool, args []int) {
480 | if priv {
481 | for _, a := range args {
482 | switch a {
483 | case 1: // DECCKM - cursor key
484 | t.modMode(set, ModeAppCursor)
485 | case 5: // DECSCNM - reverse video
486 | mode := t.mode
487 | t.modMode(set, ModeReverse)
488 | if mode != t.mode {
489 | // TODO: redraw
490 | }
491 | case 6: // DECOM - origin
492 | if set {
493 | t.cur.state |= cursorOrigin
494 | } else {
495 | t.cur.state &^= cursorOrigin
496 | }
497 | t.moveAbsTo(0, 0)
498 | case 7: // DECAWM - auto wrap
499 | t.modMode(set, ModeWrap)
500 | // IGNORED:
501 | case 0, // error
502 | 2, // DECANM - ANSI/VT52
503 | 3, // DECCOLM - column
504 | 4, // DECSCLM - scroll
505 | 8, // DECARM - auto repeat
506 | 18, // DECPFF - printer feed
507 | 19, // DECPEX - printer extent
508 | 42, // DECNRCM - national characters
509 | 12: // att610 - start blinking cursor
510 | break
511 | case 25: // DECTCEM - text cursor enable mode
512 | t.modMode(!set, ModeHide)
513 | case 9: // X10 mouse compatibility mode
514 | t.modMode(false, ModeMouseMask)
515 | t.modMode(set, ModeMouseX10)
516 | case 1000: // report button press
517 | t.modMode(false, ModeMouseMask)
518 | t.modMode(set, ModeMouseButton)
519 | case 1002: // report motion on button press
520 | t.modMode(false, ModeMouseMask)
521 | t.modMode(set, ModeMouseMotion)
522 | case 1003: // enable all mouse motions
523 | t.modMode(false, ModeMouseMask)
524 | t.modMode(set, ModeMouseMany)
525 | case 1004: // send focus events to tty
526 | t.modMode(set, ModeFocus)
527 | case 1006: // extended reporting mode
528 | t.modMode(set, ModeMouseSgr)
529 | case 1034:
530 | t.modMode(set, Mode8bit)
531 | case 1049, // = 1047 and 1048
532 | 47, 1047:
533 | alt := t.mode&ModeAltScreen != 0
534 | if alt {
535 | t.clear(0, 0, t.cols-1, t.rows-1)
536 | }
537 | if !set || !alt {
538 | t.swapScreen()
539 | }
540 | if a != 1049 {
541 | break
542 | }
543 | fallthrough
544 | case 1048:
545 | if set {
546 | t.saveCursor()
547 | } else {
548 | t.restoreCursor()
549 | }
550 | case 1001:
551 | // mouse highlight mode; can hang the terminal by design when
552 | // implemented
553 | case 1005:
554 | // utf8 mouse mode; will confuse applications not supporting
555 | // utf8 and luit
556 | case 1015:
557 | // urxvt mangled mouse mode; incompatiblt and can be mistaken
558 | // for other control codes
559 | default:
560 | t.logf("unknown private set/reset mode %d\n", a)
561 | }
562 | }
563 | } else {
564 | for _, a := range args {
565 | switch a {
566 | case 0: // Error (ignored)
567 | case 2: // KAM - keyboard action
568 | t.modMode(set, ModeKeyboardLock)
569 | case 4: // IRM - insertion-replacement
570 | t.modMode(set, ModeInsert)
571 | t.logln("insert mode not implemented")
572 | case 12: // SRM - send/receive
573 | t.modMode(set, ModeEcho)
574 | case 20: // LNM - linefeed/newline
575 | t.modMode(set, ModeCRLF)
576 | case 34:
577 | t.logln("right-to-left mode not implemented")
578 | case 96:
579 | t.logln("right-to-left copy mode not implemented")
580 | default:
581 | t.logf("unknown set/reset mode %d\n", a)
582 | }
583 | }
584 | }
585 | }
586 |
587 | func (t *State) setAttr(attr []int) {
588 | if len(attr) == 0 {
589 | attr = []int{0}
590 | }
591 | for i := 0; i < len(attr); i++ {
592 | a := attr[i]
593 | switch a {
594 | case 0:
595 | t.cur.attr.mode &^= attrReverse | attrUnderline | attrBold | attrItalic | attrBlink
596 | t.cur.attr.fg = DefaultFG
597 | t.cur.attr.bg = DefaultBG
598 | case 1:
599 | t.cur.attr.mode |= attrBold
600 | case 3:
601 | t.cur.attr.mode |= attrItalic
602 | case 4:
603 | t.cur.attr.mode |= attrUnderline
604 | case 5, 6: // slow, rapid blink
605 | t.cur.attr.mode |= attrBlink
606 | case 7:
607 | t.cur.attr.mode |= attrReverse
608 | case 21, 22:
609 | t.cur.attr.mode &^= attrBold
610 | case 23:
611 | t.cur.attr.mode &^= attrItalic
612 | case 24:
613 | t.cur.attr.mode &^= attrUnderline
614 | case 25, 26:
615 | t.cur.attr.mode &^= attrBlink
616 | case 27:
617 | t.cur.attr.mode &^= attrReverse
618 | case 38:
619 | if i+2 < len(attr) && attr[i+1] == 5 {
620 | i += 2
621 | if between(attr[i], 0, 255) {
622 | t.cur.attr.fg = Color(attr[i])
623 | } else {
624 | t.logf("bad fgcolor %d\n", attr[i])
625 | }
626 | } else {
627 | t.logf("gfx attr %d unknown\n", a)
628 | }
629 | case 39:
630 | t.cur.attr.fg = DefaultFG
631 | case 48:
632 | if i+2 < len(attr) && attr[i+1] == 5 {
633 | i += 2
634 | if between(attr[i], 0, 255) {
635 | t.cur.attr.bg = Color(attr[i])
636 | } else {
637 | t.logf("bad bgcolor %d\n", attr[i])
638 | }
639 | } else {
640 | t.logf("gfx attr %d unknown\n", a)
641 | }
642 | case 49:
643 | t.cur.attr.bg = DefaultBG
644 | default:
645 | if between(a, 30, 37) {
646 | t.cur.attr.fg = Color(a - 30)
647 | } else if between(a, 40, 47) {
648 | t.cur.attr.bg = Color(a - 40)
649 | } else if between(a, 90, 97) {
650 | t.cur.attr.fg = Color(a - 90 + 8)
651 | } else if between(a, 100, 107) {
652 | t.cur.attr.bg = Color(a - 100 + 8)
653 | } else {
654 | t.logf("gfx attr %d unknown\n", a)
655 | }
656 | }
657 | }
658 | }
659 |
660 | func (t *State) insertBlanks(n int) {
661 | src := t.cur.x
662 | dst := src + n
663 | size := t.cols - dst
664 | t.changed |= ChangedScreen
665 | t.dirty[t.cur.y] = true
666 |
667 | if dst >= t.cols {
668 | t.clear(t.cur.x, t.cur.y, t.cols-1, t.cur.y)
669 | } else {
670 | copy(t.lines[t.cur.y][dst:dst+size], t.lines[t.cur.y][src:src+size])
671 | t.clear(src, t.cur.y, dst-1, t.cur.y)
672 | }
673 | }
674 |
675 | func (t *State) insertBlankLines(n int) {
676 | if t.cur.y < t.top || t.cur.y > t.bottom {
677 | return
678 | }
679 | t.scrollDown(t.cur.y, n)
680 | }
681 |
682 | func (t *State) deleteLines(n int) {
683 | if t.cur.y < t.top || t.cur.y > t.bottom {
684 | return
685 | }
686 | t.scrollUp(t.cur.y, n)
687 | }
688 |
689 | func (t *State) deleteChars(n int) {
690 | src := t.cur.x + n
691 | dst := t.cur.x
692 | size := t.cols - src
693 | t.changed |= ChangedScreen
694 | t.dirty[t.cur.y] = true
695 |
696 | if src >= t.cols {
697 | t.clear(t.cur.x, t.cur.y, t.cols-1, t.cur.y)
698 | } else {
699 | copy(t.lines[t.cur.y][dst:dst+size], t.lines[t.cur.y][src:src+size])
700 | t.clear(t.cols-n, t.cur.y, t.cols-1, t.cur.y)
701 | }
702 | }
703 |
704 | func (t *State) setTitle(title string) {
705 | t.changed |= ChangedTitle
706 | t.title = title
707 | }
708 |
--------------------------------------------------------------------------------
/ryy/terminal/str.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 | )
7 |
8 | // STR sequences are similar to CSI sequences, but have string arguments (and
9 | // as far as I can tell, don't really have a name; STR is the name I took from
10 | // suckless which I imagine comes from rxvt or xterm).
11 | type strEscape struct {
12 | typ rune
13 | buf []rune
14 | args []string
15 | }
16 |
17 | func (s *strEscape) reset() {
18 | s.typ = 0
19 | s.buf = s.buf[:0]
20 | s.args = nil
21 | }
22 |
23 | func (s *strEscape) put(c rune) {
24 | // TODO: improve allocs with an array backed slice; bench first
25 | if len(s.buf) < 256 {
26 | s.buf = append(s.buf, c)
27 | }
28 | // Going by st, it is better to remain silent when the STR sequence is not
29 | // ended so that it is apparent to users something is wrong. The length sanity
30 | // check ensures we don't absorb the entire stream into memory.
31 | // TODO: see what rxvt or xterm does
32 | }
33 |
34 | func (s *strEscape) parse() {
35 | s.args = strings.Split(string(s.buf), ";")
36 | }
37 |
38 | func (s *strEscape) arg(i, def int) int {
39 | if i >= len(s.args) || i < 0 {
40 | return def
41 | }
42 | i, err := strconv.Atoi(s.args[i])
43 | if err != nil {
44 | return def
45 | }
46 | return i
47 | }
48 |
49 | func (s *strEscape) argString(i int, def string) string {
50 | if i >= len(s.args) || i < 0 {
51 | return def
52 | }
53 | return s.args[i]
54 | }
55 |
56 | func (t *State) handleSTR() {
57 | s := &t.str
58 | s.parse()
59 |
60 | switch s.typ {
61 | case ']': // OSC - operating system command
62 | switch d := s.arg(0, 0); d {
63 | case 0, 1, 2:
64 | title := s.argString(1, "")
65 | if title != "" {
66 | t.setTitle(title)
67 | }
68 | case 4: // color set
69 | if len(s.args) < 3 {
70 | break
71 | }
72 | // setcolorname(s.arg(1, 0), s.argString(2, ""))
73 | case 104: // color reset
74 | // TODO: complain about invalid color, redraw, etc.
75 | // setcolorname(s.arg(1, 0), nil)
76 | default:
77 | t.logf("unknown OSC command %d\n", d)
78 | // TODO: s.dump()
79 | }
80 | case 'k': // old title set compatibility
81 | title := s.argString(0, "")
82 | if title != "" {
83 | t.setTitle(title)
84 | }
85 | default:
86 | // TODO: Ignore these codes instead of complain?
87 | // 'P': // DSC - device control string
88 | // '_': // APC - application program command
89 | // '^': // PM - privacy message
90 |
91 | t.logf("unhandled STR sequence '%c'\n", s.typ)
92 | // t.str.dump()
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/ryy/terminal/str_test.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestSTRParse(t *testing.T) {
8 | var str strEscape
9 | str.reset()
10 | str.buf = []rune("0;some text")
11 | str.parse()
12 | if str.arg(0, 17) != 0 || str.argString(1, "") != "some text" {
13 | t.Fatal("STR parse mismatch")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ryy/terminal/vt.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "github.com/kr/pty"
7 | "io"
8 | "os"
9 | "os/exec"
10 | "unicode"
11 | "unicode/utf8"
12 | )
13 |
14 | // VT represents the virtual terminal emulator.
15 | type VT struct {
16 | dest *State
17 | rc io.ReadCloser
18 | br *bufio.Reader
19 | pty *os.File
20 | }
21 |
22 | // Start initializes a virtual terminal emulator with the target state
23 | // and a new pty file by starting the *exec.Command. The returned
24 | // *os.File is the pty file.
25 | func Start(state *State, cmd *exec.Cmd) (*VT, *os.File, error) {
26 | var err error
27 | t := &VT{
28 | dest: state,
29 | }
30 | t.pty, err = pty.Start(cmd)
31 | if err != nil {
32 | return nil, nil, err
33 | }
34 | t.rc = t.pty
35 | t.init()
36 | return t, t.pty, nil
37 | }
38 |
39 | // Create initializes a virtual terminal emulator with the target state
40 | // and io.ReadCloser input.
41 | func Create(state *State, rc io.ReadCloser) (*VT, error) {
42 | t := &VT{
43 | dest: state,
44 | rc: rc,
45 | }
46 | t.init()
47 | return t, nil
48 | }
49 |
50 | func (t *VT) init() {
51 | t.br = bufio.NewReader(t.rc)
52 | t.dest.numlock = true
53 | t.dest.state = t.dest.parse
54 | t.dest.cur.attr.fg = DefaultFG
55 | t.dest.cur.attr.bg = DefaultBG
56 | t.Resize(80, 24)
57 | t.dest.reset()
58 | }
59 |
60 | // File returns the pty file.
61 | func (t *VT) File() *os.File {
62 | return t.pty
63 | }
64 |
65 | // Write parses input and writes terminal changes to state.
66 | func (t *VT) Write(p []byte) (int, error) {
67 | var written int
68 | r := bytes.NewReader(p)
69 | t.dest.lock()
70 | defer t.dest.unlock()
71 | for {
72 | c, sz, err := r.ReadRune()
73 | if err != nil {
74 | if err == io.EOF {
75 | break
76 | }
77 | return written, err
78 | }
79 | written += sz
80 | if c == unicode.ReplacementChar && sz == 1 {
81 | if r.Len() == 0 {
82 | // not enough bytes for a full rune
83 | return written - 1, nil
84 | }
85 | t.dest.logln("invalid utf8 sequence")
86 | continue
87 | }
88 | t.dest.put(c)
89 | }
90 | return written, nil
91 | }
92 |
93 | // Close closes the pty or io.ReadCloser.
94 | func (t *VT) Close() error {
95 | return t.rc.Close()
96 | }
97 |
98 | // Parse blocks on read on pty or io.ReadCloser, then parses sequences until
99 | // buffer empties. State is locked as soon as first rune is read, and unlocked
100 | // when buffer is empty.
101 | // TODO: add tests for expected blocking behavior
102 | func (t *VT) Parse() error {
103 | var locked bool
104 | defer func() {
105 | if locked {
106 | t.dest.unlock()
107 | }
108 | }()
109 | for {
110 | c, sz, err := t.br.ReadRune()
111 | if err != nil {
112 | return err
113 | }
114 | if c == unicode.ReplacementChar && sz == 1 {
115 | t.dest.logln("invalid utf8 sequence")
116 | break
117 | }
118 | if !locked {
119 | t.dest.lock()
120 | locked = true
121 | }
122 |
123 | // put rune for parsing and update state
124 | t.dest.put(c)
125 |
126 | // break if our buffer is empty, or if buffer contains an
127 | // incomplete rune.
128 | n := t.br.Buffered()
129 | if n == 0 || (n < 4 && !fullRuneBuffered(t.br)) {
130 | break
131 | }
132 | }
133 | return nil
134 | }
135 |
136 | func fullRuneBuffered(br *bufio.Reader) bool {
137 | n := br.Buffered()
138 | buf, err := br.Peek(n)
139 | if err != nil {
140 | return false
141 | }
142 | return utf8.FullRune(buf)
143 | }
144 |
145 | // Resize reports new size to pty and updates state.
146 | func (t *VT) Resize(cols, rows int) {
147 | t.dest.lock()
148 | defer t.dest.unlock()
149 | _ = t.dest.resize(cols, rows)
150 | t.ptyResize()
151 | }
152 |
--------------------------------------------------------------------------------
/ryy/terminal/vt_test.go:
--------------------------------------------------------------------------------
1 | package terminal
2 |
3 | import (
4 | "io"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func extractStr(t *State, x0, x1, row int) string {
10 | var s []rune
11 | for i := x0; i <= x1; i++ {
12 | c, _, _ := t.Cell(i, row)
13 | s = append(s, c)
14 | }
15 | return string(s)
16 | }
17 |
18 | func TestPlainChars(t *testing.T) {
19 | var st State
20 | term, err := Create(&st, nil)
21 | if err != nil {
22 | t.Fatal(err)
23 | }
24 | expected := "Hello world!"
25 | _, err = term.Write([]byte(expected))
26 | if err != nil && err != io.EOF {
27 | t.Fatal(err)
28 | }
29 | actual := extractStr(&st, 0, len(expected)-1, 0)
30 | if expected != actual {
31 | t.Fatal(actual)
32 | }
33 | }
34 |
35 | func TestNewline(t *testing.T) {
36 | var st State
37 | term, err := Create(&st, nil)
38 | if err != nil {
39 | t.Fatal(err)
40 | }
41 | expected := "Hello world!\n...and more."
42 | _, err = term.Write([]byte("\033[20h")) // set CRLF mode
43 | if err != nil && err != io.EOF {
44 | t.Fatal(err)
45 | }
46 | _, err = term.Write([]byte(expected))
47 | if err != nil && err != io.EOF {
48 | t.Fatal(err)
49 | }
50 |
51 | split := strings.Split(expected, "\n")
52 | actual := extractStr(&st, 0, len(split[0])-1, 0)
53 | actual += "\n"
54 | actual += extractStr(&st, 0, len(split[1])-1, 1)
55 | if expected != actual {
56 | t.Fatal(actual)
57 | }
58 |
59 | // A newline with a color set should not make the next line that color,
60 | // which used to happen if it caused a scroll event.
61 | st.moveTo(0, st.rows-1)
62 | _, err = term.Write([]byte("\033[1;37m\n$ \033[m"))
63 | if err != nil && err != io.EOF {
64 | t.Fatal(err)
65 | }
66 | _, fg, bg := st.Cell(st.Cursor())
67 | if fg != DefaultFG {
68 | t.Fatal(st.cur.x, st.cur.y, fg, bg)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ryy/utils.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 | "unicode"
8 | "unicode/utf8"
9 |
10 | "github.com/gdamore/tcell"
11 | "github.com/go-errors/errors"
12 | runewidth "github.com/mattn/go-runewidth"
13 | )
14 |
15 | func fatalError(err error) {
16 | if err != nil {
17 | fatal(err.Error())
18 | }
19 | }
20 |
21 | func fatal(message string) {
22 | if screen != nil {
23 | screen.Fini()
24 | screen = nil
25 | }
26 | fmt.Printf("%v\n", message)
27 | os.Exit(1)
28 | }
29 |
30 | func handlePanics() {
31 | if err := recover(); err != nil {
32 | fatal(fmt.Sprintf("ry fatal error:\n%v\n%s", err, errors.Wrap(err, 2).ErrorStack()))
33 | }
34 | }
35 |
36 | func write(style tcell.Style, x, y int, str string) int {
37 | s := screen
38 | i := 0
39 | var deferred []rune
40 | dwidth := 0
41 | for _, r := range str {
42 | // Handle tabs
43 | if r == '\t' {
44 | // TODO setting
45 | tabWidth := int(configGetNumber("tab_width", nil))
46 |
47 | // Print first tab char
48 | s.SetContent(x+i, y, '>', nil, style.Foreground(tcell.ColorAqua))
49 | i++
50 |
51 | // Add space till we reach tab column or tabWidth
52 | for j := 0; j < tabWidth-1 || i%tabWidth == tabWidth-1; j++ {
53 | s.SetContent(x+i, y, ' ', nil, style)
54 | i++
55 | }
56 |
57 | deferred = nil
58 | continue
59 | }
60 |
61 | switch runewidth.RuneWidth(r) {
62 | case 0:
63 | if len(deferred) == 0 {
64 | deferred = append(deferred, ' ')
65 | dwidth = 1
66 | }
67 | case 1:
68 | if len(deferred) != 0 {
69 | s.SetContent(x+i, y, deferred[0], deferred[1:], style)
70 | i += dwidth
71 | }
72 | deferred = nil
73 | dwidth = 1
74 | case 2:
75 | if len(deferred) != 0 {
76 | s.SetContent(x+i, y, deferred[0], deferred[1:], style)
77 | i += dwidth
78 | }
79 | deferred = nil
80 | dwidth = 2
81 | }
82 | deferred = append(deferred, r)
83 | }
84 |
85 | if len(deferred) != 0 {
86 | s.SetContent(x+i, y, deferred[0], deferred[1:], style)
87 | i += dwidth
88 | }
89 |
90 | // i is the real width of what we just outputed
91 | return i
92 | }
93 |
94 | func listContainsString(list []string, search string) bool {
95 | for _, item := range list {
96 | if item == search {
97 | return true
98 | }
99 | }
100 | return false
101 | }
102 |
103 | func padr(str string, length int, padding rune) string {
104 | for utf8.RuneCountInString(str) < length {
105 | str = str + string(padding)
106 | }
107 | return str
108 | }
109 |
110 | func padl(str string, length int, padding rune) string {
111 | for utf8.RuneCountInString(str) < length {
112 | str = string(padding) + str
113 | }
114 | return str
115 | }
116 |
117 | func min(a, b int) int {
118 | if a < b {
119 | return a
120 | }
121 | return b
122 | }
123 |
124 | func max(a, b int) int {
125 | if a > b {
126 | return a
127 | }
128 | return b
129 | }
130 |
131 | func isWord(r rune) bool {
132 | return unicode.IsLetter(r) || unicode.IsNumber(r) || strings.ContainsRune("_", r)
133 | }
134 |
135 | func isSpace(r rune) bool {
136 | return r == ' ' || r == '\t' || r == '\n'
137 | }
138 |
139 | func isAlpha(r rune) bool {
140 | return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
141 | }
142 |
143 | func isNum(r rune) bool {
144 | return r >= '0' && r <= '9'
145 | }
146 |
--------------------------------------------------------------------------------
/ryy/view.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "math"
5 |
6 | "github.com/gdamore/tcell"
7 | )
8 |
9 | type ViewHighlight struct {
10 | Beg *Location
11 | End *Location
12 | Style tcell.Style
13 | }
14 |
15 | type View struct {
16 | Buf *Buffer
17 | LineOffset int
18 | CenterPending bool
19 |
20 | Highlights []*ViewHighlight
21 | }
22 |
23 | func NewView(buf *Buffer) *View {
24 | return &View{
25 | Buf: buf,
26 | LineOffset: 0,
27 | CenterPending: false,
28 | Highlights: []*ViewHighlight{},
29 | }
30 | }
31 |
32 | func (v *View) AdjustScroll(w, h int) {
33 | l := v.Buf.Cursor.Line
34 | if v.CenterPending {
35 | v.LineOffset = max(l-int(math.Floor(float64(h-1)/2)), 1)
36 | v.CenterPending = false
37 | return
38 | }
39 | // too low
40 | // (h-2) as height includes status bar and moving to 0 based
41 | if l > h-2+v.LineOffset {
42 | v.LineOffset = max(l-h+2, 0)
43 | }
44 | // too high
45 | if l < v.LineOffset {
46 | v.LineOffset = l
47 | }
48 | }
49 |
50 | // }}}
51 |
52 | // {{{ ViewTree
53 | type ViewTree struct {
54 | Parent *ViewTree
55 | Left *ViewTree
56 | Right *ViewTree
57 | Top *ViewTree
58 | Bottom *ViewTree
59 | Leaf *View
60 | Size int
61 | }
62 |
63 | func NewViewTreeLeaf(parent *ViewTree, v *View) *ViewTree {
64 | return &ViewTree{Parent: parent, Leaf: v, Size: 50}
65 | }
66 |
67 | // }}}
68 |
69 | // {{{ message
70 | func message(m string) {
71 | editorMessage = m
72 | editorMessageType = "info"
73 | }
74 |
75 | func messageError(m string) {
76 | editorMessage = m
77 | editorMessageType = "error"
78 | }
79 |
--------------------------------------------------------------------------------
/vi.go:
--------------------------------------------------------------------------------
1 | // + build vi
2 | package main
3 |
4 | import _ "embed"
5 |
6 | //go:embed vi
7 | var vi []byte
8 |
--------------------------------------------------------------------------------