File: programming/cocoa/UKSyntaxColoredTextDocument.zip/UKSyntaxColoredTextDocument/UKMultiSyntaxColoredTextDocument.m


//
//  UKMultiSyntaxColoredTextDocument.m
//  UKSyntaxColoredDocument
//
//  Created by Uli Kusterer on Mon May 17 2004.
//  Copyright (c) 2004 M. Uli Kusterer. All rights reserved.
//
 
// -----------------------------------------------------------------------------
//  Headers:
// -----------------------------------------------------------------------------
 
#import "UKMultiSyntaxColoredTextDocument.h"
#import "NSMenu+DeepSearch.h"
 
 
// -----------------------------------------------------------------------------
//  Globals:
// -----------------------------------------------------------------------------
 
static NSMutableArray*		sUKMSCTDSyntaxDefinitionFiles = nil;	// Array of syntax definition file paths in NSStrings.
static NSMutableDictionary*	sUKMSCTDSuffixToTagMappings = nil;
 
NSString*	UKMultiSyntaxColoredTextDocumentSyntaxDefinitionChanged = @"UKMultiSyntaxColoredTextDocumentSyntaxDefinitionChanged";
 
 
@implementation UKMultiSyntaxColoredTextDocument
 
// -----------------------------------------------------------------------------
//	syntaxDefinitionFiles:
//		Return and, if needed, lazily allocate the array of available syntax
//		definition files. This also updates the syntax definition menu by
//		writing the file names without suffixes of the syntax definition files
//		into the menu as menu items, whose tags are the indices into the array
//		corresponding to them.
//
//	REVISIONS:
//		2004-05-18	witness	Documented.
//		2004-05-17	witness	Created.
// -----------------------------------------------------------------------------
 
-(NSArray*) syntaxDefinitionFiles
{
	if( !sUKMSCTDSyntaxDefinitionFiles )
	{
		sUKMSCTDSyntaxDefinitionFiles = [[NSMutableArray alloc] init];
		sUKMSCTDSuffixToTagMappings = [[NSMutableDictionary alloc] init];
		
		// Load from User's Application support:
		NSString*				fpath = [NSString stringWithFormat: @"%@/Library/Application Support/%@/Syntax Definitions/",
											NSHomeDirectory(), UKSCTD_APPLICATION_NAME];
		[self addSyntaxFilesFromFolderToArray: fpath];
 
		// Load from global application support:
		fpath = [NSString stringWithFormat: @"/Library/Application Support/%@/Syntax Definitions/",
						UKSCTD_APPLICATION_NAME];
		[self addSyntaxFilesFromFolderToArray: fpath];
	
		// Load from folder in app bundle:
		fpath = [[NSBundle mainBundle] pathForResource: @"Syntax Definitions" ofType: @""];
		[self addSyntaxFilesFromFolderToArray: fpath];
		
		// Now make the menu match:
		[self rebuildSyntaxMenu];
	}
	
	return sUKMSCTDSyntaxDefinitionFiles;
}
 
 
// -----------------------------------------------------------------------------
//	rebuildSyntaxMenu:
//		Remove the old items from the syntax definition menu and add new menu
//		items for them based on the syntax definition files array.
//
//	REVISIONS:
//		2004-05-18	witness	Extracted from syntaxDefinitionFiles.
// -----------------------------------------------------------------------------
 
