• ชี้ให้เห็นถึง ความซับซ้อนและข้อจำกัดของสถาปัตยกรรมเทอร์มินัลแบบเดิม พร้อมนำเสนอแนวคิดเทอร์มินัลยุคถัดไปที่รวมการป้อนข้อมูล การแสดงผล และการควบคุมโปรเซสเข้าไว้ด้วยกันในรูปแบบใหม่
  • ใช้ Jupyter Notebook เป็นต้นแบบ เพื่อสำรวจความเป็นไปได้ของ อินเทอร์เฟซแบบโต้ตอบ เช่น การเรนเดอร์ภาพ การรันคำสั่งซ้ำ การแก้ไขผลลัพธ์ได้ และเอดิเตอร์แบบฝังในตัว
  • อธิบายอย่างเป็นรูปธรรมผ่านกรณีของ Warp และ iTerm2 ถึง การผสานรวมเชิงลึกระหว่างเชลล์กับเทอร์มินัล (shell integration) การจัดการโปรเซสที่รันยาวนาน และความสามารถในการแยก/กู้คืนเซสชัน
  • วางภาพฟีเจอร์ต่อยอดบนพื้นฐานของ การติดตาม dataflow (dataflow tracking) และ ความคงอยู่ถาวร (persistence) เช่น undo/redo ของคำสั่ง การรันซ้ำอัตโนมัติ และเทอร์มินัลสำหรับการทำงานร่วมกัน
  • เสนอ กลยุทธ์การพัฒนาแบบค่อยเป็นค่อยไป จาก CLI เชิงทรานแซกชัน → เซสชันแบบคงอยู่ → RPC แบบมีโครงสร้าง → ฟรอนต์เอนด์สไตล์ Jupyter

โครงสร้างพื้นฐานของเทอร์มินัล

  • เทอร์มินัลประกอบด้วย 4 ส่วน: terminal emulator, virtual terminal (PTY), shell, และ process group
    • terminal emulator คือโปรแกรมที่ เรนเดอร์โครงสร้างแบบกริด บนหน้าจอ
    • PTY คือสถานะภายในเคอร์เนล ทำหน้าที่ส่งอินพุตไปยัง process group และแปลงสัญญาณ (signal)
    • shell ทำหน้าที่เป็น event loop ที่อ่านและพาร์สอินพุต รวมถึงสร้างโปรเซส
    • โปรเซสต่าง ๆ โต้ตอบกับองค์ประกอบเหล่านี้ผ่านอินพุตและเอาต์พุต
  • อินพุตไม่ได้เป็นเพียงข้อความธรรมดา แต่รวมถึง สัญญาณ (signal) ด้วย ส่วนเอาต์พุตประกอบด้วย ANSI escape sequence ที่ใช้แสดงการจัดรูปแบบ

ภาพของเทอร์มินัลที่ดีกว่า

  • เทอร์มินัลแบบเดิมมีข้อจำกัดด้านฟังก์ชันมาก ทำให้ขาดทั้ง ความสามารถในการขยาย และ ความโต้ตอบ
  • Jupyter Notebook มีฟีเจอร์ที่ terminal emulator แบบ VT100 ดั้งเดิมทำไม่ได้
    • การเรนเดอร์ภาพความละเอียดสูง
    • ปุ่ม “รันใหม่ตั้งแต่ต้น” ที่แทนที่ผลลัพธ์เก่าโดยไม่เติมต่อท้าย
    • “มุมมอง” ที่สามารถเขียนทับทั้งซอร์สโค้ดและผลลัพธ์ได้ในตำแหน่งเดิม (เช่น แสดง Markdown เป็นซอร์สหรือเป็น HTML ที่เรนเดอร์แล้ว)
    • เอดิเตอร์ในตัวที่มี syntax highlighting, tabs, panels และรองรับเมาส์
  • แต่แนวคิด Jupyter Notebook ที่ ใช้เชลล์เป็นเคอร์เนล ก็เจอหลายปัญหา
    • เชลล์รับคำสั่งทีเดียวทั้งก้อน ทำให้ tab completion, syntax highlighting และ autosuggestion ใช้งานไม่ได้
    • ปัญหาการจัดการโปรเซสที่รันนาน: Jupyter โดยพื้นฐานจะรันจนกว่าเซลล์จะเสร็จ สามารถยกเลิกได้ แต่ ไม่สามารถหยุดชั่วคราว ดำเนินต่อ โต้ตอบ หรือดูโปรเซสที่กำลังรันอยู่ได้
    • ปุ่ม “รันเซลล์ใหม่” อาจสร้างปัญหากับสถานะของเครื่อง (โดยเฉพาะเมื่อมีคำสั่งอย่าง rm -rf)
    • การ undo/redo ใช้งานไม่ได้

