Files
textmate/Frameworks/DocumentWindow/src/DocumentCommand.mm

232 lines
8.8 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#import "DocumentCommand.h"
#import "DocumentController.h"
#import "DocumentSaveHelper.h"
#import <OakAppKit/OakToolTip.h>
#import <OakAppKit/OakAppKit.h>
#import <OakFoundation/NSString Additions.h>
#import <BundleEditor/BundleEditor.h>
#import <HTMLOutputWindow/HTMLOutputWindow.h>
#import <OakTextView/OakDocumentView.h>
#import <OakFileBrowser/OakFileBrowser.h>
#import <OakSystem/application.h>
#import <OakSystem/process.h>
#import <command/runner.h>
#import <ns/ns.h>
#import <oak/oak.h>
#import <bundles/bundles.h>
#import <document/collection.h>
#import <editor/editor.h>
#import <editor/write.h>
#import <io/path.h>
#import <text/trim.h>
#import <text/tokenize.h>
namespace
{
struct delegate_t : command::delegate_t
{
delegate_t (DocumentController* controller, document::document_ptr document) : _controller(controller), _document(document), _did_open_html_window(false)
{
if(_controller)
_collection = to_s(_controller.identifier);
}
ng::ranges_t write_unit_to_fd (int fd, input::type unit, input::type fallbackUnit, input_format::type format, scope::selector_t const& scopeSelector, std::map<std::string, std::string>& variables, bool* inputWasSelection);
bool accept_html_data (command::runner_ptr runner, char const* data, size_t len);
bool accept_result (std::string const& out, output::type placement, output_format::type format, output_caret::type outputCaret, ng::ranges_t const& inputRanges, std::map<std::string, std::string> const& environment);
void discard_html ();
void show_tool_tip (std::string const& str);
void show_document (std::string const& str);
void show_error (bundle_command_t const& command, int rc, std::string const& out, std::string const& err);
private:
DocumentController* _controller;
document::document_ptr _document;
oak::uuid_t _collection;
bool _did_open_html_window;
};
}
// =======================
// = Init, Saving, Input =
// =======================
ng::ranges_t delegate_t::write_unit_to_fd (int fd, input::type unit, input::type fallbackUnit, input_format::type format, scope::selector_t const& scopeSelector, std::map<std::string, std::string>& variables, bool* inputWasSelection)
{
if(!_document)
{
close(fd);
return { };
}
bool isOpen = _document->is_open();
if(!isOpen)
_document->sync_open();
ng::ranges_t const res = ng::write_unit_to_fd(_document->buffer(), ng::editor_for_document(_document)->ranges(), _document->buffer().indent().tab_size(), fd, unit, fallbackUnit, format, scopeSelector, variables, inputWasSelection);
if(!isOpen)
_document->close();
return res;
}
// ====================
// = Accepting Output =
// ====================
bool delegate_t::accept_html_data (command::runner_ptr runner, char const* data, size_t len)
{
if(!_did_open_html_window)
{
_did_open_html_window = true;
if(_controller)
[_controller setCommandRunner:runner];
else [HTMLOutputWindowController HTMLOutputWindowWithRunner:runner];
}
return true;
}
void delegate_t::discard_html ()
{
if(_did_open_html_window && _controller.htmlOutputVisible)
_controller.htmlOutputVisible = NO;
}
bool delegate_t::accept_result (std::string const& out, output::type placement, output_format::type format, output_caret::type outputCaret, ng::ranges_t const& inputRanges, std::map<std::string, std::string> const& environment)
{
bool res;
if(_document && _document->is_open())
{
res = ng::editor_for_document(_document)->handle_result(out, placement, format, outputCaret, inputRanges, environment);
}
else
{
document::document_ptr doc = document::create();
doc->sync_open();
res = ng::editor_for_document(doc)->handle_result(out, placement, format, outputCaret, ng::range_t(0) /* inputRanges */, environment);
document::show(doc);
doc->close();
}
return res;
}
// ========================================
// = Showing tool tip, document, or error =
// ========================================
void delegate_t::show_tool_tip (std::string const& str)
{
NSPoint location = _controller ? [_controller positionForWindowUnderCaret] : [NSEvent mouseLocation];
OakShowToolTip([NSString stringWithCxxString:str], location);
}
void delegate_t::show_document (std::string const& str)
{
document::show(document::from_content(str), _collection);
}
void delegate_t::show_error (bundle_command_t const& command, int rc, std::string const& out, std::string const& err)
{
show_command_error(text::trim(err + out).empty() ? text::format("Command returned status code %d.", rc) : err + out, command.uuid, _controller.window, command.name);
}
// ==============
// = Public API =
// ==============
void run_impl (bundle_command_t const& command, ng::buffer_api_t const& buffer, ng::ranges_t const& selection, document::document_ptr document, std::map<std::string, std::string> baseEnv, std::string const& pwd)
{
DocumentController* controller = [DocumentController controllerForDocument:document];
if(controller && command.output == output::new_window && command.output_format == output_format::html)
{
if(command.output_reuse == output_reuse::reuse_busy || command.output_reuse == output_reuse::abort_and_reuse_busy)
{
bundle_command_t cmd = command;
cmd.output_reuse = output_reuse::reuse_available; // Avoid infinite loop when completionHandler calls us
ng::ranges_t sel = selection;
std::string dir = pwd;
[controller bundleItemReuseOutputForCommand:command completionHandler:^(BOOL success){
if(success)
run_impl(cmd, buffer, sel, document, baseEnv, dir);
}];
return;
}
}
if(controller && command.pre_exec != pre_exec::nop)
{
bundle_command_t cmd = command;
cmd.pre_exec = pre_exec::nop;
ng::ranges_t sel = selection;
std::string dir = pwd;
[controller bundleItemPreExec:command.pre_exec completionHandler:^(BOOL success){
run_impl(cmd, buffer, sel, document, baseEnv, dir);
}];
return;
}
if(bundles::item_ptr item = bundles::lookup(command.uuid))
{
bundles::required_command_t failedRequirement;
if(missing_requirement(item, baseEnv, &failedRequirement))
{
std::vector<std::string> paths;
std::string const tmp = baseEnv["PATH"];
for(auto path : text::tokenize(tmp.begin(), tmp.end(), ':'))
{
if(path != "" && path::is_directory(path))
paths.push_back(path::with_tilde(path));
}
std::string const title = text::format("Unable to run “%.*s”.", (int)command.name.size(), command.name.data());
std::string message;
if(failedRequirement.variable != NULL_STR)
message = text::format("This command requires %1$s which wasnt found on your system.\n\nThe following locations were searched:%2$s\n\nIf %1$s is installed elsewhere then you need to set %3$s in Preferences → Variables to the full path of where you installed it.", failedRequirement.command.c_str(), ("\n\u2003• " + text::join(paths, "\n\u2003• ")).c_str(), failedRequirement.variable.c_str());
else message = text::format("This command requires %1$s which wasnt found on your system.\n\nThe following locations were searched:%2$s\n\nIf %1$s is installed elsewhere then you need to set PATH in Preferences → Variables to include the folder in which it can be found.", failedRequirement.command.c_str(), ("\n\u2003• " + text::join(paths, "\n\u2003• ")).c_str());
NSAlert* alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert setMessageText:[NSString stringWithCxxString:title]];
[alert setInformativeText:[NSString stringWithCxxString:message]];
[alert addButtonWithTitle:@"OK"];
if(failedRequirement.more_info_url != NULL_STR)
[alert addButtonWithTitle:@"More Info…"];
NSString* moreInfo = [NSString stringWithCxxString:failedRequirement.more_info_url];
OakShowAlertForWindow(alert, [controller window], ^(NSInteger button){
if(button == NSAlertSecondButtonReturn)
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:moreInfo]];
});
return;
}
}
command::runner_ptr runner = command::runner(command, buffer, selection, baseEnv, std::make_shared<delegate_t>(controller, document), pwd);
runner->launch();
runner->wait();
}
void show_command_error (std::string const& message, oak::uuid_t const& uuid, NSWindow* window, std::string commandName)
{
bundles::item_ptr bundleItem = bundles::lookup(uuid);
if(commandName == NULL_STR)
commandName = bundleItem ? bundleItem->name() : "(unknown)";
NSAlert* alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert setMessageText:[NSString stringWithCxxString:text::format("Failure running “%.*s”.", (int)commandName.size(), commandName.data())]];
[alert setInformativeText:[NSString stringWithCxxString:message] ?: @"No output"];
[alert addButtonWithTitle:@"OK"];
if(bundleItem)
[alert addButtonWithTitle:@"Edit Command"];
OakShowAlertForWindow(alert, window, ^(NSInteger button){
if(button == NSAlertSecondButtonReturn)
[[BundleEditor sharedInstance] revealBundleItem:bundleItem];
});
}