-(void) rebuildSyntaxMenu
{
	id <NSMenuItem>	foundItem = nil;
	NSMenu*			syntaxMenu = nil;
	
	// Find menu with menu items for syntax definitions:
	if( !syntaxDefinitionMenu )
	{
		foundItem = [[NSApp mainMenu] findItemWithTarget:nil andAction: @selector(takeSyntaxDefinitionFilenameFromTagOf:)];
		syntaxMenu = [foundItem menu];
	}
	else	// If we have a menu in our outlet, use that instead:
	{
		syntaxMenu = syntaxDefinitionMenu;
		foundItem = [syntaxDefinitionMenu itemAtIndex: 0];  // Assume we own the popup.
	}
	
	// Remove all old menu items from our menu:
	id <NSMenuItem>	currMItem = foundItem;
	int				currItemNum = [syntaxMenu indexOfItem: currMItem];
	
	while( [currMItem action] == @selector(takeSyntaxDefinitionFilenameFromTagOf:) )
	{
		[syntaxMenu removeItem: currMItem];
		if( currItemNum < [syntaxMenu numberOfItems] )
			currMItem = [syntaxMenu itemAtIndex: currItemNum];
		else
			break;
	}
		/* Warning! This croaks if the code below doesn't find any syntax
			definition files, because then all menu items have been removed
			and it won't find the menu the next time! */
	
	NSEnumerator*   enny = [sUKMSCTDSyntaxDefinitionFiles objectEnumerator];
	NSString*		currPath = nil;
	int				x = 0;
	
	while( (currPath = [enny nextObject]) )
	{
		NSString*   dName = [[currPath lastPathComponent] stringByDeletingPathExtension];
		NSMenuItem* currItem = [syntaxMenu addItemWithTitle: dName action: @selector(takeSyntaxDefinitionFilenameFromTagOf:)
									keyEquivalent:@""];
		[currItem setTag: x++]; // Remember array index of this one.
		
		NSDictionary*	dict = [NSDictionary dictionaryWithContentsOfFile: currPath];
		NSEnumerator*	suffixEnny = [[dict objectForKey: @"FileNameSuffixes"] objectEnumerator];
		NSString*		suffix = nil;
		while(( suffix = [suffixEnny nextObject] ))
			[sUKMSCTDSuffixToTagMappings setObject: [NSNumber numberWithInt: x -1] forKey: suffix];
	}
}
 
 
// -----------------------------------------------------------------------------
//	addSyntaxFilesFromFolder:
//		Scan the specified folder for files and add all files (except the ones
//		starting with a period) to our array of syntax definition file paths,
//		as well as adding menu items for them.
//
//		This appends to the array, so can be called several times in a row to
//		scan several folders.
//
//	REVISIONS:
//		2004-05-18	witness	Documented.
//		2004-05-17	witness	Created.
// -----------------------------------------------------------------------------
 
-(void) addSyntaxFilesFromFolderToArray: (NSString*)fpath
{
	NSDirectoryEnumerator*  enny = [[NSFileManager defaultManager] enumeratorAtPath: fpath];
	NSString*				currPath = nil;
	NSString*				currName = nil;
	
	while( (currName = [enny nextObject]) )
	{
		if( [currName characterAtIndex: 0] == '.' )
			continue;
		
		currPath = [fpath stringByAppendingPathComponent: currName];
		[sUKMSCTDSyntaxDefinitionFiles addObject: currPath];
	}
}
 
 
// -----------------------------------------------------------------------------
//	reloadSyntaxDefinitionFiles:
//		Kill and recreate the list of syntax definition files.
//
//	REVISIONS:
//		2004-05-18	witness	Created.
// -----------------------------------------------------------------------------
 
-(void) reloadSyntaxDefinitionFiles
{
	[sUKMSCTDSyntaxDefinitionFiles release];
	sUKMSCTDSyntaxDefinitionFiles = nil;
	
	[self syntaxDefinitionFiles];
}
 
 
// -----------------------------------------------------------------------------
//	* CONSTRUCTOR:
//		Lazily instantiate our array of syntax definition file paths and also
//		init the syntax coloring scheme to use to the first item from that list.
//
//	REVISIONS:
//		2004-05-18	witness	Documented.
//		2004-05-17	witness	Created.
// -----------------------------------------------------------------------------
 
-(id)	init
{
    self = [super init];
    if( self )
	{
		syntaxDefinitionFilename = [[[self syntaxDefinitionFiles] objectAtIndex: 0] retain];
	}
    return self;
}
 
 
// -----------------------------------------------------------------------------
//	* DESTRUCTOR:
//
//	REVISIONS:
//		2004-05-18	witness	Documented.
//		2004-05-17	witness	Created.
// -----------------------------------------------------------------------------
 
-(void) dealloc
{
    [syntaxDefinitionFilename release];
    syntaxDefinitionFilename = nil;
 
    [super dealloc];
}
 
 
// -----------------------------------------------------------------------------
//	windowControllerDidLoadNib:
//		If we're using a popup, reload the syntax definition list now that the
//		NIB's been loaded and in the process populate the menu.
//
//	REVISIONS:
//		2004-05-18	witness	Created.
// -----------------------------------------------------------------------------
 