แล้วมันจะทำงานอย่างไร?

  • การผสานรวมเชลล์ (Shell Integration)

    • เทอร์มินัล Warp สร้าง การผสานรวมแบบเนทีฟ ระหว่างเทอร์มินัลกับเชลล์
      • เทอร์มินัลเข้าใจจุดเริ่มต้นและจุดสิ้นสุดของแต่ละคำสั่ง รวมถึงเอาต์พุตและอินพุตของผู้ใช้
      • ทำได้โดยใช้ฟังก์ชันมาตรฐาน (custom DCS)
    • iTerm2 ก็ใช้แนวทางคล้ายกัน โดยรองรับ OSC 133 escape code
      • ไปยังคำสั่งก่อนหน้า/ถัดไปได้ด้วยคีย์ลัดเดียว
      • แจ้งเตือนเมื่อคำสั่งทำงานเสร็จ
      • ถ้าเอาต์พุตเลื่อนหลุดจอ จะแสดงคำสั่งปัจจุบันเป็น “overlay”
  • การจัดการโปรเซสที่รันนาน

    • การโต้ตอบ (interacting) :
      • หากต้องการโต้ตอบกับโปรเซสที่ทำงานนาน จำเป็นต้องมี การสื่อสารสองทาง
        • ตัวอย่าง TUI: top, gdb, vim
        • Jupyter เด่นในด้านการออกแบบ เอาต์พุตแบบโต้ตอบ ที่เปลี่ยนแปลงและอัปเดตได้
      • ฟีเจอร์เทอร์มินัลที่คาดหวัง: มี “free input cell” ให้ใช้งานตลอดเวลา
        • โปรเซสแบบโต้ตอบรันอยู่ด้านบนของหน้าต่าง และมี input cell อยู่ด้านล่าง
    • การหยุดชั่วคราว (suspending) :
      • การ “พัก” โปรเซสเรียกว่า job control
      • เทอร์มินัลยุคใหม่ควรแสดง สถานะของโปรเซสที่ถูกพักและโปรเซสเบื้องหลังแบบต่อเนื่องทางสายตา
        • คล้ายกับที่ IntelliJ แสดง “กำลังทำดัชนี...” ที่แถบงานด้านล่าง
    • การตัดการเชื่อมต่อ (disconnecting) : มี 3 แนวทางสำหรับการแยกและกู้คืนเซสชัน
      • Tmux / Zellij / Screen: แทรก terminal emulator เพิ่มอีกชั้นระหว่าง terminal emulator กับโปรแกรม เซิร์ฟเวอร์เป็นเจ้าของ PTY และเรนเดอร์เอาต์พุต ขณะที่ไคลเอนต์นำเอาต์พุตไปแสดงใน terminal emulator จริง สามารถแยกไคลเอนต์ เชื่อมต่อใหม่ หรือเชื่อมหลายไคลเอนต์พร้อมกันได้ iTerm สามารถทำตัวเป็นไคลเอนต์ของตัวเองโดยข้าม tmux client และสื่อสารกับเซิร์ฟเวอร์โดยตรง
      • Mosh: ทางเลือกแทน SSH รองรับการเชื่อมต่อกลับเข้าสู่เซสชันเทอร์มินัลหลังเครือข่ายหลุด เซิร์ฟเวอร์รัน state machine แล้วเล่นซ้ำความต่างแบบ incremental ของ viewport ไปยังไคลเอนต์ โดยคาดหวังให้ terminal emulator จัดการ multiplexing และ scrollback เอง เนื่องจากไคลเอนต์รันอยู่ฝั่งเครือข่ายจริง การแก้ไขบรรทัดแบบโลคัลจึงตอบสนองได้ทันที
      • alden/shpool/dtach/abduco/diss: จัดการเฉพาะการแยก/กลับมาใช้เซสชันด้วยโมเดลไคลเอนต์/เซิร์ฟเวอร์ ไม่รวมเครือข่ายหรือ scrollback และไม่มี terminal emulator ของตัวเอง จึงมี ระดับการแยกที่สูงกว่า เมื่อเทียบกับ tmux และ mosh
  • การรันซ้ำและการย้อนกลับ

    • คำตอบคือ การติดตาม data flow
    • ปัจจุบัน pluto.jl ทำสิ่งนี้ได้โดยเชื่อมเข้ากับคอมไพเลอร์ Julia
      • อัปเดตเซลล์ที่พึ่งพาเซลล์ก่อนหน้าแบบเรียลไทม์
      • หาก dependency ไม่เปลี่ยน ก็จะไม่อัปเดตเซลล์
      • เป็น Jupyter ที่คล้ายสเปรดชีต ซึ่งจะรันโค้ดใหม่เฉพาะเมื่อจำเป็น
    • และสามารถทำให้เป็นทั่วไปมากขึ้นด้วย orthogonal persistence
      • แซนด์บ็อกซ์โปรเซสและติดตาม I/O ทั้งหมด เพื่อป้องกันสิ่งที่ “ประหลาดเกินไป” ตราบใดที่โปรเซสไม่สื่อสารกับโปรเซสอื่นนอกแซนด์บ็อกซ์
      • ทำให้มองโปรเซสได้ว่าเป็นฟังก์ชันบริสุทธิ์ของอินพุต โดยอินพุตคือ “ทั้งระบบไฟล์ ตัวแปรสภาพแวดล้อมทั้งหมด และคุณสมบัติของโปรเซสทั้งหมด”

