From e40e42181fcc0c9742d6fcb654b0e1dee07a2513 Mon Sep 17 00:00:00 2001 From: Jim Ingham Date: Mon, 30 Aug 2010 19:44:40 +0000 Subject: [PATCH] Added a way to open the current source file & line in an external editor, and you can turn this on with: lldb -e llvm-svn: 112502 --- lldb/include/lldb/API/SBDebugger.h | 8 ++ lldb/include/lldb/Core/Debugger.h | 15 +++ lldb/include/lldb/Host/Host.h | 3 + lldb/lldb.xcodeproj/project.pbxproj | 4 + lldb/source/API/SBDebugger.cpp | 20 ++++ lldb/source/Commands/CommandObjectFrame.cpp | 9 +- lldb/source/Commands/CommandObjectThread.cpp | 10 +- lldb/source/Core/Debugger.cpp | 3 +- lldb/source/Host/macosx/Host.mm | 115 +++++++++++++++++++ lldb/source/Host/macosx/cfcpp/CFCReleaser.h | 7 +- lldb/tools/driver/Driver.cpp | 14 ++- lldb/tools/driver/Driver.h | 1 + 12 files changed, 202 insertions(+), 7 deletions(-) diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h index 5b2a2431042c..2ad6f0236df0 100644 --- a/lldb/include/lldb/API/SBDebugger.h +++ b/lldb/include/lldb/API/SBDebugger.h @@ -106,6 +106,14 @@ public: lldb::SBSourceManager & GetSourceManager (); + + // FIXME: Once we get the set show stuff in place, the driver won't need + // an interface to the Set/Get UseExternalEditor. + bool + SetUseExternalEditor (bool input); + + bool + UseExternalEditor (); bool GetDefaultArchitecture (char *arch_name, size_t arch_name_len); diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index a7d745be9672..743e1798784b 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -143,6 +143,20 @@ public: static lldb::DebuggerSP FindDebuggerWithID (lldb::user_id_t id); + + bool + SetUseExternalEditor (bool value) + { + bool old_value = m_use_external_editor; + m_use_external_editor = value; + return old_value; + } + + bool + UseExternalEditor () + { + return m_use_external_editor; + } protected: @@ -170,6 +184,7 @@ protected: std::stack m_input_readers; std::string m_input_reader_data; + bool m_use_external_editor; // FIXME: Convert this to a set/show variable on the debugger. private: diff --git a/lldb/include/lldb/Host/Host.h b/lldb/include/lldb/Host/Host.h index f73f19f4ba74..1445e499b7e8 100644 --- a/lldb/include/lldb/Host/Host.h +++ b/lldb/include/lldb/Host/Host.h @@ -267,6 +267,9 @@ public: static ArchSpec GetArchSpecForExistingProcess (const char *process_name); + + static bool + OpenFileInExternalEditor (FileSpec &file_spec, uint32_t line_no); }; diff --git a/lldb/lldb.xcodeproj/project.pbxproj b/lldb/lldb.xcodeproj/project.pbxproj index 453b38fc1162..c3149dd4fad6 100644 --- a/lldb/lldb.xcodeproj/project.pbxproj +++ b/lldb/lldb.xcodeproj/project.pbxproj @@ -356,6 +356,7 @@ 4C08CDEC11C81F1E001610A8 /* ThreadSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C08CDEB11C81F1E001610A8 /* ThreadSpec.h */; }; 4C5DBBC811E3FEC60035160F /* CommandObjectCommands.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C5DBBC611E3FEC60035160F /* CommandObjectCommands.cpp */; }; 4C5DBBC911E3FEC60035160F /* CommandObjectCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C5DBBC711E3FEC60035160F /* CommandObjectCommands.h */; }; + 4C74CB6312288704006A8171 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C74CB6212288704006A8171 /* Carbon.framework */; }; 4CA9637B11B6E99A00780E28 /* CommandObjectApropos.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9637911B6E99A00780E28 /* CommandObjectApropos.cpp */; }; 9A19A6AF1163BBB200E0D453 /* SBValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A19A6A51163BB7E00E0D453 /* SBValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9A19A6B01163BBB300E0D453 /* SBValue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9A19A6AD1163BB9800E0D453 /* SBValue.cpp */; }; @@ -960,6 +961,7 @@ 4C51FF1611A4C486007C962F /* ObjCTrampolineHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjCTrampolineHandler.cpp; sourceTree = ""; }; 4C5DBBC611E3FEC60035160F /* CommandObjectCommands.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommandObjectCommands.cpp; path = source/Commands/CommandObjectCommands.cpp; sourceTree = ""; }; 4C5DBBC711E3FEC60035160F /* CommandObjectCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CommandObjectCommands.h; path = source/Commands/CommandObjectCommands.h; sourceTree = ""; }; + 4C74CB6212288704006A8171 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; 4C98D3DA118FB96F00E575D0 /* ClangFunction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ClangFunction.cpp; path = source/Expression/ClangFunction.cpp; sourceTree = ""; }; 4C98D3DB118FB96F00E575D0 /* RecordingMemoryManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RecordingMemoryManager.cpp; path = source/Expression/RecordingMemoryManager.cpp; sourceTree = ""; }; 4C98D3E0118FB98F00E575D0 /* ClangFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ClangFunction.h; path = include/lldb/Expression/ClangFunction.h; sourceTree = ""; }; @@ -1075,6 +1077,7 @@ 26680231115FD1A0008E1FE4 /* Foundation.framework in Frameworks */, 26680232115FD1A4008E1FE4 /* libpython2.6.dylib in Frameworks */, 26680233115FD1A7008E1FE4 /* libobjc.dylib in Frameworks */, + 4C74CB6312288704006A8171 /* Carbon.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1105,6 +1108,7 @@ 08FB7795FE84155DC02AAC07 /* Source */, 26F5C22410F3D950009D5894 /* Tools */, 1AB674ADFE9D54B511CA2CBB /* Products */, + 4C74CB6212288704006A8171 /* Carbon.framework */, ); name = lldb; sourceTree = ""; diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp index 73f125bcb8a8..fe2b99a2dc28 100644 --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -563,3 +563,23 @@ SBDebugger::FindDebuggerWithID (int id) sb_debugger.reset (debugger_sp); return sb_debugger; } + +bool +SBDebugger::SetUseExternalEditor (bool value) +{ + if (m_opaque_sp) + return m_opaque_sp->SetUseExternalEditor (value); + else + return false; +} + +bool +SBDebugger::UseExternalEditor () +{ + if (m_opaque_sp) + return m_opaque_sp->UseExternalEditor (); + else + return false; +} + + diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp index 316b0f78fd81..cd9ee08c2b0c 100644 --- a/lldb/source/Commands/CommandObjectFrame.cpp +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -114,12 +114,19 @@ public: if (exe_ctx.frame) { + bool already_shown = false; + SymbolContext frame_sc(exe_ctx.frame->GetSymbolContext(eSymbolContextLineEntry)); + if (interpreter.GetDebugger().UseExternalEditor() && frame_sc.line_entry.file && frame_sc.line_entry.line != 0) + { + already_shown = Host::OpenFileInExternalEditor (frame_sc.line_entry.file, frame_sc.line_entry.line); + } + if (DisplayFrameForExecutionContext (exe_ctx.thread, exe_ctx.frame, interpreter, result.GetOutputStream(), true, - true, + !already_shown, 3, 3)) { diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp index 1ed9794663fa..8d1737b6af7a 100644 --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -60,13 +60,21 @@ lldb_private::DisplayThreadInfo // Show one frame with only the first showing source if (show_source) { + bool already_shown = false; + StackFrameSP frame_sp = thread->GetStackFrameAtIndex(0); + SymbolContext frame_sc(frame_sp->GetSymbolContext (eSymbolContextLineEntry)); + if (interpreter.GetDebugger().UseExternalEditor() && frame_sc.line_entry.file && frame_sc.line_entry.line != 0) + { + already_shown = Host::OpenFileInExternalEditor (frame_sc.line_entry.file, frame_sc.line_entry.line); + } + DisplayFramesForExecutionContext (thread, interpreter, strm, 0, // Start at first frame 1, // Number of frames to show false,// Don't show the frame info since we already displayed most of it above... - 1, // Show source for the first frame + !already_shown, // Show source for the first frame 3, // lines of source context before 3); // lines of source context after } diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 62ffa404c832..ee33ec53f499 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -128,7 +128,8 @@ Debugger::Debugger () : m_command_interpreter_ap (new CommandInterpreter (*this, eScriptLanguageDefault, false)), m_exe_ctx (), m_input_readers (), - m_input_reader_data () + m_input_reader_data (), + m_use_external_editor(false) { m_command_interpreter_ap->Initialize (); } diff --git a/lldb/source/Host/macosx/Host.mm b/lldb/source/Host/macosx/Host.mm index 4bd78df1b513..2191d8b6137d 100644 --- a/lldb/source/Host/macosx/Host.mm +++ b/lldb/source/Host/macosx/Host.mm @@ -24,6 +24,8 @@ #include #include +#include +#include #include "cfcpp/CFCBundle.h" #include "cfcpp/CFCReleaser.h" @@ -779,3 +781,116 @@ Host::GetArchSpecForExistingProcess (const char *process_name) } return returnSpec; } + +bool +Host::OpenFileInExternalEditor (FileSpec &file_spec, uint32_t line_no) +{ + // We attach this to an 'odoc' event to specify a particular selection + typedef struct { + int16_t reserved0; // must be zero + int16_t fLineNumber; + int32_t fSelStart; + int32_t fSelEnd; + uint32_t reserved1; // must be zero + uint32_t reserved2; // must be zero + } BabelAESelInfo; + + char file_path[PATH_MAX]; + file_spec.GetPath(file_path, PATH_MAX); + CFCString file_cfstr (file_path, kCFStringEncodingUTF8); + CFCReleaser file_URL(::CFURLCreateWithFileSystemPath(NULL, file_cfstr.get(), kCFURLPOSIXPathStyle, false)); + + OSStatus error; + BabelAESelInfo file_and_line_info; + + AEKeyDesc file_and_line_desc; + + bzero(&file_and_line_info, sizeof (file_and_line_info)); + file_and_line_info.fSelStart = 1; + file_and_line_info.fSelEnd = 1; + file_and_line_info.fLineNumber = line_no - 1; + + error = AECreateDesc(typeChar, &file_and_line_info, sizeof (file_and_line_info), &(file_and_line_desc.descContent)); + if (error != noErr) + { + return false; + } + + file_and_line_desc.descKey = keyAEPosition; + + LSApplicationParameters app_params; + bzero (&app_params, sizeof (app_params)); + app_params.flags = kLSLaunchDefaults | kLSLaunchDontSwitch; + + ProcessSerialNumber psn; + CFCReleaser file_array(CFArrayCreate (NULL, (const void **) file_URL.ptr_address(false), 1, NULL)); + error = LSOpenURLsWithRole(file_array.get(), kLSRolesAll, &file_and_line_desc, &app_params, &psn, 1); + + AEDisposeDesc (&(file_and_line_desc.descContent)); + + if (error != noErr) + { + return false; + } + + ProcessInfoRec which_process; + bzero(&which_process, sizeof(which_process)); + unsigned char ap_name[PATH_MAX]; + which_process.processName = ap_name; + error = GetProcessInformation (&psn, &which_process); + + bool using_xcode = strncmp((char *) ap_name+1, "Xcode", 5) == 0; + + // Xcode doesn't obey the line number in the Open Apple Event. So I have to send + // it an AppleScript to focus on the right line. + + if (using_xcode) + { + static ComponentInstance osa_component = NULL; + static const char *as_template = "tell application \"Xcode\"\n" + "set doc to the first document whose path is \"%s\"\n" + "set the selection to paragraph %d of doc\n" + "--- set the selected paragraph range to {%d, %d} of doc\n" + "end tell\n"; + const int chars_for_int = 32; + static int as_template_len = strlen (as_template); + + + char *as_str; + AEDesc as_desc; + + if (osa_component == NULL) + { + osa_component = OpenDefaultComponent (kOSAComponentType, + kAppleScriptSubtype); + } + + if (osa_component == NULL) + { + return false; + } + + uint32_t as_str_size = as_template_len + strlen (file_path) + 3 * chars_for_int + 1; + as_str = (char *) malloc (as_str_size); + snprintf (as_str, as_str_size - 1, as_template, file_path, line_no, line_no, line_no); + error = AECreateDesc (typeChar, as_str, strlen (as_str), &as_desc); + + free (as_str); + + if (error != noErr) + return false; + + OSAID ret_OSAID; + error = OSACompileExecute (osa_component, &as_desc, kOSANullScript, + kOSAModeNeverInteract, &ret_OSAID); + + OSADispose (osa_component, ret_OSAID); + + AEDisposeDesc (&as_desc); + + if (error != noErr) + return false; + } + + return true; +} diff --git a/lldb/source/Host/macosx/cfcpp/CFCReleaser.h b/lldb/source/Host/macosx/cfcpp/CFCReleaser.h index cd35de6d665d..2aa13506fc61 100644 --- a/lldb/source/Host/macosx/cfcpp/CFCReleaser.h +++ b/lldb/source/Host/macosx/cfcpp/CFCReleaser.h @@ -96,11 +96,14 @@ public: // assertion fires, check the offending code, or call // reset() prior to using the "ptr_address()" member to make // sure any owned objects has CFRelease called on it. + // I had to add the "enforce_null" bool here because some + // API's require the pointer address even though they don't change it. //---------------------------------------------------------- T* - ptr_address() + ptr_address(bool enforce_null = true) { - assert (_ptr == NULL); + if (enforce_null) + assert (_ptr == NULL); return &_ptr; } diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index fd996bfc44bd..260257fdb0bb 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -70,6 +70,9 @@ static lldb::OptionDefinition g_options[] = { LLDB_OPT_SET_3, true, "file", 'f', required_argument, NULL, NULL, "", "Tells the debugger to use the file as the program to be debugged." }, + { LLDB_OPT_SET_ALL, false, "editor", 'e', no_argument, NULL, NULL, "", + "Tells the debugger to open source files using the host's \"external editor\" mechanism." }, + // { LLDB_OPT_SET_4, true, "crash-log", 'c', required_argument, NULL, NULL, "", // "Load executable images from a crash log for symbolication." }, @@ -324,7 +327,8 @@ Driver::OptionData::OptionData () : m_debug_mode (false), m_print_version (false), m_print_help (false), - m_seen_options() + m_seen_options(), + m_use_external_editor(false) { } @@ -341,6 +345,7 @@ Driver::OptionData::Clear () m_debug_mode = false; m_print_help = false; m_print_version = false; + m_use_external_editor = false; } void @@ -508,7 +513,10 @@ Driver::ParseArgs (int argc, const char *argv[], FILE *out_fh, bool &exit) case 'c': m_option_data.m_crash_log = optarg; break; - + case 'e': + m_option_data.m_use_external_editor = true; + break; + case 'f': { SBFileSpec file(optarg); @@ -1042,6 +1050,8 @@ Driver::MainLoop () m_debugger.SetErrorFileHandle (stderr, false); m_debugger.SetOutputFileHandle (stdout, false); m_debugger.SetInputFileHandle (stdin, true); + + m_debugger.SetUseExternalEditor(m_option_data.m_use_external_editor); // You have to drain anything that comes to the master side of the PTY. master_out_comm is // for that purpose. The reason you need to do this is a curious reason... editline will echo diff --git a/lldb/tools/driver/Driver.h b/lldb/tools/driver/Driver.h index 199165bee09d..d1e77549dab4 100644 --- a/lldb/tools/driver/Driver.h +++ b/lldb/tools/driver/Driver.h @@ -103,6 +103,7 @@ public: bool m_debug_mode; bool m_print_version; bool m_print_help; + bool m_use_external_editor; // FIXME: When we have set/show variables we can remove this from here. typedef std::set OptionSet; OptionSet m_seen_options; };