-(void) windowControllerDidLoadNib:(NSWindowController *)windowController
{
	if( syntaxDefinitionMenu )  // Connected to a popup in our NIB?
		[self rebuildSyntaxMenu];
	
	NSString*		fileSuffix = [[self fileName] pathExtension];
	NSNumber*		numObj = [sUKMSCTDSuffixToTagMappings objectForKey: fileSuffix];
	if( numObj )
		[self setSyntaxDefinitionFilename: [sUKMSCTDSyntaxDefinitionFiles objectAtIndex: [numObj intValue]]];
	
	[super windowControllerDidLoadNib: windowController];
}
 
 
// -----------------------------------------------------------------------------
//	syntaxDefinitionDictionary:
//		Return the syntax definition dictionary to use for colorizing. This
//		overrides a method in UKSyntaxColoredTextDocument since UKSCTD only
//		looks for its files in the application bundle.
//
//	REVISIONS:
//		2004-05-18	witness	Documented.
//		2004-05-17	witness	Created.
// -----------------------------------------------------------------------------
 
-(NSDictionary*)	syntaxDefinitionDictionary
{
	NSString*   synDefName = [self syntaxDefinitionFilename];
	return [NSDictionary dictionaryWithContentsOfFile: synDefName];
}
 
 
// -----------------------------------------------------------------------------
//	syntaxDefinitionFilename:
//		Return the full pathname of the syntax definition file to use for
//		syntax coloring.
//
//	REVISIONS:
//		2004-05-18	witness	Documented.
//		2004-05-17	witness	Created.
// -----------------------------------------------------------------------------
 
-(NSString*)	syntaxDefinitionFilename
{
    return [[syntaxDefinitionFilename retain] autorelease]; 
}
 
 
// -----------------------------------------------------------------------------
//	setSyntaxDefinitionFilename:
//		Select a different syntax definition file name to be used for coloring.
//		This also causes a recoloring of the text using the new coloring scheme.
//
//	REVISIONS:
//		2004-05-18	witness	Documented.
//		2004-05-17	witness	Created.
// -----------------------------------------------------------------------------
 
-(void) setSyntaxDefinitionFilename:(NSString *)aSyntaxDefinitionFileName
{
    [aSyntaxDefinitionFileName retain];
    [syntaxDefinitionFilename release];
    syntaxDefinitionFilename = aSyntaxDefinitionFileName;
	
	[[NSNotificationCenter defaultCenter] postNotificationName: UKMultiSyntaxColoredTextDocumentSyntaxDefinitionChanged object: self];
	
	[self recolorCompleteFile: self];
}
 
 
// -----------------------------------------------------------------------------
//	takeSyntaxDefinitionFilenameFromTagOf:
//		Menu action for our menu items that allow changing the syntax definition
//		to use.
//
//	REVISIONS:
//		2004-05-18	witness	Created.
// -----------------------------------------------------------------------------
 
-(IBAction) takeSyntaxDefinitionFilenameFromTagOf: (id)sender
{
	[self setSyntaxDefinitionFilename: [sUKMSCTDSyntaxDefinitionFiles objectAtIndex: [sender tag]]];
}
 
 
// -----------------------------------------------------------------------------
//	validateMenuItem:
//		Make sure the menu item for the syntax coloring scheme this file uses
//		is selectable and checked.
//
//	REVISIONS:
//		2004-05-18	witness	Created.
// -----------------------------------------------------------------------------
 
-(BOOL) validateMenuItem: (NSMenuItem*)anItem
{
	if( [anItem action] == @selector(takeSyntaxDefinitionFilenameFromTagOf:) )
	{
		[anItem setState: ([[sUKMSCTDSyntaxDefinitionFiles objectAtIndex: [anItem tag]] isEqualToString: [self syntaxDefinitionFilename]])];
		
		return( [anItem tag] < [sUKMSCTDSyntaxDefinitionFiles count] );
	}
	else
		return [super validateMenuItem: anItem];
}
 
 
// -----------------------------------------------------------------------------
//	windowNibName:
//		By default, UKMultiSyntaxColoredTextDocument uses a different NIB which
//		includes a popup menu in the status bar. If you want the menu in the
//		main menu bar instead, just subclass this and override this method to
//		return [super windowNibName], which gets you the regular NIB from the
//		superclass without the popup, and it'll cause the code in here to look
//		for its menu in the menu bar because the syntaxDefinitionMenu outlet
//		won't be set. Of course you can also return any other NIB you create
//		here, though then it'll be your duty to keep it in sync with the
//		"official" ones from me.
//
//	REVISIONS:
//		2004-05-18	witness	Created.
// -----------------------------------------------------------------------------
 
-(NSString*)	windowNibName
{
    return @"UKMultiSyntaxColoredTextDocument";
}
 
 
@end

This code uses the PclZip Zip File reading code, which is subject to the GNU LGPL. It also uses the GeSHi syntax highlighter, subject to the GPL. Ask if you want this for your own web site, it's free.