Design (LLD) Sublime Text IDE - Machine Coding

Design (LLD) Sublime Text IDE - Machine Coding

Features Required

  1. Multiple File Handling: Ability to handle multiple open files with tabs.

  2. Syntax Highlighting: Highlight code based on the programming language.

  3. Auto-completion: Suggest code completions based on current context.

  4. Find and Replace: Search for specific text in the code and replace it.

  5. Command Palette: Allow users to access all available commands in the IDE.

  6. Plugin System: Support for plugins/extensions to enhance IDE functionalities.

  7. Undo/Redo Functionality: Support for undoing and redoing code changes.

  8. Code Folding: Hide and show parts of code for better readability.

  9. Themes and Customization: Allow users to change the look and feel of the IDE.

  10. Code Formatting: Automatically format the code based on predefined rules.

Design Patterns

  1. Observer Pattern:

    • Why: To handle event-driven updates like notifying different parts of the IDE (e.g., UI, file system) when a file is saved or edited.

    • Feature: File editing, save actions, and auto-suggestions.

  2. Command Pattern:

    • Why: Encapsulates each operation as an object, such as 'undo', 'redo', 'find', 'replace'. It helps in decoupling the UI from business logic.

    • Feature: Undo/Redo, Find and Replace, and Command Palette operations.

  3. Strategy Pattern:

    • Why: For different strategies of syntax highlighting based on the language (Python, Java, C++).

    • Feature: Syntax Highlighting and Code Formatting.

  4. Factory Pattern:

    • Why: To create objects based on the required type (e.g., different language parsers for syntax highlighting).

    • Feature: Plugin system and syntax highlighting for different languages.

  5. Decorator Pattern:

    • Why: To add functionalities like auto-completion, syntax highlighting, and code formatting dynamically to an editor component.

    • Feature: Code auto-completion, Syntax Highlighting.

  6. Memento Pattern:

    • Why: To capture and restore the state of an editor during undo/redo operations.

    • Feature: Undo/Redo functionality.

  7. Singleton Pattern:

    • Why: To ensure that certain global managers like theme manager, plugin manager, and command palette are single instances.

    • Feature: Theme Manager, Plugin Manager.

Multiple Algorithms Involved:

  1. Trie Data Structure:

    • Feature: For auto-completion, a trie helps store and search for code completion suggestions efficiently.
  2. Text Search Algorithms:

    • Feature: Knuth-Morris-Pratt (KMP) or Rabin-Karp algorithm for the 'Find' feature.
  3. Syntax Parsing Algorithm:

    • Feature: Custom parsing for code folding and syntax highlighting.

Code (Java)

import java.util.*;

// Observer Pattern - File Change Listener Interface
interface FileChangeListener {
    void onFileChanged(String fileName);
}

// Observer Pattern - Editor as a Listener
class FileEditor implements FileChangeListener {
    private String content;

    public FileEditor(String content) {
        this.content = content;
    }

    @Override
    public void onFileChanged(String fileName) {
        System.out.println(fileName + " has been modified.");
    }

    public void editContent(String newContent) {
        this.content = newContent;
        System.out.println("File content edited.");
    }

    public String getContent() {
        return content;
    }
}

// Observer Pattern - FileManager with Notifier
class FileManager {
    private List<FileChangeListener> listeners = new ArrayList<>();
    private Map<String, FileEditor> openFiles = new HashMap<>();

    public void addListener(FileChangeListener listener) {
        listeners.add(listener);
    }

    public void openFile(String fileName, String content) {
        FileEditor fileEditor = new FileEditor(content);
        openFiles.put(fileName, fileEditor);
        notifyListeners(fileName);
    }

    public void editFile(String fileName, String newContent) {
        FileEditor fileEditor = openFiles.get(fileName);
        if (fileEditor != null) {
            fileEditor.editContent(newContent);
            notifyListeners(fileName);
        } else {
            System.out.println("File not found.");
        }
    }

    public void notifyListeners(String fileName) {
        for (FileChangeListener listener : listeners) {
            listener.onFileChanged(fileName);
        }
    }

    public FileEditor getFileEditor(String fileName) {
        return openFiles.get(fileName);
    }
}

// Command Pattern - Command Interface
interface Command {
    void execute();
    void undo();
}

// Command Pattern - Undo Command
class UndoCommand implements Command {
    private FileEditor fileEditor;
    private String prevContent;

    public UndoCommand(FileEditor fileEditor) {
        this.fileEditor = fileEditor;
        this.prevContent = fileEditor.getContent();
    }

