main.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package unicode_input
  3. import (
  4. "bytes"
  5. "errors"
  6. "fmt"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "slices"
  11. "strconv"
  12. "strings"
  13. "unicode"
  14. "kitty/tools/cli"
  15. "kitty/tools/tty"
  16. "kitty/tools/tui"
  17. "kitty/tools/tui/loop"
  18. "kitty/tools/tui/readline"
  19. "kitty/tools/unicode_names"
  20. "kitty/tools/utils"
  21. "kitty/tools/utils/style"
  22. "kitty/tools/wcswidth"
  23. )
  24. var _ = fmt.Print
  25. const INDEX_CHAR string = "."
  26. const INDEX_BASE = 36
  27. const InvalidChar rune = unicode.MaxRune + 1
  28. const default_set_of_symbols string = `
  29. ‘’“”‹›«»‚„ 😀😛😇😈😉😍😎😮👍👎 —–§¶†‡©®™ →⇒•·°±−×÷¼½½¾
  30. …µ¢£€¿¡¨´¸ˆ˜ ÀÁÂÃÄÅÆÇÈÉÊË ÌÍÎÏÐÑÒÓÔÕÖØ ŒŠÙÚÛÜÝŸÞßàá âãäåæçèéêëìí
  31. îïðñòóôõöøœš ùúûüýÿþªºαΩ∞
  32. `
  33. var DEFAULT_SET []rune
  34. var EMOTICONS_SET []rune
  35. const DEFAULT_MODE string = "HEX"
  36. func build_sets() {
  37. DEFAULT_SET = make([]rune, 0, len(default_set_of_symbols))
  38. for _, ch := range default_set_of_symbols {
  39. if !unicode.IsSpace(ch) {
  40. DEFAULT_SET = append(DEFAULT_SET, ch)
  41. }
  42. }
  43. EMOTICONS_SET = make([]rune, 0, 0x1f64f-0x1f600+1)
  44. for i := 0x1f600; i <= 0x1f64f; i++ {
  45. EMOTICONS_SET = append(EMOTICONS_SET, rune(i))
  46. }
  47. }
  48. func codepoint_ok(code rune) bool {
  49. return !(code <= 32 || code == 127 || (128 <= code && code <= 159) || (0xd800 <= code && code <= 0xdbff) || (0xDC00 <= code && code <= 0xDFFF) || code > unicode.MaxRune)
  50. }
  51. func parse_favorites(raw string) (ans []rune) {
  52. ans = make([]rune, 0, 128)
  53. for _, line := range utils.Splitlines(raw) {
  54. line = strings.TrimSpace(line)
  55. if len(line) == 0 || strings.HasPrefix(line, "#") {
  56. continue
  57. }
  58. idx := strings.Index(line, "#")
  59. if idx > -1 {
  60. line = line[:idx]
  61. }
  62. code_text, _, _ := strings.Cut(line, " ")
  63. code, err := strconv.ParseUint(code_text, 16, 32)
  64. if err == nil && codepoint_ok(rune(code)) {
  65. ans = append(ans, rune(code))
  66. }
  67. }
  68. return
  69. }
  70. func serialize_favorites(favs []rune) string {
  71. b := strings.Builder{}
  72. b.Grow(8192)
  73. b.WriteString(`# Favorite characters for unicode input
  74. # Enter the hex code for each favorite character on a new line. Blank lines are
  75. # ignored and anything after a # is considered a comment.
  76. `)
  77. for _, ch := range favs {
  78. b.WriteString(fmt.Sprintf("%x # %s %s\n", ch, string(ch), unicode_names.NameForCodePoint(ch)))
  79. }
  80. return b.String()
  81. }
  82. var loaded_favorites []rune
  83. var favorites_loaded_from_user_config bool
  84. func favorites_path() string {
  85. return filepath.Join(utils.ConfigDir(), "unicode-input-favorites.conf")
  86. }
  87. func load_favorites(refresh bool) []rune {
  88. if refresh || loaded_favorites == nil {
  89. raw, err := os.ReadFile(favorites_path())
  90. if err == nil {
  91. loaded_favorites = parse_favorites(utils.UnsafeBytesToString(raw))
  92. favorites_loaded_from_user_config = true
  93. } else {
  94. loaded_favorites = DEFAULT_SET
  95. favorites_loaded_from_user_config = false
  96. }
  97. }
  98. return loaded_favorites
  99. }
  100. type CachedData struct {
  101. Recent []rune `json:"recent,omitempty"`
  102. Mode string `json:"mode,omitempty"`
  103. }
  104. var cached_data *CachedData
  105. type Mode int
  106. const (
  107. HEX Mode = iota
  108. NAME
  109. EMOTICONS
  110. FAVORITES
  111. )
  112. type ModeData struct {
  113. mode Mode
  114. key string
  115. title string
  116. }
  117. var all_modes [4]ModeData
  118. type checkpoints_key struct {
  119. mode Mode
  120. text string
  121. codepoints []rune
  122. index_word int
  123. }
  124. func (self *checkpoints_key) clear() {
  125. *self = checkpoints_key{}
  126. }
  127. func (self *checkpoints_key) is_equal(other checkpoints_key) bool {
  128. return self.mode == other.mode && self.text == other.text && slices.Equal(self.codepoints, other.codepoints) && self.index_word == other.index_word
  129. }
  130. type handler struct {
  131. mode Mode
  132. recent []rune
  133. current_char rune
  134. err error
  135. lp *loop.Loop
  136. ctx style.Context
  137. rl *readline.Readline
  138. choice_line string
  139. emoji_variation string
  140. checkpoints_key checkpoints_key
  141. table table
  142. current_tab_formatter, tab_bar_formatter, chosen_formatter, chosen_name_formatter, dim_formatter func(...any) string
  143. }
  144. func (self *handler) initialize() {
  145. self.ctx.AllowEscapeCodes = true
  146. self.checkpoints_key.index_word = -1
  147. self.table.initialize(self.emoji_variation, self.ctx)
  148. self.lp.SetWindowTitle("Unicode input")
  149. self.current_char = InvalidChar
  150. self.current_tab_formatter = self.ctx.SprintFunc("reverse=false bold=true")
  151. self.tab_bar_formatter = self.ctx.SprintFunc("reverse=true")
  152. self.chosen_formatter = self.ctx.SprintFunc("fg=green")
  153. self.chosen_name_formatter = self.ctx.SprintFunc("italic=true dim=true")
  154. self.dim_formatter = self.ctx.SprintFunc("dim=true")
  155. self.rl = readline.New(self.lp, readline.RlInit{Prompt: "> ", DontMarkPrompts: true})
  156. self.rl.Start()
  157. self.refresh()
  158. }
  159. func (self *handler) finalize() string {
  160. self.rl.End()
  161. self.rl.Shutdown()
  162. return ""
  163. }
  164. func (self *handler) resolved_char() string {
  165. if self.current_char == InvalidChar {
  166. return ""
  167. }
  168. return resolved_char(self.current_char, self.emoji_variation)
  169. }
  170. func is_index(word string) bool {
  171. if !strings.HasPrefix(word, INDEX_CHAR) {
  172. return false
  173. }
  174. word = strings.TrimLeft(word, INDEX_CHAR)
  175. _, err := strconv.ParseUint(word, INDEX_BASE, 32)
  176. return err == nil
  177. }
  178. func (self *handler) update_codepoints() {
  179. var index_word uint64
  180. var q checkpoints_key
  181. q.mode = self.mode
  182. q.index_word = -1
  183. switch self.mode {
  184. case HEX:
  185. q.codepoints = self.recent
  186. if len(q.codepoints) == 0 {
  187. q.codepoints = DEFAULT_SET
  188. }
  189. case EMOTICONS:
  190. q.codepoints = EMOTICONS_SET
  191. case FAVORITES:
  192. q.codepoints = load_favorites(false)
  193. case NAME:
  194. q.text = self.rl.AllText()
  195. if !q.is_equal(self.checkpoints_key) {
  196. words := strings.Split(q.text, " ")
  197. words = utils.RemoveAll(words, INDEX_CHAR)
  198. if len(words) > 1 {
  199. for i, w := range words {
  200. if i > 0 && is_index(w) {
  201. iw := words[i]
  202. words = words[:i]
  203. index_word, _ = strconv.ParseUint(strings.TrimLeft(iw, INDEX_CHAR), INDEX_BASE, 32)
  204. q.index_word = int(index_word)
  205. break
  206. }
  207. }
  208. }
  209. query := strings.Join(words, " ")
  210. if len(query) > 1 {
  211. words = words[1:]
  212. q.codepoints = unicode_names.CodePointsForQuery(query)
  213. }
  214. }
  215. }
  216. if !q.is_equal(self.checkpoints_key) {
  217. self.checkpoints_key = q
  218. self.table.set_codepoints(q.codepoints, self.mode, q.index_word)
  219. }
  220. }
  221. var debugprintln = tty.DebugPrintln
  222. func (self *handler) update_current_char() {
  223. self.update_codepoints()
  224. self.current_char = InvalidChar
  225. text := self.rl.AllText()
  226. switch self.mode {
  227. case HEX:
  228. if strings.HasPrefix(text, INDEX_CHAR) {
  229. if len(text) > 1 {
  230. self.current_char = self.table.codepoint_at_hint(text[1:])
  231. }
  232. } else if len(text) > 0 {
  233. code, err := strconv.ParseUint(text, 16, 32)
  234. if err == nil && code <= unicode.MaxRune {
  235. self.current_char = rune(code)
  236. }
  237. }
  238. case NAME:
  239. cc := self.table.current_codepoint()
  240. if cc > 0 && cc <= unicode.MaxRune {
  241. self.current_char = rune(cc)
  242. }
  243. default:
  244. if len(text) > 0 {
  245. self.current_char = self.table.codepoint_at_hint(strings.TrimLeft(text, INDEX_CHAR))
  246. }
  247. }
  248. if !codepoint_ok(self.current_char) {
  249. self.current_char = InvalidChar
  250. }
  251. }
  252. func (self *handler) update_prompt() {
  253. self.update_current_char()
  254. ch := "??"
  255. color := "red"
  256. self.choice_line = ""
  257. if self.current_char != InvalidChar {
  258. ch, color = self.resolved_char(), "green"
  259. self.choice_line = fmt.Sprintf(
  260. "Chosen: %s U+%x %s", self.chosen_formatter(ch), self.current_char,
  261. self.chosen_name_formatter(title(unicode_names.NameForCodePoint(self.current_char))))
  262. }
  263. prompt := fmt.Sprintf("%s> ", self.ctx.SprintFunc("fg="+color)(ch))
  264. self.rl.SetPrompt(prompt)
  265. }
  266. func (self *handler) draw_title_bar() {
  267. self.lp.AllowLineWrapping(false)
  268. entries := make([]string, 0, len(all_modes))
  269. for _, md := range all_modes {
  270. entry := fmt.Sprintf(" %s (%s) ", md.title, md.key)
  271. if md.mode == self.mode {
  272. entry = self.current_tab_formatter(entry)
  273. }
  274. entries = append(entries, entry)
  275. }
  276. sz, _ := self.lp.ScreenSize()
  277. text := fmt.Sprintf("Search by:%s", strings.Join(entries, ""))
  278. extra := int(sz.WidthCells) - wcswidth.Stringwidth(text)
  279. if extra > 0 {
  280. text += strings.Repeat(" ", extra)
  281. }
  282. self.lp.Println(self.tab_bar_formatter(text))
  283. }
  284. func (self *handler) draw_screen() {
  285. self.lp.StartAtomicUpdate()
  286. defer self.lp.EndAtomicUpdate()
  287. self.lp.ClearScreen()
  288. self.draw_title_bar()
  289. y := 1
  290. writeln := func(text ...any) {
  291. self.lp.Println(text...)
  292. y += 1
  293. }
  294. switch self.mode {
  295. case NAME:
  296. writeln("Enter words from the name of the character")
  297. case HEX:
  298. writeln("Enter the hex code for the character")
  299. default:
  300. writeln("Enter the index for the character you want from the list below")
  301. }
  302. self.rl.RedrawNonAtomic()
  303. self.lp.AllowLineWrapping(false)
  304. self.lp.SaveCursorPosition()
  305. defer self.lp.RestoreCursorPosition()
  306. writeln()
  307. writeln(self.choice_line)
  308. sz, _ := self.lp.ScreenSize()
  309. write_help := func(x string) {
  310. lines := style.WrapTextAsLines(x, int(sz.WidthCells)-1, style.WrapOptions{})
  311. for _, line := range lines {
  312. if line != "" {
  313. writeln(self.dim_formatter(line))
  314. }
  315. }
  316. }
  317. switch self.mode {
  318. case HEX:
  319. write_help(fmt.Sprintf("Type %s followed by the index for the recent entries below", INDEX_CHAR))
  320. case NAME:
  321. write_help(fmt.Sprintf("Use Tab or arrow keys to choose a character. Type space and %s to select by index", INDEX_CHAR))
  322. case FAVORITES:
  323. write_help("Press F12 to edit the list of favorites")
  324. }
  325. q := self.table.layout(int(sz.HeightCells)-y, int(sz.WidthCells))
  326. if q != "" {
  327. self.lp.QueueWriteString(q)
  328. }
  329. }
  330. func (self *handler) on_text(text string, from_key_event, in_bracketed_paste bool) error {
  331. err := self.rl.OnText(text, from_key_event, in_bracketed_paste)
  332. if err != nil {
  333. return err
  334. }
  335. self.refresh()
  336. return nil
  337. }
  338. func (self *handler) switch_mode(mode Mode) {
  339. if self.mode != mode {
  340. self.mode = mode
  341. self.rl.ResetText()
  342. self.current_char = InvalidChar
  343. self.choice_line = ""
  344. }
  345. }
  346. func (self *handler) handle_hex_key_event(event *loop.KeyEvent) {
  347. text := self.rl.AllText()
  348. val, err := strconv.ParseUint(text, 16, 32)
  349. new_val := -1
  350. if err != nil {
  351. return
  352. }
  353. if event.MatchesPressOrRepeat("tab") {
  354. new_val = int(val) + 10
  355. } else if event.MatchesPressOrRepeat("up") {
  356. new_val = int(val) + 1
  357. } else if event.MatchesPressOrRepeat("down") {
  358. new_val = utils.Max(32, int(val)-1)
  359. }
  360. if new_val > -1 {
  361. event.Handled = true
  362. self.rl.SetText(fmt.Sprintf("%x", new_val))
  363. }
  364. }
  365. func (self *handler) handle_name_key_event(event *loop.KeyEvent) {
  366. if event.MatchesPressOrRepeat("shift+tab") || event.MatchesPressOrRepeat("left") {
  367. event.Handled = true
  368. self.table.move_current(0, -1)
  369. } else if event.MatchesPressOrRepeat("tab") || event.MatchesPressOrRepeat("right") {
  370. event.Handled = true
  371. self.table.move_current(0, 1)
  372. } else if event.MatchesPressOrRepeat("up") {
  373. event.Handled = true
  374. self.table.move_current(-1, 0)
  375. } else if event.MatchesPressOrRepeat("down") {
  376. event.Handled = true
  377. self.table.move_current(1, 0)
  378. }
  379. }
  380. func (self *handler) handle_emoticons_key_event(event *loop.KeyEvent) {
  381. }
  382. func (self *handler) handle_favorites_key_event(event *loop.KeyEvent) {
  383. if event.MatchesPressOrRepeat("f12") {
  384. event.Handled = true
  385. exe, err := os.Executable()
  386. if err != nil {
  387. self.err = err
  388. self.lp.Quit(1)
  389. return
  390. }
  391. fp := favorites_path()
  392. if len(load_favorites(false)) == 0 || !favorites_loaded_from_user_config {
  393. raw := serialize_favorites(load_favorites(false))
  394. err = os.MkdirAll(filepath.Dir(fp), 0o755)
  395. if err != nil {
  396. self.err = fmt.Errorf("Failed to create config directory to store favorites in: %w", err)
  397. self.lp.Quit(1)
  398. return
  399. }
  400. err = utils.AtomicUpdateFile(fp, bytes.NewReader(utils.UnsafeStringToBytes(raw)), 0o600)
  401. if err != nil {
  402. self.err = fmt.Errorf("Failed to write to favorites file %s with error: %w", fp, err)
  403. self.lp.Quit(1)
  404. return
  405. }
  406. }
  407. err = self.lp.SuspendAndRun(func() error {
  408. cmd := exec.Command(exe, "edit-in-kitty", "--type=overlay", fp)
  409. cmd.Stdin = os.Stdin
  410. cmd.Stdout = os.Stdout
  411. cmd.Stderr = os.Stderr
  412. err = cmd.Run()
  413. if err == nil {
  414. load_favorites(true)
  415. } else {
  416. fmt.Fprintln(os.Stderr, err)
  417. fmt.Fprintln(os.Stderr, "Failed to run edit-in-kitty, favorites have not been changed. Press Enter to continue.")
  418. var ln string
  419. fmt.Scanln(&ln)
  420. }
  421. return nil
  422. })
  423. if err != nil {
  424. self.err = err
  425. self.lp.Quit(1)
  426. return
  427. }
  428. }
  429. }
  430. func (self *handler) next_mode(delta int) {
  431. for num, md := range all_modes {
  432. if md.mode == self.mode {
  433. idx := (num + delta + len(all_modes)) % len(all_modes)
  434. md = all_modes[idx]
  435. self.switch_mode(md.mode)
  436. break
  437. }
  438. }
  439. }
  440. var ErrCanceledByUser = errors.New("Canceled by user")
  441. func (self *handler) on_key_event(event *loop.KeyEvent) (err error) {
  442. if event.MatchesPressOrRepeat("esc") || event.MatchesPressOrRepeat("ctrl+c") {
  443. return ErrCanceledByUser
  444. }
  445. if event.MatchesPressOrRepeat("f1") || event.MatchesPressOrRepeat("ctrl+1") {
  446. event.Handled = true
  447. self.switch_mode(HEX)
  448. } else if event.MatchesPressOrRepeat("f2") || event.MatchesPressOrRepeat("ctrl+2") {
  449. event.Handled = true
  450. self.switch_mode(NAME)
  451. } else if event.MatchesPressOrRepeat("f3") || event.MatchesPressOrRepeat("ctrl+3") {
  452. event.Handled = true
  453. self.switch_mode(EMOTICONS)
  454. } else if event.MatchesPressOrRepeat("f4") || event.MatchesPressOrRepeat("ctrl+4") {
  455. event.Handled = true
  456. self.switch_mode(FAVORITES)
  457. } else if event.MatchesPressOrRepeat("ctrl+tab") || event.MatchesPressOrRepeat("ctrl+]") {
  458. event.Handled = true
  459. self.next_mode(1)
  460. } else if event.MatchesPressOrRepeat("ctrl+shift+tab") || event.MatchesPressOrRepeat("ctrl+[") {
  461. event.Handled = true
  462. self.next_mode(-1)
  463. }
  464. if !event.Handled {
  465. switch self.mode {
  466. case HEX:
  467. self.handle_hex_key_event(event)
  468. case NAME:
  469. self.handle_name_key_event(event)
  470. case EMOTICONS:
  471. self.handle_emoticons_key_event(event)
  472. case FAVORITES:
  473. self.handle_favorites_key_event(event)
  474. }
  475. }
  476. if !event.Handled {
  477. err = self.rl.OnKeyEvent(event)
  478. if err != nil {
  479. if err == readline.ErrAcceptInput {
  480. self.refresh()
  481. self.lp.Quit(0)
  482. return nil
  483. }
  484. return err
  485. }
  486. }
  487. if event.Handled {
  488. self.refresh()
  489. }
  490. return
  491. }
  492. func (self *handler) refresh() {
  493. self.update_prompt()
  494. self.draw_screen()
  495. }
  496. func run_loop(opts *Options) (lp *loop.Loop, err error) {
  497. output := tui.KittenOutputSerializer()
  498. lp, err = loop.New()
  499. if err != nil {
  500. return
  501. }
  502. cv := utils.NewCachedValues("unicode-input", &CachedData{Recent: DEFAULT_SET, Mode: DEFAULT_MODE})
  503. cached_data = cv.Load()
  504. defer cv.Save()
  505. h := handler{recent: cached_data.Recent, lp: lp, emoji_variation: opts.EmojiVariation}
  506. switch opts.Tab {
  507. case "previous":
  508. switch cached_data.Mode {
  509. case "HEX":
  510. h.mode = HEX
  511. case "NAME":
  512. h.mode = NAME
  513. case "EMOTICONS":
  514. h.mode = EMOTICONS
  515. case "FAVORITES":
  516. h.mode = FAVORITES
  517. }
  518. case "code":
  519. h.mode = HEX
  520. case "name":
  521. h.mode = NAME
  522. case "emoticons":
  523. h.mode = EMOTICONS
  524. case "favorites":
  525. h.mode = FAVORITES
  526. }
  527. all_modes[0] = ModeData{mode: HEX, title: "Code", key: "F1"}
  528. all_modes[1] = ModeData{mode: NAME, title: "Name", key: "F2"}
  529. all_modes[2] = ModeData{mode: EMOTICONS, title: "Emoticons", key: "F3"}
  530. all_modes[3] = ModeData{mode: FAVORITES, title: "Favorites", key: "F4"}
  531. lp.OnInitialize = func() (string, error) {
  532. h.initialize()
  533. lp.SendOverlayReady()
  534. return "", nil
  535. }
  536. lp.OnResize = func(old_size, new_size loop.ScreenSize) error {
  537. h.refresh()
  538. return nil
  539. }
  540. lp.OnResumeFromStop = func() error {
  541. h.refresh()
  542. return nil
  543. }
  544. lp.OnText = h.on_text
  545. lp.OnFinalize = h.finalize
  546. lp.OnKeyEvent = h.on_key_event
  547. err = lp.Run()
  548. if err != nil {
  549. return
  550. }
  551. if h.err == nil {
  552. switch h.mode {
  553. case HEX:
  554. cached_data.Mode = "HEX"
  555. case NAME:
  556. cached_data.Mode = "NAME"
  557. case EMOTICONS:
  558. cached_data.Mode = "EMOTICONS"
  559. case FAVORITES:
  560. cached_data.Mode = "FAVORITES"
  561. }
  562. if h.current_char != InvalidChar {
  563. cached_data.Recent = h.recent
  564. idx := slices.Index(cached_data.Recent, h.current_char)
  565. if idx > -1 {
  566. cached_data.Recent = slices.Delete(cached_data.Recent, idx, idx+1)
  567. }
  568. cached_data.Recent = slices.Insert(cached_data.Recent, 0, h.current_char)
  569. if len(cached_data.Recent) > len(DEFAULT_SET) {
  570. cached_data.Recent = cached_data.Recent[:len(DEFAULT_SET)]
  571. }
  572. ans := h.resolved_char()
  573. o, err := output(ans)
  574. if err != nil {
  575. return lp, err
  576. }
  577. fmt.Println(o)
  578. }
  579. }
  580. err = h.err
  581. return
  582. }
  583. func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) {
  584. go unicode_names.Initialize() // start parsing name data in the background
  585. build_sets()
  586. lp, err := run_loop(o)
  587. if err != nil {
  588. if err == ErrCanceledByUser {
  589. err = nil
  590. }
  591. return 1, err
  592. }
  593. ds := lp.DeathSignalName()
  594. if ds != "" {
  595. fmt.Println("Killed by signal: ", ds)
  596. lp.KillIfSignalled()
  597. return 1, nil
  598. }
  599. return
  600. }
  601. func EntryPoint(parent *cli.Command) {
  602. create_cmd(parent, main)
  603. }