ฟีเจอร์ที่ต่อยอดได้

  • ต้องมีฟรอนต์เอนด์แบบ Jupyter:
    • Runbooks (จริง ๆ แล้วสร้างได้ด้วยเพียง Jupyter และ PTY primitives)
    • การปรับแต่งเทอร์มินัลด้วย CSS ปกติ โดยไม่ต้องใช้ภาษาปรับแต่งเฉพาะหรือ ANSI color code แปลก ๆ
    • การค้นหาคำสั่งจากเอาต์พุต/เวลา: ตอนนี้ค้นหาได้ทั้งในเอาต์พุตทั้งหมดของเซสชันปัจจุบันหรือในประวัติอินพุตคำสั่งทั้งหมด แต่ยังไม่มี smart filter และเอาต์พุตก็ไม่คงอยู่ข้ามเซสชัน
  • ต้องมีการผสานรวมเชลล์:
    • timestamp และเวลาในการรันของแต่ละคำสั่ง
    • การแก้ไขบรรทัดแบบโลคัลแม้จะข้ามขอบเขตเครือข่าย
    • IntelliSense สำหรับคำสั่งเชลล์โดยไม่ต้องกด Tab พร้อมการเรนเดอร์ที่ผสานอยู่ในเทอร์มินัล
  • ต้องมีการติดตามแซนด์บ็อกซ์:
    • ทุกความสามารถของการติดตามแซนด์บ็อกซ์: เทอร์มินัลแบบทำงานร่วมกัน การค้นหาไฟล์ที่ถูกแก้ไขโดยคำสั่ง asciinema ที่แก้ไขได้ระหว่างรันไทม์ และการติดตาม build system
    • ขยาย smart search ให้ค้นหาได้ตามสถานะดิสก์ ณ เวลาที่คำสั่งถูกรัน
    • ขยาย undo/redo ให้เป็นโมเดลแบบแตกแขนงคล้าย git (emacs undo-tree รองรับแล้ว) พร้อมมุมมองหลายแบบของ process tree
    • ด้วยโมเดล undo-tree และการทำแซนด์บ็อกซ์ ทำให้ สามารถให้ LLM เข้าถึงโปรเจกต์และรันหลายตัวแบบขนานพร้อมกันได้ โดยไม่เขียนทับสถานะของกันและกัน พร้อมตรวจสอบ แก้ไข และบันทึกงานเป็น runbook ไว้ใช้ภายหลัง
    • เทอร์มินัลที่ตรวจสอบได้เฉพาะสถานะเดิม ในสภาพแวดล้อม production โดยไม่กระทบต่อสถานะของเครื่อง

