2 điểm bởi GN⁺ 9 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Khôi phục khả năng sử dụng framework giao diện văn bản cổ điển trên Linux và Windows, đồng thời cung cấp nền tảng phát triển chung với cả UTF-8 và màu mở rộng
  • Giữ mức tương thích mã nguồn cao nhất có thể với các ứng dụng Turbo Vision hiện có, đồng thời tiếp tục dùng API dựa trên char để hấp thụ khác biệt terminal theo từng nền tảng bên trong thư viện
  • Tích hợp nhập và hiển thị Unicode trên toàn bộ framework để xử lý đồng thời ký tự rộng gấp đôi, ký tự kết hợp, phím tắt nhiều byte, chỉnh sửa và vẽ dựa trên UTF-8
  • Cung cấp widget cơ bản như cửa sổ chồng lớp, menu, hộp thoại, ô nhập liệu, thanh cuộn, đồng thời hỗ trợ các hành vi hiện đại như vòng lặp sự kiện, bộ hẹn giờ, clipboard, bánh xe chuột, thay đổi kích thước và chỉnh sửa tệp lớn
  • Vượt qua API cũ xoay quanh 16 màu để gom cả màu 24-bit, bảng màu xterm-256, clipboard hệ thống và hỗ trợ nhiều môi trường console, tạo thành nền tảng kết nối mã TUI cũ với môi trường terminal hiện đại

Tính chất dự án và định hướng cốt lõi

  • bản port hiện đại của Turbo Vision 2.0, giúp tái sử dụng framework giao diện văn bản cổ điển trên Linux và Windows, đồng thời đưa vào cả UTF-8 và màu mở rộng
  • Ban đầu tập trung vào việc bổ sung hỗ trợ Linux, giữ nguyên hành vi DOS/Windows hiện có và bảo toàn tính tương thích ở cấp mã nguồn với các ứng dụng Turbo Vision cũ
    • Để phục vụ mức tương thích này, một số hàm RTL của Borland C++ cũng được tự triển khai trực tiếp
  • Trong khoảng tháng 7–8 năm 2020, dự án đã tích hợp hỗ trợ Unicode đầy đủ vào cấu trúc hiện có, và trong quá trình đó cũng viết ra trình soạn thảo văn bản Turbo
  • Thư viện được bọc để ứng dụng không phải lách qua khác biệt về tính năng terminal hay tự xử lý I/O terminal trực tiếp, nhờ đó hấp thụ khác biệt nền tảng ở phía thư viện
  • Tập trung vào việc viết giao diện văn bản bằng cùng một mã trên Linux và Windows, đồng thời tiếp tục duy trì API dựa trên char thay vì wchar_t hay TCHAR

Vì sao hữu ích

  • Cung cấp nhiều lớp widget để có thể dùng ngay các thành phần như cửa sổ chồng lớp có thể đổi kích thước, menu thả xuống, hộp thoại, nút bấm, thanh cuộn, ô nhập liệu, checkbox và radio button
  • Khi tự tạo widget riêng, cũng giảm gánh nặng phải viết lại các xử lý dùng chung như phân phối sự kiện hay hiển thị ký tự Unicode toàn chiều rộng
  • Xử lý để cho ra kết quả giống nhau nhiều nhất có thể trên từng môi trường
    • Ngay cả khi màu nền sáng trên Linux console phụ thuộc vào thuộc tính blink, thư viện cũng sẽ xử lý thay
  • Tận dụng hỗ trợ UTF-8 của setlocale trong Microsoft RTL để trên Windows, mã như std::ifstream f("コンピュータ.txt"); có thể hoạt động đúng như mong đợi
    • Trên Windows, RTL sẽ chuyển ngay tên tệp sang bảng mã hệ thống

