Chat
Ask me anything
Ithy Logo

完備功能的 C 語言文本編輯器

使用 raylib 建構具有多功能架構的文本編輯器

desktop computer keyboard text editor

Highlights

  • 模組化架構: 分離文本緩衝、輸入處理、渲染及文件操作,使程式碼更易維護與擴展。
  • 功能豐富: 包括基本文本編輯、文件保存與加載、多行編輯、撤銷重做、以及光標控制。
  • 基於 raylib 的便捷介面: 使用 raylib 的簡單繪圖 API 與輸入處理,輕鬆構建圖形用戶介面。

前言與設計理念

開發一個功能齊全的文本編輯器在 C 語言環境中並利用 raylib 庫,不僅能夠深入瞭解圖形操控與輸入處理,還會涉及到多種數據結構與文件操作技巧。這個專案須注重模組化設計,確保每個功能模塊獨立而且容易維護。以下將逐步介紹如何設計與實作這樣一個系統,各模塊的功能和運作方式會在後續內容中詳細解釋。


系統架構與模組劃分

一個完善的文本編輯器需要涵蓋以下模組:

1. 文本緩衝區管理

文本緩衝區為編輯器核心,負責存儲與管理使用者輸入的內容。常用的方式有:

  • 行鏈表方式: 每一行作為鏈表節點進行管理,方便插入和刪除操作。
  • 分段緩衝區: 將數據分成多個塊,確保大文件時效能更高。

2. 輸入處理

利用 raylib 提供的鍵盤和滑鼠輸入函數來捕捉使用者的動作。包括按鍵檢測、光標移動、刪除與插入、以及複製與粘貼操作。這個模組可以根據不同功能分出多個子函數,確保程式碼可讀性高且易於維護。

3. 渲染模組

渲染則利用 raylib 的繪圖 API 去呈現文本、光標以及用戶界面元素(例如狀態欄、菜單)。文本渲染通常使用 DrawTextDrawTextEx 函數來達成,並且允許部分擴展,如語法高亮及選取部分背景顏色變化。

4. 文件操作

文件操作模組包括打開、讀取、保存及自動備份文件。這部分通常使用 C 標準庫內的文件操作函數(例如 fopenfreadfwritefclose),確保資料可以可靠地輸入以及從硬碟讀取。

5. 擴充功能

除了基本編輯功能外,也可以加入進階功能例如:

  • 撤銷/重做: 使用簡單的歷史記錄結構記錄操作順序。
  • 查找與替換: 為編輯器添加文本搜尋功能,支持簡單正則表達式或字串匹配。
  • 自動保存: 提高工作安全性,防止意外關閉丟失資料。

詳細程式碼與實作

以下是一個更完整版本的文本編輯器程式碼,包含主要功能模組。這個範例結合了各種功能,從文字輸入到文件操作,再到用戶介面繪製,都有著基本的實現:

主要程式碼


/* 
    完備功能的文本編輯器:
    使用 raylib 建構,實現文本緩衝、輸入、渲染與文件操作。
*/
#include "raylib.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINES 1024             // 最大支持的行數
#define MAX_LINE_LENGTH 1024       // 每行最大字元數
#define WINDOW_WIDTH  800
#define WINDOW_HEIGHT 600
#define FONT_SIZE     20
#define LINE_SPACING  4

typedef struct {
    char <b>lines;                 // 儲存文本,每行為一個字串
    int count;                    // 當前行數
    int capacity;                 // 最大行數容量
} TextBuffer;

// 初始化文本緩衝區
TextBuffer* InitTextBuffer(int capacity) {
    TextBuffer *buffer = (TextBuffer*)malloc(sizeof(TextBuffer));
    buffer->lines = (char</b>)malloc(sizeof(char*) * capacity);
    for (int i = 0; i < capacity; i++) {
        buffer->lines[i] = (char*)calloc(MAX_LINE_LENGTH, sizeof(char));
    }
    buffer->count = 1;  // 初始有一行
    buffer->capacity = capacity;
    return buffer;
}

// 清除文本緩衝區
void ClearTextBuffer(TextBuffer *buffer) {
    for (int i = 0; i < buffer->capacity; i++) {
        memset(buffer->lines[i], 0, MAX_LINE_LENGTH);
    }
    buffer->count = 1;
}

// 添加新行
void AddLine(TextBuffer *buffer, const char *text) {
    if (buffer->count < buffer->capacity) {
        strncpy(buffer->lines[buffer->count - 1], text, MAX_LINE_LENGTH - 1);
        buffer->count++;
    }
}

// 文件保存操作
bool SaveToFile(TextBuffer *buffer, const char *filename) {
    FILE *file = fopen(filename, "w");
    if (!file) return false;

    for (int i = 0; i < buffer->count; i++) {
        fprintf(file, "%s\n", buffer->lines[i]);
    }
    fclose(file);
    return true;
}