กลยุทธ์การสร้างแบบเป็นขั้นตอน

  • ขั้นที่ 1: semantics แบบทรานแซกชัน (transactional semantics)

    • การเริ่มออกแบบเทอร์มินัลใหม่จาก terminal emulator เป็นแนวทางที่ผิด
      • ผู้ใช้ผูกพันกับ emulator ของตน ทั้งการตั้งค่า รูปลักษณ์ และคีย์ไบน์ดิง
      • ต้นทุนในการเปลี่ยน emulator สูงมาก
    • วิธีที่ถูกต้องคือเริ่มจากเลเยอร์ CLI
      • โปรแกรม CLI ติดตั้งและรันง่าย และมี ต้นทุนในการเปลี่ยนต่ำมาก
      • ใช้งานแบบครั้งคราวได้โดยไม่ต้องเปลี่ยนเวิร์กโฟลว์ทั้งหมด
    • เขียน CLI ที่นำ ความหมายเชิงทรานแซกชันสำหรับเทอร์มินัล มาใช้
      • อินเทอร์เฟซอย่าง transaction [start|rollback|commit]
      • ทุกอย่างที่รันหลัง start สามารถย้อนกลับได้
      • แค่นี้ก็อาจสร้างธุรกิจได้ทั้งก้อน
  • ขั้นที่ 2: เซสชันแบบคงอยู่ (Persistent Sessions)

    • หลังมี semantics แบบทรานแซกชันแล้ว ให้ แยกเรื่อง persistence ออกจาก tmux และ mosh
    • หากต้องการ persistence ของ PTY จำเป็นต้องใช้โมเดลไคลเอนต์/เซิร์ฟเวอร์
      • เคอร์เนลคาดว่า PTY ทั้งสองฝั่งจะเชื่อมต่ออยู่ตลอดเวลา
      • สามารถทำอย่างเรียบง่ายโดยใช้คำสั่งอย่าง alden หรือไลบรารีที่คล้ายกัน โดยไม่กระทบ terminal emulator หรือโปรแกรมที่กำลังรันอยู่ในเซสชัน PTY
    • เพื่อให้ได้ scrollback เซิร์ฟเวอร์ต้องเก็บ I/O ไว้ไม่จำกัดและ replay ตอนที่ไคลเอนต์เชื่อมต่อใหม่
      • terminal emulator จะมี native scrollback ที่ปฏิบัติต่อข้อมูลเหล่านี้เหมือนเอาต์พุตทั่วไป
      • สามารถ replay และ resume จากจุดเริ่มใดก็ได้
      • ต้องพาร์ส ANSI escape code แต่เป็นงานที่ทำได้หากลงแรงพอ
    • สำหรับการกลับมาทำงานต่อของเครือข่ายแบบ mosh ให้ใช้ Eternal TCP (และอาจสร้างบน QUIC เพื่อเพิ่มประสิทธิภาพ)
      • แยก persistence ของ PTY ออกจาก persistence ของการเชื่อมต่อเครือข่าย
      • Eternal TCP เป็นเพียงการปรับปรุงประสิทธิภาพ: มันสร้างต่อบน bash script ที่วน ssh host eternal-pty attach ก็ได้
    • ณ จุดนี้จะสามารถ มีหลายไคลเอนต์เชื่อมต่อกับเซสชันเทอร์มินัลเดียวกันได้ แบบ tmux ขณะที่การจัดการหน้าต่างยังคงเป็นหน้าที่ของ terminal emulator
      • หากต้องการการจัดการหน้าต่างแบบรวมศูนย์ terminal emulator อาจใช้โปรโตคอล tmux -CC แบบที่ iTerm ทำ
    • ทุกส่วนของขั้นนี้ทำคู่ขนานกับ semantics แบบทรานแซกชันได้อย่างอิสระ แต่เพียงอย่างเดียวก็ยังไม่พอสำหรับการสร้างธุรกิจ
  • ขั้นที่ 3: RPC แบบมีโครงสร้าง

    • อาศัยโมเดลไคลเอนต์/เซิร์ฟเวอร์
    • เมื่อเซิร์ฟเวอร์คั่นอยู่ระหว่าง terminal emulator กับไคลเอนต์ ก็สามารถทำฟีเจอร์อย่าง การติดแท็ก I/O ด้วยเมทาดาทา ได้
      • เพิ่ม timestamp ให้ข้อมูลทุกชิ้นได้
      • แยกอินพุตออกจากเอาต์พุตได้
      • xterm.js ก็ทำงานคล้ายกัน
    • เมื่อรวมกับการผสานรวมเชลล์ ก็สามารถ แยก shell prompt ออกจากเอาต์พุตของโปรแกรมในชั้นข้อมูล ได้
    • ทำให้ได้ structured log ของเซสชันเทอร์มินัล
      • เล่น log ซ้ำเป็น recording แบบ asciinema
      • แปลง shell prompt โดยไม่ต้องรันคำสั่งทั้งหมดใหม่
      • นำเข้าไปยัง Jupyter Notebook หรือ Atuin Desktop
      • บันทึกคำสั่งแล้วรันซ้ำเป็นสคริปต์ภายหลัง
      • เทอร์มินัลกลายเป็นข้อมูล
  • ขั้นที่ 4: ฟรอนต์เอนด์คล้าย Jupyter

    • นี่คือ ขั้นแรกที่แตะ terminal emulator โดยตรง และตั้งใจให้เป็นขั้นสุดท้าย
      • เพราะมีต้นทุนในการเปลี่ยนสูงที่สุด
    • ใช้ประโยชน์จากทุกอย่างที่สร้างมาเพื่อมอบ UI ที่ดี
    • CLI transaction ไม่จำเป็นอีกต่อไป เว้นแต่จะต้องการ nested transaction
      • เพราะทั้งเซสชันเทอร์มินัลเริ่มต้นเป็นทรานแซกชันโดยปริยาย
    • เนื่องจากทุกชิ้นส่วนถูกประกอบเข้าด้วยกันแล้ว จึงสามารถมอบฟีเจอร์ต่อยอดทั้งหมดที่กล่าวถึงข้างต้นได้

บทสรุป

  • สถาปัตยกรรมนี้ถูกมองว่า กล้าหาญ ทะเยอทะยาน และอาจใช้เวลาสร้างครบทั้งหมดนานถึงราว 10 ปี
  • ควรเดินหน้าอย่างอดทนและค่อยเป็นค่อยไปทีละขั้น
  • หวังว่าบทความนี้จะสร้างแรงบันดาลใจให้ใครสักคนเริ่มลงมือสร้างมันด้วยตัวเอง

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น