Thiết kế Unicode và xử lý văn bản

  • Chọn UTF-8 làm mã hóa mặc định cho nhập liệu và hiển thị
    • Tương thích với kiểu dữ liệu char * hiện có và trùng với mã hóa I/O terminal, giúp giảm các bước chuyển đổi không cần thiết
    • Cũng phù hợp với định hướng của UTF-8 Everywhere Manifesto
  • Khi build bằng Borland C++, dự án không hỗ trợ Unicode, nhưng các mở rộng API được thiết kế để có thể viết mã độc lập với mã hóa
  • Trong KeyDownEvent, ngoài charScan.charCode hiện có còn bổ sung text[4]textLength để truyền trực tiếp chuỗi byte đầu vào UTF-8
    • Trong charScan.charCode, ký tự theo chuẩn CP437 vẫn tiếp tục được lưu để đảm bảo tương thích ngược
    • Khuyến nghị dùng getText() để lấy string view
  • Các ví dụ nhập liệu cho thấy rõ cách Unicode và mã cũ cùng hoạt động
    • Khi nhập ñ, sẽ có charCode=164 ('ñ'), text={'\xC3','\xB1'}, textLength=2
    • Khi nhập , do không có trong CP437 nên keyCode=0x0, charCode=0, text={'\xE2','\x82','\xAC'}, textLength=3
    • Khi nhập phím tắt, textLength=0 nên để trống
  • TScreenCell có thể chứa codepoint UTF-8 cùng các thuộc tính mở rộng như in đậm, gạch chân và in nghiêng
    • Ký tự điều khiển ASCII được xử lý như ký tự code page
    • UTF-8 hợp lệ sẽ được hiển thị nguyên trạng, còn UTF-8 không hợp lệ sẽ được xử lý như ký tự code page
    • Có thể trộn ASCII mở rộng với UTF-8 nên thuận lợi cho tương thích ngược, nhưng cũng có thể tạo ra kết quả ngoài ý muốn
  • Ký tự rộng gấp đôiký tự kết hợp cũng được xử lý riêng
    • Ký tự kết hợp sẽ được chồng lên ký tự đứng trước
    • ZERO WIDTH JOINER U+200D luôn bị lược bỏ
    • Nếu terminal tôn trọng độ rộng ký tự theo wcwidth thì hầu như sẽ không có lỗi hiển thị đồ họa đáng замет
    • Có thể xem ví dụ trong Wide character display

API Unicode và hỗ trợ các view tiêu chuẩn

  • API vẽ có nhận biết Unicode

    • TDrawBuffer::moveChar, putChar xử lý char c như ký tự code page
    • moveStr, moveCStr nhận TStringView và xử lý chuỗi theo quy tắc Unicode
      • maxStrWidth dựa trên số cột chứ không phải số byte
      • strIndent cũng dựa trên số cột thay vì byte nên có thể dùng cho cuộn ngang
      • Giá trị trả về là độ rộng hiển thị của phần văn bản đã được sao chép
    • moveBuf được giữ lại vì đây là hàm từng dùng để xử lý chuỗi không null-terminated trước khi có TStringView
    • cstrlen(TStringView s) trả về độ dài hiển thị không tính ~, còn strwidth(TStringView s) trả về độ dài hiển thị có tính ~
  • Đơn giản hóa draw() và giảm lỗi

    • TFileViewer::draw() trước đây sao chép chuỗi con vào bộ đệm tạm rồi gọi moveStr, nhưng cách này tiềm ẩn nguy cơ buffer overrun và vấn đề ranh giới đa byte
    • Sau khi sửa, chỉ cần một dòng b.moveStr(0, p, c, size.x, delta.x); để xử lý, loại bỏ khâu sao chép trung gian
      • Có thể áp dụng delta.x trực tiếp theo đơn vị cột
      • Chỉ sao chép tối đa size.x cột nên bớt cần tự tính số byte và điều kiện biên
  • Các view tiêu chuẩn hỗ trợ hiển thị Unicode

  • Các view xử lý cả nhập liệu Unicode

    • TInputLine, cb489d42, TEditor, phím tắt của TMenuView đều xử lý được cả đầu vào Unicode từ người dùng
    • Các instance TEditor mặc định ở chế độ UTF-8 và có thể chuyển sang chế độ single-byte bằng Ctrl+P
    • Việc chuyển đổi này không thay đổi nội dung tài liệu mà chỉ đổi cách hiển thịmã hóa đầu vào
    • Phím tắt được nhấn mạnh như trong TStatusLine, TButton không được hỗ trợ
  • Chuỗi đa ngôn ngữ trong menu và thanh trạng thái

    • Có thể dùng nguyên trạng các chuỗi như ~Ñ~ello, 階~毎~料入報最..., 五劫~の~擦り切れ, העברית ~א~ינטרנט, ~Alt-Ç~ Exit trong menu và thanh trạng thái
    • Có thể xem màn hình kết quả tại Unicode Hello