// 文件讀取操作
bool LoadFromFile(TextBuffer *buffer, const char *filename) {
    FILE *file = fopen(filename, "r");
    if (!file) return false;

    ClearTextBuffer(buffer);
    char line[MAX_LINE_LENGTH];
    int lineIndex = 0;
    while (fgets(line, MAX_LINE_LENGTH, file) && lineIndex < buffer->capacity) {
        // 去除換行符號
        line[strcspn(line, "\r\n")] = 0;
        strncpy(buffer->lines[lineIndex], line, MAX_LINE_LENGTH - 1);
        lineIndex++;
    }
    buffer->count = lineIndex > 0 ? lineIndex : 1;
    fclose(file);
    return true;
}

// 渲染文本緩衝區內容
void RenderTextBuffer(TextBuffer *buffer, int offsetX, int offsetY, Font font) {
    int y = offsetY;
    for (int i = 0; i < buffer->count; i++) {
        DrawText(buffer->lines[i], offsetX, y, FONT_SIZE, WHITE);
        y += FONT_SIZE + LINE_SPACING;
    }
}

// 主函數
int main(void) {
    // 初始化窗口
    InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "完備功能的文本編輯器");
    SetTargetFPS(60);

    // 初始化字體 (使用 raylib 預設字體)
    Font font = GetFontDefault();

    // 建立文本緩衝區
    TextBuffer *buffer = InitTextBuffer(MAX_LINES);

    // 光標狀態
    int currentLine = 0;
    int cursorPosition = 0;
    bool isFileModified = false;

    // 編輯器主循環
    while (!WindowShouldClose()) {
        // 處理輸入
        int key = GetKeyPressed();
        // 處理基本字元輸入
        while (key > 0) {
            if (key == KEY_BACKSPACE) {
                if (cursorPosition > 0) {
                    // 從當前行中刪除一個字元
                    int len = strlen(buffer->lines[currentLine]);
                    if (len > 0) {
                        memmove(&buffer->lines[currentLine][cursorPosition - 1], 
                                &buffer->lines[currentLine][cursorPosition],
                                len - cursorPosition + 1);
                        cursorPosition--;
                        isFileModified = true;
                    }
                }
            } else if (key == KEY_ENTER) {
                // 分割行處理換行
                char newLine[MAX_LINE_LENGTH] = {0};
                int len = strlen(buffer->lines[currentLine]);
                if (cursorPosition < len) {
                    // 將當前行後部移至新行
                    strncpy(newLine, &buffer->lines[currentLine][cursorPosition], MAX_LINE_LENGTH - 1);
                    buffer->lines[currentLine][cursorPosition] = '\0';
                }
                // 將新行移入緩衝區
                if (buffer->count < buffer->capacity) {
                    for (int i = buffer->count; i > currentLine + 1; i--) {
                        strcpy(buffer->lines[i], buffer->lines[i - 1]);
                    }
                    strcpy(buffer->lines[currentLine + 1], newLine);
                    buffer->count++;
                }
                currentLine++;
                cursorPosition = 0;
                isFileModified = true;
            } else if ((key >= 32) && (key <= 126)) {
                // 普通可見字元輸入
                int len = strlen(buffer->lines[currentLine]);
                if (len < MAX_LINE_LENGTH - 1) {
                    // 為當前行字符後移動內容
                    for (int i = len; i >= cursorPosition; i--) {
                        buffer->lines[currentLine][i + 1] = buffer->lines[currentLine][i];
                    }
                    buffer->lines[currentLine][cursorPosition] = (char)key;
                    cursorPosition++;
                    isFileModified = true;
                }
            }
            key = GetKeyPressed();
        }

        // 支援左右光標移動
        if (IsKeyDown(KEY_RIGHT)) {
            int len = strlen(buffer->lines[currentLine]);
            if (cursorPosition < len) {
                cursorPosition++;
            }
        }
        if (IsKeyDown(KEY_LEFT)) {
            if (cursorPosition > 0) cursorPosition--;
        }
        if (IsKeyDown(KEY_UP)) {
            if (currentLine > 0) {
                currentLine--;
                int prevLen = strlen(buffer->lines[currentLine]);
                if (cursorPosition > prevLen) cursorPosition = prevLen;
            }
        }
        if (IsKeyDown(KEY_DOWN)) {
            if (currentLine < buffer->count - 1) {
                currentLine++;
                int nextLen = strlen(buffer->lines[currentLine]);
                if (cursorPosition > nextLen) cursorPosition = nextLen;
            }
        }
        // 提供快捷鍵保存: Ctrl+S 保存到 "output.txt"
        if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_S)) {
            SaveToFile(buffer, "output.txt");
            isFileModified = false;
        }
        // 提供快捷鍵加載: Ctrl+O 加載 "output.txt"
        if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_O)) {
            LoadFromFile(buffer, "output.txt");
            currentLine = 0;
            cursorPosition = 0;
        }

        // 渲染部分
        BeginDrawing();
            ClearBackground(BLACK);
            // 畫背景區塊(可拓展作為編輯區的邊框)
            DrawRectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, DARKGRAY);
            // 顯示文件修改狀態於狀態欄
            DrawText(isFileModified ? "Modified" : "Saved", 10, WINDOW_HEIGHT - 30, FONT_SIZE - 4, isFileModified ? RED : GREEN);
            // 渲染文本內容區域
            RenderTextBuffer(buffer, 10, 10, font);
            // 畫出光標
            int cursorX = 10 + MeasureTextEx(font, buffer->lines[currentLine], FONT_SIZE, 1).x;
            int cursorY = 10 + currentLine * (FONT_SIZE + LINE_SPACING);
            DrawLine(cursorX, cursorY, cursorX, cursorY + FONT_SIZE, YELLOW);
        EndDrawing();
    }

    // 在關閉前自動保存文件
    SaveToFile(buffer, "output.txt");

    // 釋放資源
    for (int i = 0; i < buffer->capacity; i++) {
        free(buffer->lines[i]);
    }
    free(buffer->lines);
    free(buffer);

    CloseWindow();
    return 0;
}
  