    @Override
    public void execute() {
        System.out.println("Undo operation performed.");
        fileEditor.editContent(prevContent);
    }

    @Override
    public void undo() {
        System.out.println("Redo operation performed.");
        // Redo is technically restoring the new content, but for simplicity we retain old content in this demo
        fileEditor.editContent(prevContent);
    }
}

// Strategy Pattern - Syntax Highlighter
interface SyntaxHighlighter {
    void highlight(String code);
}

// Strategy Pattern - Python Highlighter
class PythonHighlighter implements SyntaxHighlighter {
    @Override
    public void highlight(String code) {
        System.out.println("Syntax Highlighting for Python code: " + code);
    }
}

// Strategy Pattern - Java Highlighter
class JavaHighlighter implements SyntaxHighlighter {
    @Override
    public void highlight(String code) {
        System.out.println("Syntax Highlighting for Java code: " + code);
    }
}

// Factory Pattern - Syntax Highlighter Factory
class SyntaxHighlighterFactory {
    public static SyntaxHighlighter getHighlighter(String language) {
        switch (language.toLowerCase()) {
            case "python":
                return new PythonHighlighter();
            case "java":
                return new JavaHighlighter();
            default:
                throw new IllegalArgumentException("Unsupported language");
        }
    }
}

// Decorator Pattern - CodeEditor base class
abstract class CodeEditor {
    public abstract void display();
}

// Decorator Pattern - Basic Code Editor
class BasicEditor extends CodeEditor {
    @Override
    public void display() {
        System.out.println("Basic editor without additional features.");
    }
}

// Decorator Pattern - Adding Auto-Completion to CodeEditor
class AutoCompletionDecorator extends CodeEditor {
    private CodeEditor editor;

    public AutoCompletionDecorator(CodeEditor editor) {
        this.editor = editor;
    }

    @Override
    public void display() {
        editor.display();
        System.out.println("Auto-completion feature enabled.");
    }
}

// Memento Pattern - Saving and Restoring Editor State
class EditorState {
    private String content;