Mô hình hoạt động theo nền tảng

  • Unix

    • Sử dụng hỗ trợ terminal dựa trên Ncurses
    • Hỗ trợ mã hóa chuột X10, SGR, modifyOtherKeys của xterm, fixterms của Paul Evans, keyboard protocol của Kitty, win32-input-mode của WSL Conpty, far2l, modifier TIOCLINUX của console Linux và chuột GPM
    • trình xử lý tín hiệu tùy chỉnh để khôi phục trạng thái terminal trước khi chương trình kết thúc bất thường
    • Nếu stderr là tty thì sẽ lưu vào bộ đệm để tránh làm hỏng màn hình, rồi in ra khi thoát hoặc tạm dừng
      • Có giới hạn kích thước bộ đệm nên khi đầy, việc ghi vào stderr có thể thất bại
      • Nếu muốn giữ lại toàn bộ thì nên dùng chuyển hướng 2>
    • Đọc các biến môi trường TERM, COLORTERM, ESCDELAY, TVISION_USE_STDIO
      • Nếu COLORTERM=truecolor hoặc 24bit thì giả định hỗ trợ màu 24-bit
      • Giá trị mặc định của ESCDELAY10ms
      • Nếu TVISION_USE_STDIO không rỗng thì sẽ thực hiện I/O bằng stdinstdout thay vì /dev/tty
  • Windows

    • Chỉ tương thích với Win32 Console API
    • Trên các terminal emulator không hỗ trợ API này, chương trình sẽ tự động mở một cửa sổ console riêng
    • Bố cục ứng dụng dựa trên kích thước cửa sổ console chứ không phải buffer console, và sẽ khôi phục buffer console khi thoát hoặc tạm dừng
    • Nếu không phải Borland C++ thì sẽ đổi code page của console sang UTF-8 khi khởi động và khôi phục lại khi thoát
    • Cũng chuyển Microsoft C runtime sang chế độ UTF-8 để giảm nhu cầu lập trình viên phải tự dùng trực tiếp các biến thể wchar_t
    • Với tổ hợp chế độ legacy và font bitmap, việc hiển thị Unicode có thể bị lỗi; nếu phát hiện tình huống này thì sẽ thử đổi font sang Consolas hoặc Lucida Console
      • Có thể xem ví dụ trong photo
  • Cải tiến hành vi chung

    • Cung cấp màu 24-bit, khả năng cùng tồn tại với chuyển hướng stdin/stdout/stderr, và khả năng tương thích với file help 32-bit
    • Dùng biến môi trường TVISION_MAX_FPS để điều khiển tốc độ làm mới tối đa
      • Mặc định là 60
      • 0 sẽ vô hiệu hóa giới hạn
      • -1 khiến chương trình vẽ ngay mỗi khi gọi THardwareInfo::screenWrite
    • Hỗ trợ nút chuột giữa, bánh xe chuột, kích thước màn hình tối đa 32767 hàng/cột, và sự kiện thay đổi kích thước
    • Cửa sổ có thể đổi kích thước từ cả góc dưới bên trái, và có thể kéo vùng trống bằng nút chuột giữa
    • Menu có thể đóng bằng cách bấm lại vào mục cha, còn thanh cuộn phản hồi với cuộn theo trang và thao tác bánh xe trong lúc kéo
    • TInputLine không cuộn văn bản không cần thiết khi chuyển focus
    • TFileViewerTEditor hỗ trợ xuống dòng LF; TEditor giữ nguyên kiểu xuống dòng hiện có khi lưu, còn file mới mặc định dùng CRLF
    • TEditor còn bao gồm menu chuột phải, cuộn kéo bằng nút giữa, phím xóa theo từ, cải tiến phím Home, và hỗ trợ file lớn hơn 64 KiB
    • tvdemo có applet xem sự kiện và tùy chọn đổi mẫu nền