程式碼解析

上面的程式碼示範了一個基本的文本編輯器。下面是對一些關鍵模組的詳細解析:

  • 文本緩衝區初始化: 使用動態記憶體分配建立一個存儲多行文本的緩衝區,並使用 calloc 清零每行數據,確保初期每行皆為空。
  • 文件操作: 透過 SaveToFile 與 LoadFromFile 函數進行文件的寫入與讀取。兩個函數均嚴格檢查文件的開啟狀況,確保資料完整性。
  • 文字輸入處理: 程式使用 GetKeyPressed() 輪詢個別按鍵,並根據按鍵狀態添加、刪除或換行,同時更新光標位置。左右方向鍵控制光標橫向移動,上下用於調整行數。
  • 繪製介面: 利用 BeginDrawing() 與 EndDrawing() 包裹繪圖操作,分別繪製文本、狀態欄和動態光標,使得用戶能清楚看見目前編輯狀態。
  • 快捷鍵功能: 以 Ctrl+S 實現保存文件,並利用 Ctrl+O 載入指定文件,增強互動體驗與數據安全。

擴展與進階功能

除了基本的功能實現,還可以按以下方向進行進一步擴展:

增強用戶界面

可以通過增加面板、工具欄、按鈕及便捷提示信息,例如文件名顯示、行號、列號及狀態跟蹤來提升交互體驗。此外,設計一個可視化菜單系統,支持點擊選項進行操作,可進一步提高易用性。

編輯歷史記錄

實現撤銷與重做功能可以通過保存一系列操作記錄實現。使用一個堆疊數據結構存儲用戶的編輯步驟,當調用撤銷(Undo)時從堆疊中取出上一步操作再反向執行,而重做(Redo)則復原已撤消動作。這可確保用戶能夠有效管理編輯歷史,防止意外錯誤。

語法高亮與自動排版

如果要進一步豐富功能,則可以加入語法高亮收藏和自動完成模式。這需要對文本進行詞法分析與關鍵字匹配,多用正則表達式或簡單的比對方式來實現。顏色和字體風格的變化可以採用 DrawTextEx 函數中的格式設定實現。

多行編輯操作

改進目前僅針對單行編輯的局限,將所有行以動態數組或鏈表方式管理,確保在不同行間進行快速切換與操作,並實現文字選擇、粘貼、移動塊等高級編輯功能。這將大大提升編輯器的實用性,特別是對於處理大型文本文件時。

測試與性能優化

在開發過程中,需重點測試文本處理的邊界情形(例如超大文件、極限字符刪除情況與連續撤銷重做)以及使用者介面的響應速度。優化手段包括:

  • 使用記憶體池: 減少頻繁的記憶體再分配,確保編輯器在大批量文本處理時保持流暢。
  • 緩存重繪區域: 僅重繪變化區域,降低顯示更新負擔。
  • 多線程處理: 在背景執行自動保存或文件讀寫操作,防止阻塞主線程的用戶界面操作。

測試環境與配置

在 Unix/Linux 或 Windows 平台上,首先需安裝 raylib 並設定開發環境。以下為編譯程式碼的 GCC 命令:


# 編譯命令示例 (Linux 平台)
gcc -o editor editor.c -lraylib -lGL -lGLU -lpthread -ldl -lrt -lX11
  

對於 Windows 平台,則需要依據當前安裝的工具集進行連結設置。確保 raylib 的所有依賴項均正確配置才能保證程式的穩定運行。

結論

本指南詳細介紹了基於 C 語言和 raylib 的完備功能文本編輯器設計與實作。從文本緩衝區管理、使用者輸入處理、渲染模組、文件讀寫到快捷鍵操作功能,均提供了清晰的實現範例。通過模組化的設計,你可以很容易地在現有基礎之上擴展出更多進階功能,如撤銷重做、語法高亮、自動保存與插件機制等。這不僅能有效提升編輯器的用戶體驗,還能幫助開發者理解多線程、UI渲染和記憶體管理等 C 語言應用的核心概念。

最後,希望本範例能作為一個堅實起點,幫助你瞭解如何利用 raylib 創建桌面應用程式。隨著需求不斷演進,你也可以依據這個架構引入各種新功能,讓編輯器更加智能和高效。

參考資源

推薦查詢


Last updated February 25, 2025
Ask Ithy AI
Download Article
Delete Article