    public EditorState(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

// Memento Pattern - Editor History to Save States
class EditorHistory {
    private Stack<EditorState> history = new Stack<>();

    public void saveState(EditorState state) {
        history.push(state);
    }

    public EditorState undo() {
        if (!history.isEmpty()) {
            return history.pop();
        }
        return null;
    }
}

// Singleton Pattern - Theme Manager
class ThemeManager {
    private static ThemeManager instance;

    private ThemeManager() {}

    public static ThemeManager getInstance() {
        if (instance == null) {
            instance = new ThemeManager();
        }
        return instance;
    }

    public void applyTheme(String theme) {
        System.out.println("Applying theme: " + theme);
    }
}

// Sublime IDE Simulation
public class SublimeIDE {
    public static void main(String[] args) {
        // Observer Pattern Usage: Editing a File and Notifying Listeners
        FileManager fileManager = new FileManager();
        FileEditor fileEditor = new FileEditor("Initial content of test.java");
        fileManager.addListener(fileEditor);
        fileManager.openFile("test.java", "Initial content of test.java");
        fileManager.editFile("test.java", "Modified content of test.java");

        // Command Pattern Usage: Undo Command
        UndoCommand undoCommand = new UndoCommand(fileManager.getFileEditor("test.java"));
        undoCommand.execute();

        // Strategy Pattern Usage: Syntax Highlighting
        SyntaxHighlighter pythonHighlighter = SyntaxHighlighterFactory.getHighlighter("python");
        pythonHighlighter.highlight("def hello_world():");

        SyntaxHighlighter javaHighlighter = SyntaxHighlighterFactory.getHighlighter("java");
        javaHighlighter.highlight("public static void main(String[] args) {}");

        // Decorator Pattern Usage: Adding Auto-completion to Basic Editor
        CodeEditor basicEditor = new BasicEditor();
        CodeEditor editorWithAutoComplete = new AutoCompletionDecorator(basicEditor);
        editorWithAutoComplete.display();

        // Memento Pattern Usage: Saving and Restoring Editor State
        EditorHistory editorHistory = new EditorHistory();
        editorHistory.saveState(new EditorState(fileEditor.getContent()));

        // Modifying content again
        fileManager.editFile("test.java", "Second modification.");
        editorHistory.saveState(new EditorState(fileEditor.getContent()));

        // Undo to restore previous state
        EditorState previousState = editorHistory.undo();
        if (previousState != null) {
            fileManager.getFileEditor("test.java").editContent(previousState.getContent());
            System.out.println("Restored content: " + fileManager.getFileEditor("test.java").getContent());
        }

        // Singleton Pattern Usage: Applying a Theme
        ThemeManager themeManager = ThemeManager.getInstance();
        themeManager.applyTheme("Dark Mode");

        // Final demonstration of file state
        System.out.println("Final content of test.java: " + fileManager.getFileEditor("test.java").getContent());
    }
}

Issues in Multi-Threaded System:

  1. Race Conditions:

    • FileManager: Multiple threads could try to open, edit, or save files simultaneously. Since openFiles is a shared resource (a HashMap), concurrent access can lead to race conditions, resulting in data corruption or exceptions.

    • FileEditor: If multiple threads are modifying a file simultaneously, the file’s content may end up in an inconsistent state.

    • EditorHistory: The undo/redo operations depend on the order of state changes. If multiple threads are pushing states concurrently, this will corrupt the history and make undo operations unreliable.

  2. Lack of Synchronization:

    • Listeners in FileManager: The list of FileChangeListener objects is not thread-safe. If one thread is modifying the list while another is iterating over it (for notifications), it may throw ConcurrentModificationException.

    • Undo/Redo operations: Multiple threads executing undo/redo commands simultaneously can result in corrupted file states, as there's no control over the sequence of operations.

  3. Non-Atomic Operations:

    • File Operations: Opening a file, editing it, and saving changes are not atomic operations. If a file is opened by one thread and edited by another before being saved, the system will produce inconsistent results.
  4. Thread Interference:

    • EditorState: Without proper synchronization, multiple threads can interfere with each other when modifying or restoring the editor state, leading to lost updates or restoring incorrect states.
  5. Singleton Pattern (ThemeManager):

    • The ThemeManager is not thread-safe. In a multi-threaded environment, multiple threads may try to create an instance simultaneously, breaking the singleton contract.

Solutions for a Multi-Threaded System

Covered in Premium Course - https://tagmango.com/web/checkout/65e78c83760483f8e9dd2e5b


Garbage Collection (GC) Issues in the Above Implementation

While the implementation is functionally complete and well-structured using design patterns, there are some potential areas where Garbage Collection (GC) issues could arise. Below are some considerations and potential issues:

1. Memory Leaks due to Observer Pattern:

  • Issue: In the Observer Pattern (FileManager and FileEditor), the FileManager maintains a list of listeners (FileChangeListener instances). If a listener (e.g., FileEditor) is no longer needed (e.g., the file is closed or removed), but the reference to it is not removed from the FileManager, this can cause a memory leak. The FileEditor object will not be garbage collected because it is still being referenced by the FileManager.

  • Solution:

    • Covered in Premium Course.

2. Retained Command History (Memento Pattern):

  • Issue: In the Memento Pattern, the EditorHistory class stores the states of the editor (i.e., EditorState objects). If the state history grows too large, this could result in excessive memory consumption and cause the GC to struggle with large objects, leading to GC pressure or OutOfMemoryError.

  • Solution:

    • Covered in Premium Course.

3. Clean UP for Singleton Objects:

  • Issue: The Singleton Pattern (e.g., ThemeManager) creates a single instance of the class, which stays in memory for the entire lifecycle of the application. If the theme manager holds references to other objects (like UI components or theme assets), this could lead to memory retention issues, especially if those references are no longer needed but the ThemeManager doesn’t release them.

  • Solution:

    • Covered in Premium Course.

4. Large Collections and Cache Handling (Factory Pattern):

  • Issue: In the Factory Pattern, if the factory maintains a cache of objects (e.g., syntax highlighters), there is a risk of retaining unnecessary objects in memory, especially if these objects are no longer needed. This could cause unnecessary memory retention, leading to GC issues.

  • Solution:

    • Covered in Premium Course.

5. Excessive Object Creation:

  • Issue: Every time the file is edited or a command is executed, new objects (e.g., FileEditor, UndoCommand, EditorState) are created. While this may not cause immediate issues, frequent object creation could result in excessive object allocation, leading to frequent GC pauses (especially in large applications or when many files are opened).

  • Solution:

    • Covered in Premium Course.

6. Stale or Unused File Editors:

  • Issue: The FileManager keeps a reference to all opened FileEditor objects. If a file is closed but not removed from the openFiles map, these editors will stay in memory, potentially causing memory bloat.

  • Solution:

    • Covered in Premium Course.

Did you find this article valuable?

Support Subhahu Jain by becoming a sponsor. Any amount is appreciated!