Vòng lặp sự kiện và thay đổi API

  • Sử dụng bộ đệm ghi màn hình, và thông thường chỉ gửi ra terminal một lần cho mỗi lượt của vòng lặp sự kiện đang hoạt động
  • Nếu cần cập nhật ngay trong vòng lặp bận, có thể gọi TScreen::flushScreen()
  • TEventQueue::waitForEvents(int timeoutMs) sẽ chờ sự kiện đầu vào, đồng thời trong lúc chờ vẫn cập nhật màn hình bằng TScreen::flushScreen()
  • TProgram::getEvent() gọi hàm này với eventTimeoutMs mặc định là 20 để tránh vòng lặp bận 100% CPU
  • TEventQueue::wakeUp() là phương thức thread-safe để đánh thức vòng lặp sự kiện từ luồng khác
  • TView::getEvent(TEvent &, int timeoutMs) cho phép chờ timeout theo từng view
  • Đã thêm setTimer, killTimer, sẽ phát sinh evBroadcastcmTimerExpired khi timer hết hạn
    • Nếu periodMs âm thì timer sẽ là loại một lần và tự động được dọn dẹp
    • Sự kiện timer được tạo trong TProgram::idle() và chỉ được xử lý khi không có sự kiện bàn phím hoặc chuột
  • TDrawBuffer không còn là mảng độ dài cố định nữa, và giúp ngăn truy cập vượt phạm vi
  • TRect với move, grow, intersect, Union trả về TRect& nên có thể chaining
  • TApplication cung cấp sẵn dosShell(), cascade(), tile() và xử lý mặc định cmDosShell, cmCascade, cmTile
  • Đã thêm các kiểu như TStringView, TSpan<T>, TDrawSurface, TSurfaceView, TClipboard
  • THardwareInfo, TScreen, TEventQueue được khởi tạo khi tạo TApplication đầu tiên, thay vì trước main
  • Phần mở rộng đầu vào cũng được bổ sung
    • evMouseWheel, mwUp, mwDown, mwLeft, mwRight
    • mbMiddleButton, meTripleClick
    • evMouseUp.buttons giữ lại thông tin về nút đã được nhả
    • hotKeyStr, getAltCharStr, getCtrlCharStr cho phép triển khai phím tắt đa byte
    • Có thể định nghĩa tổ hợp phím mới như Shift+Alt+Up bằng TKey
    • TInputLine hỗ trợ limitMode của ilMaxBytes, ilMaxWidth, ilMaxChars

Tích hợp clipboard

  • Tích hợp clipboard hệ thống

    • Trước đây chỉ có clipboard nội bộ thông qua thành viên tĩnh TEditor::clipboard, và chỉ TEditor mới có thể sử dụng nó
    • Lớp TClipboard mới cho phép truy cập clipboard hệ thống, và sẽ dùng clipboard nội bộ nếu không thể truy cập
  • Môi trường hỗ trợ

    • Được hỗ trợ mặc định trên Windows, WSL và macOS
    • Trên Unix, ngoại trừ macOS, cần phụ thuộc bên ngoài
    • Khi chạy từ xa, có thể hoạt động với X11 forwarding của ssh -X, far2lputty4far2l, hoặc terminal hỗ trợ OSC 52
    • Một số terminal khác chỉ hỗ trợ sao chép
    • Thao tác dán bằng Ctrl+Shift+V hoặc Cmd+V của terminal emulator vẫn luôn khả dụng riêng biệt
  • API và xử lý dán

    • Có thể dùng TClipboard nếu định nghĩa Uses_TClipboard trước khi include <tvision/tv.h>
    • setText(TStringView text) đặt nội dung clipboard hệ thống, và sẽ dùng clipboard nội bộ nếu không thể truy cập
    • requestText() yêu cầu bất đồng bộ nội dung clipboard hệ thống, rồi nhận lại dưới dạng sự kiện evKeyDown thông thường
    • Có thể phân biệt văn bản được dán với nhập phím thông thường bằng cờ kbPaste trong keyDown.controlKeyState
    • Để giảm sự kém hiệu quả khi các đoạn dán dài bị xử lý như hàng nghìn lần nhấn phím, TView::textEvent(...) đã được thêm vào
      • Gom các văn bản evKeyDown liên tiếp để đọc vào bộ đệm người dùng
      • Khi không còn là văn bản nữa thì chuyển sang vòng lặp kế tiếp bằng putEvent()
  • Tích hợp lệnh chuẩn

    • TEditorTInputLine phản hồi các lệnh cmCut, cmCopy, cmPaste
    • Ứng dụng cần liên kết kbCtrlX, kbCtrlC, kbCtrlV với từng lệnh, chẳng hạn trên thanh trạng thái
    • Tự động bật hoặc tắt các lệnh liên quan tùy theo trạng thái focus và chọn

