Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6eb5d49
Support detecting homebrew prefix directory for Apple Silicon
jgavris Dec 26, 2021
963ea60
Fix cast of non Objective-C pointer in shared_ptr
jgavris Dec 26, 2021
7579de9
Silence vfork deprecated on macOS Monterey until migration to posix_s…
jgavris Dec 26, 2021
42059a4
Fix configure
tagliala Sep 25, 2025
064468b
Fix Ruby scripts
tagliala Sep 25, 2025
1fb9916
Fix broken hyperlink to Ragel
staykids Feb 24, 2022
acee8b8
Fix "toggleing" typo in Preferences
staykids Feb 24, 2022
add1429
Avoid clang 15 crash by using correct array size
neverpanic Jul 19, 2023
2753f4c
Fix incorrect number of seconds in a day
ndbroadbent May 18, 2024
4af8e74
Add attr.filebrowser when file browser pane has focus
pravdomil Jul 13, 2025
466e51c
Add comprehensive copilot instructions for TextMate development (#1)
Copilot Sep 25, 2025
6069685
Add GitHub Actions workflow for building TextMate package with manual…
Copilot Sep 25, 2025
04bb3b8
Fix build workflow to execute automatically on pushes and PRs (#4)
Copilot Sep 25, 2025
e3e1a00
Add cache support for Homebrew and intermediate compile in build work…
Copilot Sep 25, 2025
3ff70d5
Fix build workflow warnings and restrict artifact to TextMate.app onl…
Copilot Sep 25, 2025
4d8117b
Fix file browser and document view backgrounds (#9)
tagliala Sep 25, 2025
2453f1d
Drop macOS < 14 compatibility (#12)
Copilot Sep 26, 2025
054dde1
Modernize ERB usage to eliminate Ruby 3.x deprecation warnings (#13)
Copilot Sep 26, 2025
2556c85
Fix gutter color (#14)
tagliala Sep 26, 2025
fa2f55b
Center tabs (#18)
tagliala Sep 29, 2025
61c972b
Bump version (#20)
tagliala Oct 3, 2025
4515b22
Fix config: test for either .a or .dylib libs (#21)
leeronr Feb 24, 2026
8c74cec
Fix Ruby script for newer rubies (#24)
tagliala May 12, 2026
0c2f932
Center tab titles and close buttons in OakTabBarView (#25)
Copilot May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/BUILD_PACKAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Building TextMate Package via GitHub Actions

This repository includes a GitHub Actions workflow to build TextMate on macOS and create a distributable package.

## How to Trigger the Build

1. Navigate to the **Actions** tab in the GitHub repository
2. Select the **"Build TextMate Package"** workflow
3. Click **"Run workflow"**
4. Choose the build type (release or debug) - defaults to release
5. Click **"Run workflow"** button

## What the Workflow Does

The workflow performs the following steps:

1. **Environment Setup**:
- Checks out the repository with submodules
- Sets up Ruby 4.0
- Installs required Homebrew dependencies:
- boost (portable C++ libraries)
- capnp (Cap'n Proto serialization)
- google-sparsehash (cache-friendly hash map)
- multimarkdown (marked-up plain text compiler)
- ninja (build system)
- ragel (state machine compiler)
- gdbm (GNU Database Manager, needed for Ruby DBM gem)

2. **Ruby Dependencies**:
- Runs `bundle install` to install the gems declared in `Gemfile`
- Verifies the bundled `dbm` gem can be loaded properly

3. **Build Process**:
- Runs `./configure` to check dependencies and generate build files
- Executes `ninja TextMate` to build the application
- Handles build errors while ignoring warnings (as requested)

4. **Package Creation**:
- Locates the built TextMate.app bundle
- Copies it to a package directory
- Creates a downloadable artifact

## Download the Package

After the workflow completes successfully:

1. Go to the workflow run page
2. Scroll down to the **Artifacts** section
3. Download the `textmate-package-<commit-sha>` artifact
4. Extract the ZIP file to get the TextMate.app bundle

## Build Configuration

- Default build type is **release** (optimized for distribution)
- Debug builds include additional debugging symbols and sanitizers
- The build target is `TextMate` which creates the full application bundle

## Requirements Met

This workflow addresses all the requirements from the issue:

- ✅ Manual trigger via `workflow_dispatch`
- ✅ macOS runner (`macOS-latest`)
- ✅ All Homebrew dependencies installed
- ✅ Ruby setup with a Bundler-managed `dbm` gem
- ✅ Package creation for distribution
- ✅ Build warnings ignored (errors still cause failure)
- ✅ Artifact upload for easy download

## Troubleshooting

If the build fails:

1. Check the workflow logs for specific error messages
2. Verify all dependencies are correctly installed
3. Check if there are any changes needed to the build configuration
4. The workflow will list build artifacts even on partial failures
116 changes: 116 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# TextMate Copilot Instructions

This is the TextMate text editor codebase - a sophisticated macOS text editor with extensible architecture.

## Project Structure

- **Applications/**: Contains the main TextMate app and helper utilities
- **Frameworks/**: Core C++ and Objective-C++ frameworks providing functionality
- **PlugIns/**: Plugin modules for extensibility
- **Shared/**: Common headers and precompiled headers
- **vendor/**: Third-party dependencies

## Key Technologies

- **Languages**: C++20, Objective-C++, Objective-C
- **Build System**: Ninja (via custom rave build system)
- **Platform**: macOS (minimum 14.0)
- **Architecture**: Framework-based modular design

## Coding Conventions

### C++ Style
- Use tabs for indentation (tabSize = 3)
- Prefer `snake_case` for functions and variables
- Use `PascalCase` for classes and types
- Namespace everything under appropriate namespaces (`ng`, `bundles`, etc.)
- Header guards use format: `FILENAME_H_RANDOMID`

### Memory Management
- Use ARC (Automatic Reference Counting) for Objective-C++
- Follow RAII patterns for C++
- Prefer smart pointers when appropriate

### File Extensions
- `.h` files are treated as Objective-C++ by default
- `.cc` for C++ implementation
- `.mm` for Objective-C++ implementation
- `.m` for Objective-C implementation

## Important Patterns

### Framework Structure
Each framework typically has:
- `src/` directory with headers and implementation
- Public headers exposed through include paths
- Private implementation details hidden

### Bundle System
- TextMate uses a bundle system for extensibility
- Bundles contain commands, snippets, themes, etc.
- Bundle items are queried using scope selectors

### Editor Architecture
- Core editing functionality in `ng` namespace
- Modular design with clipboard, snippets, selection, etc.
- Event-driven architecture with callbacks

### Build System
- Uses custom `rave` build configuration files (`.rave`)
- Ninja backend for actual compilation
- Debug and release configurations available
- Per-framework and per-application build targets

## Key Components

### Core Frameworks
- **editor**: Text editing engine and operations
- **document**: Document model and management
- **bundles**: Bundle loading and querying system
- **selection**: Text selection and ranges
- **regexp**: Regular expression support
- **scm**: Source control integration
- **settings**: Configuration and preferences

### UI Components
- **OakTextView**: Main text editing view
- **DocumentWindow**: Document window management
- **OakAppKit**: Custom AppKit extensions
- **MenuBuilder**: Dynamic menu construction

## Development Guidelines

### Adding New Features
1. Consider which framework should contain the functionality
2. Follow existing patterns for similar features
3. Update appropriate `.rave` build files
4. Add tests in `tests/` subdirectories when applicable

### Working with Bundles
- Bundle items have UUIDs, names, and scope selectors
- Use `bundles::query()` to find relevant items
- Commands can transform text, show output, etc.

### Text Processing
- Use `ng::ranges_t` for text selections
- Text transformations follow functional patterns
- Support for snippets with placeholder expansion

### Platform Integration
- Heavy use of Foundation and AppKit frameworks
- Custom Objective-C++ wrappers for C++ functionality
- Native macOS UI patterns and behaviors

## Testing
- Uses CxxTest framework for unit testing
- Test files follow `t_*.{cc,mm}` pattern
- GUI tests use `gui_*.mm` pattern
- Run tests with `ninja <framework>/test`

## Build Commands
- `./configure && ninja TextMate/run` - Build and launch TextMate
- `ninja TextMate` - Build TextMate without launching
- `ninja <framework>/test` - Run tests for specific framework
- `ninja -t clean` - Clean build artifacts

When contributing to TextMate, maintain the existing architectural patterns, follow the established coding style, and ensure changes integrate well with the bundle and framework system.
143 changes: 131 additions & 12 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,137 @@
name: CI
name: Build TextMate Package

on: [push]
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
workflow_dispatch:
inputs:
build_type:
description: Build type (release/debug)
required: false
default: release
type: choice
options:
- release
- debug

permissions:
contents: read

jobs:
build:
runs-on: macOS-latest
runs-on: macos-latest

steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Setup
run: brew install boost capnp google-sparsehash multimarkdown ninja ragel
- name: Configure
run: ./configure
- name: Build
run: ninja TextMate
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: false

- name: Cache Homebrew packages
uses: actions/cache@v4
with:
path: |
/usr/local/Cellar
/usr/local/opt
/usr/local/bin
/usr/local/lib
/usr/local/include
~/Library/Caches/Homebrew
key: ${{ runner.os }}-homebrew-${{ hashFiles('**/configure', '**/README.md') }}-v1
restore-keys: |
${{ runner.os }}-homebrew-

- name: Install Homebrew dependencies
run: |
# Install each package individually to avoid warnings about already installed packages
packages="boost capnp google-sparsehash multimarkdown ninja ragel gdbm"
missing_packages=""

for package in $packages; do
if ! brew list "$package" > /dev/null 2>&1; then
missing_packages="$missing_packages $package"
else
echo "$package is already installed"
fi
done

if [ -n "$missing_packages" ]; then
echo "Installing missing packages:$missing_packages"
brew update
brew install $missing_packages
else
echo "All Homebrew dependencies already installed"
fi

- name: Install Ruby gems
run: |
bundle install
bundle exec ruby -e "require 'dbm'; puts 'DBM gem loaded successfully'"

- name: Cache intermediate build artifacts
uses: actions/cache@v4
with:
path: |
~/build
~/.cache
key: ${{ runner.os }}-build-${{ hashFiles('**/*.rave', '**/configure', 'Frameworks/**/*.cc', 'Frameworks/**/*.mm', 'Frameworks/**/*.h', 'Applications/**/*.cc', 'Applications/**/*.mm', 'Applications/**/*.h') }}-${{ inputs.build_type || 'release' }}-v1
restore-keys: |
${{ runner.os }}-build-${{ inputs.build_type || 'release' }}-
${{ runner.os }}-build-

- name: Configure
run: |
echo "Starting configuration..."
./configure
echo "Configuration completed."

- name: Build TextMate Package
run: |
echo "Starting build process..."
echo "Build type: ${{ inputs.build_type }}"
if ! ninja TextMate; then
echo "Build failed. Searching for partial artifacts..."
find ~/build -name "*TextMate*" -type f 2>/dev/null || echo "No TextMate artifacts found"
exit 1
fi
echo "Build completed successfully."

- name: List build output
run: |
echo "Contents of build directory:"
find ~/build -type f -name "*TextMate*" 2>/dev/null || echo "No TextMate files found"
find ~/build -type d -name "*.app" 2>/dev/null || echo "No .app directories found"
echo "Full build directory structure:"
ls -la ~/build/ || echo "~/build not found"

- name: Collect artifacts
run: |
mkdir -p textmate-package
if [ -d ~/build/TextMate/Applications/TextMate.app ]; then
cp -R ~/build/TextMate/Applications/TextMate.app textmate-package/
echo "Collected TextMate.app from ~/build/TextMate/Applications/"
else
# Fallback: search for TextMate.app anywhere in build directory
find ~/build -name "TextMate.app" -type d -exec cp -R {} textmate-package/ \; 2>/dev/null && echo "Collected TextMate.app from fallback search" || echo "TextMate.app not found"
fi
echo "Package contents:"
ls -la textmate-package/ || echo "Package directory empty"

- name: Upload TextMate Package
uses: actions/upload-artifact@v4
with:
name: TextMate-app-${{ github.sha }}
path: textmate-package/
if-no-files-found: warn
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
build.ninja
local.rave
Frameworks/license/src/revoked_serials.cc
*.bak
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby-4.0.3
2 changes: 1 addition & 1 deletion Applications/PrivilegedTool/default.rave
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
target "${dirname}" {
require authorization io text
sources src/*.cc
sources src/*.cc src/*.mm
executable "${target}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <text/format.h>
#include <io/io.h>
#include <authorization/constants.h>
#include <oak/compat.h>

static char const* const kPlistFormatString =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
Expand Down Expand Up @@ -43,7 +44,7 @@ static std::string plist_content ()

static void launch_control (char const* command, std::string const& argument)
{
pid_t pid = vfork();
pid_t pid = oak::vfork();
if(pid == 0)
{
execl("/bin/launchctl", "/bin/launchctl", command, argument.c_str(), nullptr);
Expand Down
8 changes: 3 additions & 5 deletions Applications/QuickLookGenerator/src/generate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,9 @@ OSStatus TextMateQuickLookPlugIn_GeneratePreviewForURL (void* instance, QLPrevie
NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.macromates.TextMate"];
NSString* appearance = [userDefaults stringForKey:@"themeAppearance"];
BOOL darkMode = [appearance isEqualToString:@"dark"];
if(@available(macos 10.14, *))
{
if(!darkMode && ![appearance isEqualToString:@"light"]) // If it is not ‘light’ then assume ‘auto’
darkMode = [[NSAppearance.currentAppearance bestMatchFromAppearancesWithNames:@[ NSAppearanceNameAqua, NSAppearanceNameDarkAqua ]] isEqualToString:NSAppearanceNameDarkAqua];
}
// Appearance detection is always available on macOS 14.0+
if(!darkMode && ![appearance isEqualToString:@"light"]) // If it is not 'light' then assume 'auto'
darkMode = [[NSAppearance.currentAppearance bestMatchFromAppearancesWithNames:@[ NSAppearanceNameAqua, NSAppearanceNameDarkAqua ]] isEqualToString:NSAppearanceNameDarkAqua];
NSString* themeUUID = [userDefaults stringForKey:darkMode ? @"darkModeThemeUUID" : @"universalThemeUUID"];

settings_t const settings = settings_for_path(URLtoString(url), fileType);
Expand Down
Loading