Mô hình màu mở rộng và tính tương thích

  • API Turbo Vision hỗ trợ màu mở rộng vượt ra ngoài 16 màu truyền thống
    • Thuộc tính màu BIOS 4-bit
    • RGB 24-bit
    • Chỉ mục bảng màu xterm-256color 8-bit
    • Màu mặc định của terminal
  • Nếu terminal không hỗ trợ một định dạng màu cụ thể, Turbo Vision sẽ lượng tử hóa để hiển thị
  • Trên nền tảng hiện đại, TColorAttr được đưa vào thay cho uchar, và TAttrPair thay cho ushort
    • TPalette cũng dùng mảng TColorAttr
    • TView::mapColor trở thành virtual, cho phép chỉ định màu mà không cần đi qua hệ thống palette phân cấp
  • TColorBIOS, TColorRGB, TColorXTerm, TColorDesired chia tách các tầng biểu diễn màu
    • TColorAttr có bitmask foreground, background, style
    • Các style gồm slBold, slItalic, slUnderline, slBlink, slReverse, slStrike
    • slReverse có độ tin cậy thấp nên khuyến nghị dùng reverseAttribute(TColorAttr attr)
  • Có ba cách dùng màu mở rộng trong TView
    • Override mapColor để hardcode theo từng chỉ mục
    • Truyền trực tiếp TColorAttr vào TDrawBuffer trong draw()
    • Chỉnh sửa chính palette của ứng dụng
  • TScreen::screenMode biểu thị khả năng hiển thị
    • smMono là monocolor
    • smBW80 là grayscale
    • smCO80 là tối thiểu 16 màu
    • smColor256 là tối thiểu 256 màu
    • smColorHigh là nhiều màu hơn mức đó, ví dụ màu 24-bit
  • Tính tương thích ngược được thiết kế để duy trì API chung không cần #ifdef
    • Trên Borland C++, TColorAttrTAttrPair lần lượt được typedef thành ucharushort
    • Trên nền tảng hiện đại, chúng có thể thay thế các vị trí ucharushort
    • Mã legacy vẫn biên dịch nguyên trạng, nhưng thuộc tính màu non-BIOS có thể bị mất ngay khi bị chuyển đổi ngầm sang uchar hoặc ushort
  • Mã trong phương thức draw vốn dùng ushort cho cả cặp chỉ mục palette và cặp thuộc tính màu vẫn hoạt động như cũ, nhưng để giữ được màu mở rộng thì chỉ cần đổi khai báo biến từ ushort sang TAttrPair
  • TColorDialog chưa được thiết kế lại, nên không thể dùng làm bộ chọn màu mở rộng ở runtime

Xây dựng, phát hành, tích hợp

  • Linux và họ Unix

    • Có thể build thư viện tĩnh libtvision.a bằng CMake và GCC/Clang
    • Đồng thời cũng tạo ra hello, tvdemo, tvedit, tvdir, mmenu, palette và Help Compiler tvhc
    • Yêu cầu gồm trình biên dịch hỗ trợ C++14, libncursesw và tùy chọn libgpm
    • Tích hợp clipboard lúc runtime sử dụng xsel, xclip, wl-clipboard
    • Trong một số môi trường có thể cần -ltinfow, nếu không khi chạy có thể gặp segmentation fault
      • Vấn đề liên quan: #11
  • Windows và các toolchain khác

    • Bản build MSVC dùng thư mục build riêng theo từng kiến trúc đích, và đầu ra được tạo thành tvision.lib cùng các ứng dụng ví dụ
    • Tùy chọn TV_USE_STATIC_RTL cho phép liên kết tĩnh runtime của Microsoft
    • Không thể liên kết lẫn lộn /MT/MD, cũng như binary debug và non-debug
    • Để dùng <tvision/tv.h> cần các cờ /permissive-, /Zc:__cplusplus, và nếu dùng submodule CMake thì chúng sẽ được tự động bật
    • Tài liệu ghi rằng Windows XP cũng khả dụng nếu dùng đúng phiên bản và cấu hình MSVC
    • MinGW build bằng CMake tương tự Linux, và nếu trình biên dịch hỗ trợ thì có thể chạy trên Windows XP trở lên
    • Cũng có thể build thư viện DOS hoặc Windows bằng Borland C++, nhưng không hỗ trợ Unicode
    • Trong môi trường Borland C++, winevdm được đề xuất như một phương án thay thế
  • Cách tích hợp vào dự án và tình trạng phát hành

    • Trong CMake, có hai cách là find_package(tvision CONFIG) hoặc add_subdirectory(tvision)
    • Cả hai cách đều tự động xử lý đường dẫn include và các liên kết cần thiết như Ncurses, GPM
    • Có port tvision trên vcpkg
    • Hiện chưa có stable release, và tác giả khuyến khích báo cáo các vấn đề phát hiện được khi nâng cấp theo commit mới nhất
    • Trên họ Unix, cần tự build ứng dụng demo
    • Binary cho Windows được cung cấp tại Actions
      • examples-dos32.zip: file thực thi 32-bit build bằng Borland C++, không hỗ trợ Unicode
      • examples-x86.zip: file thực thi 32-bit build bằng MSVC, yêu cầu Windows Vista trở lên
      • examples-x64.zip: file thực thi 64-bit build bằng MSVC, yêu cầu Windows Vista x64 trở lên

Ví dụ, tài liệu, trường hợp sử dụng

1 bình luận

 
Ý kiến trên Hacker News
  • Thật vui khi thấy kho lưu trữ này lên trang nhất, và hiện giờ tôi đang tự làm một wrapper cho chính kho này
    Tôi đang chạy Turbo Vision trên macOS qua .Net, và nó mang lại cảm giác khá kỳ diệu
    Tôi đang bổ sung API cấp cao hơn, đồng thời bao lại hoặc cải thiện palette API vốn khá lỗi thời, và cũng thêm cả layout
    Hiện vẫn đang làm rất tích cực trong một kho riêng tư; hôm nay thì chỉnh palette dựa trên surface nơi control được đặt, ngày mai lại tinh chỉnh phần khác, kiểu như vậy
    Vẫn còn việc phải làm như dọn dẹp layout, thêm các control cơ bản còn thiếu theo tiêu chuẩn ngày nay
    Trước đây tôi cũng đã dùng Terminal.Gui, nhưng có lẽ vì đang chuyển sang v2 nên khá khó dùng mà không dính bug, và Claude cũng cho thấy rất rõ không nên làm gì khi tạo thư viện TUI mà không tính đến terminal thực tế
    Vì thế tôi đã nghĩ sẽ thật tuyệt nếu có một Turbo Vision hiện đại, rồi tình cờ phát hiện kho này, và thấy nó còn hỗ trợ cả Unicode nên thực sự rất biết ơn

    • Oxygene là một phần của bộ sản phẩm Elements của RemObjects, nên ngoài Oxygene thuộc họ Pascal còn có thể trộn với nhiều ngôn ngữ phổ biến khác và mang sang Windows, macOS, Linux, Android v.v.
      https://www.remobjects.com/elements/oxygene/
      https://www.remobjects.com/elements/
    • Tôi cũng từng làm việc với bản port tvision này, và mỗi lần đụng vào framework TUI mới lại thấy cuối cùng Turbo Vision vẫn tốt hơn
      Tôi cũng đang làm một .NET wrapper, có lẽ tiến độ kém hơn, nhưng muốn mô phỏng API Windows Forms giống nhất có thể và thậm chí thêm cả TUI designer kéo-thả
      Ví dụ ở đây: https://github.com/brianluft/terminalforms/tree/main/src/TerminalFormsDemo
      Phần lớn công việc tích hợp C++ rắc rối được xử lý ở đây: https://github.com/brianluft/terminalforms/tree/main/src/tfcore
      Tôi export các hàm C đơn giản để có thể gọi bằng P/Invoke, còn phía C# thì chủ yếu tập trung vào tổ chức lớp
      Ban đầu tôi cố ép rằng mọi thứ làm được trong C++ cũng phải làm được trong C#, nhưng nó trở nên quá phức tạp; thậm chí tôi còn dùng placement new để nhét đối tượng C++ vào buffer C#, gần như đạt tới mức kế thừa lớp C++ từ phía C#, rồi thiết kế sụp đổ
      Cuối cùng tôi chuyển sang cách tiếp cận trực diện hơn, ít linh hoạt hơn nhưng đơn giản hơn rất nhiều, và để sự linh hoạt ở phía C#
      Tôi tò mò hệ thống P/Invoke của bạn được tổ chức như thế nào
    • Mỗi khi nghịch thư viện TV này là cảm giác hoài niệm được gãi đúng chỗ nên rất vui
      Nhờ vậy mà có lẽ tôi không còn thử mấy chuyện viển vông như viết app cho GEOS hay tham gia đội Hurd một người nữa
    • Tôi cũng muốn thử làm điều tương tự
      Tôi có dùng Terminal.Gui, nhưng phía TV cuốn hút hơn nên từng nghĩ tới chuyện làm wrapper, và nếu được công khai thì tôi thật sự rất muốn xem
  • Sự nghiệp lập trình của tôi đúng nghĩa là bắt đầu từ thùng rác vào thập niên 90
    Tôi nhặt được một cuốn sách Turbo Vision mà ai đó vứt đi, và lập tức mê mẩn cái TUI ánh xanh mà ai cũng có thể tạo ra

  • Bản gốc nằm trong Turbo Pascal 6, còn bản port C++ ra đời sau
    Vậy nên có thể xem đây là một bản port hiện đại của một bản port
    Borland cũng từng như vậy với các framework khác; OWL ban đầu cũng đến từ phía Turbo Pascal for Windows 1.5 trước, và khá nhiều công cụ của C++ Builder thật ra được viết bằng Delphi
    Object Pascal của Turbo Pascal 5.5, rồi Turbo Vision của bản 6 là nơi tôi nhập môn OOP, và tôi cảm thấy mình đã may mắn khi đi theo con đường đó
    Ngay cả trong môi trường như MS-DOS, tôi vẫn có thể học rất rõ các lợi ích của OOP và kiểu framework mà Turbo Vision mang lại

    • Điều thú vị là Free Vision từng là kết quả của việc ai đó dịch thủ công bản C++ từng được phát hành vào public domain, rồi chuyển ngược lại sang Object/Free Pascal
    • OWL thực sự đi trước thời đại
  • Khi Borland tung ra Turbo Pascal, Turbo C++, TurboVision, tôi cảm thấy cả một vũ trụ khả năng bỗng mở ra
    Hiệu năng compiler cũng tuyệt vời, còn các cuốn manual thì như tác phẩm nghệ thuật; ước gì tôi vẫn còn giữ những cuốn đó
    Đây đúng là một báu vật văn hóa

    • Những manual đó thực sự phi thường
      Đầu thập niên 90, tôi gần như tự học C/C++ chỉ bằng cách đọc Turbo C++ và cả chồng sách Borland đi kèm, còn bây giờ thì thật khó tưởng tượng cảnh chỉ đọc tài liệu tham khảo mà học như thế
    • Turbo Vision từ lâu đã là một kiểu tiêu chuẩn vàng đối với tôi
      Các framework TUI mới lúc nào cũng cho cảm giác thiếu thiếu gì đó, và giờ tôi định dùng lại cái này để xem đó có chỉ là hoài niệm hay không
      Tôi sẽ đưa nó vào công cụ tiếp theo của mình, và muốn gửi lời tán dương lớn tới những người đã làm ra nó
    • Đã có một thời tôi all-in với Borland
      Trừ GW-BASIC và MS-DOS, còn lại từ Turbo BASIC, Turbo Pascal, Turbo C++ cho MS-DOS và Windows 3.x, đến Turbo Vision, OWL, tất cả đều là Borland
      Tôi chỉ dùng VC++ vào khoảng bản 5, và MFC lúc nào cũng thấy quá nhạt nhòa so với sản phẩm của Borland
      Ngay cả bây giờ, cũng hiếm thứ nào thực sự bắt kịp năng lực RAD của C++ Builder, và .NET cũng mất khá lâu mới giải quyết được câu chuyện viết mức thấp và AOT kiểu Delphi
      Tôi nghĩ nên phát cho các lập trình viên Go, C++, Rust mỗi người vài bản Turbo Pascal 7 cho MS-DOS cùng Delphi hiện đại
  • Turbo Vision 2.0 đến giờ vẫn khá thực dụng, nên một năm trước tôi đã trực tiếp dùng nó cho một công việc nguyên mẫu
    Tôi đã thử làm một frontend Turbo Vision cho debugger LLDB để nó hoạt động giống Turbo Debugger của Borland, và phần lớn mọi thứ diễn ra đúng như mong muốn
    Thật đáng ngạc nhiên khi nó như tiếp nối đúng chỗ đã dừng lại từ những năm 199x, và tôi còn có thể compile rồi chạy mã từ năm 1993 mà không gặp vấn đề lớn
    Trình editor bên trong cũng có một phiên bản tốt hơn dựa trên Scintilla, với các tính năng như syntax highlighting, nhưng phần tôi định sửa thì không suôn sẻ lắm nên có lẽ phải nhờ tác giả giúp
    Tuy vậy, theo nghĩa tri thức chung hiện đại thì tài liệu vẫn còn thiếu, nên khó mà hỏi Stack Overflow hay AI; tôi buộc phải quay về cách cũ là học bằng ví dụ mã nguồn và đọc đi đọc lại vài cuốn sách Turbo Vision
    Layout thủ công khá phiền, nên sẽ hay nếu có auto layout kiểu Qt, và tôi cũng hơi nhớ splitter, dù việc tự triển khai có vẻ không khó
    Một điều nữa khiến tôi ngạc nhiên là TV thực ra khá nhỏ gọn và compact. Hồi thập niên 90 nó từng cho cảm giác rất đồ sộ
    Nhìn chung, công cuộc hiện đại hóa được làm rất tốt và tôi cực kỳ thích nó

  • Chỉ cần nhìn thấy cả đống chỉ thị cmake là tôi đã muốn quay lại quá khứ
    Với Turbo C hay Pascal, chỉ cần nhấn F9 là chạy ngay
    Mặt khác, tôi cũng thấy điều này cho thấy sự bất lực của toolchain của chúng ta
    Ở thời đại này, lẽ ra chỉ cần trỏ vào một compiler online để chạy ngay, hoặc tải về rồi mở một thư mục và chạy là xong, nhưng nó đã thành nghi thức hơn là công cụ

    • Trên Unix hiện đại, việc compile phần mềm từng là một vấn đề đã được giải quyết
      ./configure && make && make install mới đúng là gold standard và vẫn nên như vậy
  • Đây chỉ là một trong các bản port/bản clone của Turbo Vision
    Phía C++ còn có cái này: https://github.com/kloczek/tvision
    Bản đi kèm FreePascal/Lazarus được viết bằng Pascal, và cũng có một bản Rust, dù trông hơi vibe-coded: https://github.com/aovestdipaperino/turbo-vision-4-rust

  • Chạy nó trong terminal thì có phần mất đi cảm giác cốt lõi mà chuột của màn hình chế độ văn bản mang lại
    Trên màn hình text mode thật, nó không hiện như con trỏ chuột mà giống một khối vàng di chuyển bằng chuột hơn
    Tôi tò mò không biết có ai từng chạy nó trên text mode Linux độ phân giải cao với GPM chưa

    • Về bản chất nó không hẳn là màu vàng
      Nó hoạt động bằng cách đảo màu của ô bị đè lên, và vì cửa sổ chính tối xanh lam thường phủ phần lớn màn hình nên kết quả thường trông như một khối vàng sáng
  • Tôi khuyên nghe tập gần đây của Wookash podcast nói về Chuck Jazdzewski
    Ông là một thành viên của đội ngũ gốc tạo ra Turbo Vision, và cũng có nhiều câu chuyện về toàn bộ hệ sinh thái đó

  • Tôi vẫn muốn Turbo Vision thật sự, tức bản Pascal, hơn là bản C++
    Bản C++ cuối cùng vẫn cho cảm giác gần như là phía được chuyển từ bản Pascal sang
    Ví dụ trong Pascal, uses là từ khóa, còn kiểu include module bằng #define thì thế nào cũng thấy như một trò hack
    Dĩ nhiên bây giờ có thể khác biệt đó không còn lớn nữa

    • Free Vision đi kèm Free Pascal về cơ bản đảm nhiệm vai trò đó
      IDE text mode cũng dùng Free Vision
      https://wiki.lazarus.freepascal.org/images/1/19/Userscreen.png
      Tuy nhiên, điểm khác biệt cốt lõi là Free Vision và Turbo Vision dùng kiểu object từ thời Turbo Pascal 5.5 chứ không phải class của Delphi
      class nhờ có RTTI nên dễ hiện thực những thứ như tuần tự hóa tự động, còn object thì không có; vì vậy nếu muốn phân biệt các kiểu khác nhau lúc runtime thì phải làm tuần tự hóa thủ công, chẳng hạn đăng ký VMT pointer nằm ở offset cố định của object pointer
      Free Pascal có bổ sung cho object một số tiện ích như private/protected/public, property, nhưng Free Vision không dùng các phần mở rộng đó vì phải triển khai API Turbo Vision gốc