function count = cprintf(style,format,varargin)
% CPRINTF displays styled formatted text in the Command Window
%
% Syntax:
% count = cprintf(style,format,...)
%
% Description:
% CPRINTF processes the specified text using the exact same FORMAT
% arguments accepted by the built-in SPRINTF and FPRINTF functions.
%
% CPRINTF then displays the text in the Command Window using the
% specified STYLE argument. The accepted styles are those used for
% Matlab's syntax highlighting (see: File / Preferences / Colors /
% M-file Syntax Highlighting Colors), and also user-defined colors.
%
% The possible pre-defined STYLE names are:
%
% 'Text' - default: black
% 'Keywords' - default: blue
% 'Comments' - default: green
% 'Strings' - default: purple
% 'UnterminatedStrings' - default: dark red
% 'SystemCommands' - default: orange
% 'Errors' - default: light red
% 'Hyperlinks' - default: underlined blue
%
% 'Black','Cyan','Magenta','Blue','Green','Red','Yellow','White'
%
% STYLE beginning with '-' or '_' will be underlined. For example:
% '-Blue' is underlined blue, like 'Hyperlinks';
% '_Comments' is underlined green etc.
%
% STYLE beginning with '*' will be bold (R2011b+ only). For example:
% '*Blue' is bold blue;
% '*Comments' is bold green etc.
% Note: Matlab does not currently support both bold and underline,
% only one of them can be used in a single cprintf command. But of
% course bold and underline can be mixed by using separate commands.
%
% STYLE also accepts a regular Matlab RGB vector, that can be underlined
% and bolded: -[0,1,1] means underlined cyan, '*[1,0,0]' is bold red.
%
% STYLE is case-insensitive and accepts unique partial strings just
% like handle property names.
%
% CPRINTF by itself, without any input parameters, displays a demo
%
% Example:
% cprintf; % displays the demo
% cprintf('text', 'regular black text');
% cprintf('hyper', 'followed %s','by');
% cprintf('key', '%d colored', 4);
% cprintf('-comment','& underlined');
% cprintf('err', 'elements\n');
% cprintf('cyan', 'cyan');
% cprintf('_green', 'underlined green');
% cprintf(-[1,0,1], 'underlined magenta');
% cprintf([1,0.5,0],'and multi-\nline orange\n');
% cprintf('*blue', 'and *bold* (R2011b+ only)\n');
% cprintf('string'); % same as fprintf('string') and cprintf('text','string')
%
% Bugs and suggestions:
% Please send to Yair Altman (altmany at gmail dot com)
%
% Warning:
% This code heavily relies on undocumented and unsupported Matlab
% functionality. It works on Matlab 7+, but use at your own risk!
%
% A technical description of the implementation can be found at:
% http://UndocumentedMatlab.com/blog/cprintf/
%
% Limitations:
% 1. In R2011a and earlier, a single space char is inserted at the
% beginning of each CPRINTF text segment (this is ok in R2011b+).
%
% 2. In R2011a and earlier, consecutive differently-colored multi-line
% CPRINTFs sometimes display incorrectly on the bottom line.
% As far as I could tell this is due to a Matlab bug. Examples:
% >> cprintf('-str','under\nline'); cprintf('err','red\n'); % hidden 'red', unhidden '_'
% >> cprintf('str','regu\nlar'); cprintf('err','red\n'); % underline red (not purple) 'lar'
%
% 3. Sometimes, non newline ('\n')-terminated segments display unstyled
% (black) when the command prompt chevron ('>>') regains focus on the
% continuation of that line (I can't pinpoint when this happens).
% To fix this, simply newline-terminate all command-prompt messages.
%
% 4. In R2011b and later, the above errors appear to be fixed. However,
% the last character of an underlined segment is not underlined for
% some unknown reason (add an extra space character to make it look better)
%
% 5. In old Matlab versions (e.g., Matlab 7.1 R14), multi-line styles
% only affect the first line. Single-line styles work as expected.
% R14 also appends a single space after underlined segments.
%
% 6. Bold style is only supported on R2011b+, and cannot also be underlined.
%
% Change log:
% 2012-08-09: Graceful degradation support for deployed (compiled) and non-desktop applications; minor bug fixes
% 2012-08-06: Fixes for R2012b; added bold style; accept RGB string (non-numeric) style
% 2011-11-27: Fixes for R2011b
% 2011-08-29: Fix by Danilo (FEX comment) for non-default text colors
% 2011-03-04: Performance improvement
% 2010-06-27: Fix for R2010a/b; fixed edge case reported by Sharron; CPRINTF with no args runs the demo
% 2009-09-28: Fixed edge-case problem reported by Swagat K
% 2009-05-28: corrected nargout behavior sugegsted by Andreas Gäb
% 2009-05-13: First version posted on MathWorks File Exchange
%
% See also:
% sprintf, fprintf
% License to use and modify this code is granted freely to all interested, as long as the original author is
% referenced and attributed as such. The original author maintains the right to be solely associated with this work.
% Programmed and Copyright by Yair M. Altman: altmany(at)gmail.com
% $Revision: 1.08 $ $Date: 2012/10/17 21:41:09 $
persistent majorVersion minorVersion
if isempty(majorVersion)
%v = version; if str2double(v(1:3)) <= 7.1
%majorVersion = str2double(regexprep(version,'^(\d+).*','$1'));
%minorVersion = str2double(regexprep(version,'^\d+\.(\d+).*','$1'));
%[a,b,c,d,versionIdStrs]=regexp(version,'^(\d+)\.(\d+).*'); %#ok unused
v = sscanf(version, '%d.', 2);
majorVersion = v(1); %str2double(versionIdStrs{1}{1});
minorVersion = v(2); %str2double(versionIdStrs{1}{2});
end
% The following is for debug use only:
%global docElement txt el
if nargin<1, showDemo(majorVersion,minorVersion); return; end
if isempty(style), return; end
if all(ishandle(style)) && length(style)~=3
dumpElement(style);
return;
end
% Process the text string
if nargin<2, format = style; style='text'; end
%error(nargchk(2, inf, nargin, 'struct'));
%str = sprintf(format,varargin{:});
% In compiled mode
try useDesktop = usejava('desktop'); catch, useDesktop = false; end
if isdeployed || ~useDesktop %#ok - for Matlab 6 compatibility
% do not display any formatting - use simple fprintf()
% See: http://undocumentedmatlab.com/blog/bold-color-text-in-the-command-window/#comment-103035
% Also see: https://mail.google.com/mail/u/0/?ui=2&shva=1#all/1390a26e7ef4aa4d
% Also see: https://mail.google.com/mail/u/0/?ui=2&shva=1#all/13a6ed3223333b21
count1 = fprintf(format,varargin{:});
else
if ~exist('el','var') || isempty(el), el=handle([]); end %#ok mlint short-circuit error ("used before defined")
% Else (Matlab desktop mode)
% Get the normalized style name and underlining flag
[underlineFlag, boldFlag, style] = processStyleInfo(style);
% Set hyperlinking, if so requested
if underlineFlag
format = ['' format ''];
% Matlab 7.1 R14 (possibly a few newer versions as well?)
% have a bug in rendering consecutive hyperlinks
% This is fixed by appending a single non-linked space
if majorVersion < 7 || (majorVersion==7 && minorVersion <= 1)
format(end+1) = ' ';
end
end
% Set bold, if requested and supported (R2011b+)
if boldFlag
if (majorVersion > 7 || minorVersion >= 13)
format = ['' format ''];
else
boldFlag = 0;
end
end
% Get the current CW position
cmdWinDoc = com.mathworks.mde.cmdwin.CmdWinDocument.getInstance;
lastPos = cmdWinDoc.getLength;
% If not beginning of line
bolFlag = 0; %#ok
%if docElement.getEndOffset - docElement.getStartOffset > 1
% Display a hyperlink element in order to force element separation
% (otherwise adjacent elements on the same line will be merged)
if majorVersion<7 || (majorVersion==7 && minorVersion<13)
if ~underlineFlag
fprintf(' '); %fprintf(' \b');
elseif format(end)~=10 % if no newline at end
fprintf(' '); %fprintf(' \b');
end
end
%drawnow;
bolFlag = 1;
%end
% Get a handle to the Command Window component
mde = com.mathworks.mde.desk.MLDesktop.getInstance;
cw = mde.getClient('Command Window');
xCmdWndView = cw.getComponent(0).getViewport.getComponent(0);
% Store the CW background color as a special color pref
% This way, if the CW bg color changes (via File/Preferences),
% it will also affect existing rendered strs
com.mathworks.services.Prefs.setColorPref('CW_BG_Color',xCmdWndView.getBackground);
% Display the text in the Command Window
count1 = fprintf(2,format,varargin{:});
%awtinvoke(cmdWinDoc,'remove',lastPos,1); % TODO: find out how to remove the extra '_'
drawnow; % this is necessary for the following to work properly (refer to Evgeny Pr in FEX comment 16/1/2011)
docElement = cmdWinDoc.getParagraphElement(lastPos+1);
if majorVersion<7 || (majorVersion==7 && minorVersion<13)
if bolFlag && ~underlineFlag
% Set the leading hyperlink space character ('_') to the bg color, effectively hiding it
% Note: old Matlab versions have a bug in hyperlinks that need to be accounted for...
%disp(' '); dumpElement(docElement)
setElementStyle(docElement,'CW_BG_Color',1+underlineFlag,majorVersion,minorVersion); %+getUrlsFix(docElement));
%disp(' '); dumpElement(docElement)
el(end+1) = handle(docElement); %#ok used in debug only
end
% Fix a problem with some hidden hyperlinks becoming unhidden...
fixHyperlink(docElement);
%dumpElement(docElement);
end
% Get the Document Element(s) corresponding to the latest fprintf operation
while docElement.getStartOffset < cmdWinDoc.getLength
% Set the element style according to the current style
%disp(' '); dumpElement(docElement)
specialFlag = underlineFlag | boldFlag;
setElementStyle(docElement,style,specialFlag,majorVersion,minorVersion);
%disp(' '); dumpElement(docElement)
docElement2 = cmdWinDoc.getParagraphElement(docElement.getEndOffset+1);
if isequal(docElement,docElement2), break; end
docElement = docElement2;
%disp(' '); dumpElement(docElement)
end
% Force a Command-Window repaint
% Note: this is important in case the rendered str was not '\n'-terminated
xCmdWndView.repaint;
% The following is for debug use only:
el(end+1) = handle(docElement); %#ok used in debug only
%elementStart = docElement.getStartOffset;
%elementLength = docElement.getEndOffset - elementStart;
%txt = cmdWinDoc.getText(elementStart,elementLength);
end
if nargout
count = count1;
end
return; % debug breakpoint
% Process the requested style information
function [underlineFlag,boldFlag,style] = processStyleInfo(style)
underlineFlag = 0;
boldFlag = 0;
% First, strip out the underline/bold markers
if ischar(style)
% Styles containing '-' or '_' should be underlined (using a no-target hyperlink hack)
%if style(1)=='-'
underlineIdx = (style=='-') | (style=='_');
if any(underlineIdx)
underlineFlag = 1;
%style = style(2:end);
style = style(~underlineIdx);
end
% Check for bold style (only if not underlined)
boldIdx = (style=='*');
if any(boldIdx)
boldFlag = 1;
style = style(~boldIdx);
end
if underlineFlag && boldFlag
warning('YMA:cprintf:BoldUnderline','Matlab does not support both bold & underline')
end
% Check if the remaining style sting is a numeric vector
%styleNum = str2num(style); %#ok % not good because style='text' is evaled!
%if ~isempty(styleNum)
if any(style==' ' | style==',' | style==';')
style = str2num(style); %#ok
end
end
% Style = valid matlab RGB vector
if isnumeric(style) && length(style)==3 && all(style<=1) && all(abs(style)>=0)
if any(style<0)
underlineFlag = 1;
style = abs(style);
end
style = getColorStyle(style);
elseif ~ischar(style)
error('YMA:cprintf:InvalidStyle','Invalid style - see help section for a list of valid style values')
% Style name
else
% Try case-insensitive partial/full match with the accepted style names
validStyles = {'Text','Keywords','Comments','Strings','UnterminatedStrings','SystemCommands','Errors', ...
'Black','Cyan','Magenta','Blue','Green','Red','Yellow','White', ...
'Hyperlinks'};
matches = find(strncmpi(style,validStyles,length(style)));
% No match - error
if isempty(matches)
error('YMA:cprintf:InvalidStyle','Invalid style - see help section for a list of valid style values')
% Too many matches (ambiguous) - error
elseif length(matches) > 1
error('YMA:cprintf:AmbigStyle','Ambiguous style name - supply extra characters for uniqueness')
% Regular text
elseif matches == 1
style = 'ColorsText'; % fixed by Danilo, 29/8/2011
% Highlight preference style name
elseif matches < 8
style = ['Colors_M_' validStyles{matches}];
% Color name
elseif matches < length(validStyles)
colors = [0,0,0; 0,1,1; 1,0,1; 0,0,1; 0,1,0; 1,0,0; 1,1,0; 1,1,1];
requestedColor = colors(matches-7,:);
style = getColorStyle(requestedColor);
% Hyperlink
else
style = 'Colors_HTML_HTMLLinks'; % CWLink
underlineFlag = 1;
end
end
% Convert a Matlab RGB vector into a known style name (e.g., '[255,37,0]')
function styleName = getColorStyle(rgb)
intColor = int32(rgb*255);
javaColor = java.awt.Color(intColor(1), intColor(2), intColor(3));
styleName = sprintf('[%d,%d,%d]',intColor);
com.mathworks.services.Prefs.setColorPref(styleName,javaColor);
% Fix a bug in some Matlab versions, where the number of URL segments
% is larger than the number of style segments in a doc element
function delta = getUrlsFix(docElement) %#ok currently unused
tokens = docElement.getAttribute('SyntaxTokens');
links = docElement.getAttribute('LinkStartTokens');
if length(links) > length(tokens(1))
delta = length(links) > length(tokens(1));
else
delta = 0;
end
% fprintf(2,str) causes all previous '_'s in the line to become red - fix this
function fixHyperlink(docElement)
try
tokens = docElement.getAttribute('SyntaxTokens');
urls = docElement.getAttribute('HtmlLink');
urls = urls(2);
links = docElement.getAttribute('LinkStartTokens');
offsets = tokens(1);
styles = tokens(2);
doc = docElement.getDocument;
% Loop over all segments in this docElement
for idx = 1 : length(offsets)-1
% If this is a hyperlink with no URL target and starts with ' ' and is collored as an error (red)...
if strcmp(styles(idx).char,'Colors_M_Errors')
character = char(doc.getText(offsets(idx)+docElement.getStartOffset,1));
if strcmp(character,' ')
if isempty(urls(idx)) && links(idx)==0
% Revert the style color to the CW background color (i.e., hide it!)
styles(idx) = java.lang.String('CW_BG_Color');
end
end
end
end
catch
% never mind...
end
% Set an element to a particular style (color)
function setElementStyle(docElement,style,specialFlag, majorVersion,minorVersion)
%global tokens links urls urlTargets % for debug only
global oldStyles
if nargin<3, specialFlag=0; end
% Set the last Element token to the requested style:
% Colors:
tokens = docElement.getAttribute('SyntaxTokens');
if( ~isempty(tokens) )
try
styles = tokens(2);
oldStyles{end+1} = styles.cell;
% Correct edge case problem
extraInd = double(majorVersion>7 || (majorVersion==7 && minorVersion>=13)); % =0 for R2011a-, =1 for R2011b+
%{
if ~strcmp('CWLink',char(styles(end-hyperlinkFlag))) && ...
strcmp('CWLink',char(styles(end-hyperlinkFlag-1)))
extraInd = 0;%1;
end
hyperlinkFlag = ~isempty(strmatch('CWLink',tokens(2)));
hyperlinkFlag = 0 + any(cellfun(@(c)(~isempty(c)&&strcmp(c,'CWLink')),tokens(2).cell));
%}
styles(end-extraInd) = java.lang.String('');
styles(end-extraInd-specialFlag) = java.lang.String(style); %#ok apparently unused but in reality used by Java
if extraInd
styles(end-specialFlag) = java.lang.String(style);
end
oldStyles{end} = [oldStyles{end} styles.cell];
catch
% never mind for now
end
end
% Underlines (hyperlinks):
%{
links = docElement.getAttribute('LinkStartTokens');
if isempty(links)
%docElement.addAttribute('LinkStartTokens',repmat(int32(-1),length(tokens(2)),1));
else
%TODO: remove hyperlink by setting the value to -1
end
%}
% Correct empty URLs to be un-hyperlinkable (only underlined)
urls = docElement.getAttribute('HtmlLink');
if ~isempty(urls)
urlTargets = urls(2);
for urlIdx = 1 : length(urlTargets)
try
if( ~isempty(urlTargets(urlIdx)) )
if urlTargets(urlIdx).length < 1
urlTargets(urlIdx) = []; % '' => []
end
end
catch
% never mind...
a=1; %#ok used for debug breakpoint...
end
end
end
% Bold: (currently unused because we cannot modify this immutable int32 numeric array)
%{
try
%hasBold = docElement.isDefined('BoldStartTokens');
bolds = docElement.getAttribute('BoldStartTokens');
if ~isempty(bolds)
%docElement.addAttribute('BoldStartTokens',repmat(int32(1),length(bolds),1));
end
catch
% never mind - ignore...
a=1; %#ok used for debug breakpoint...
end
%}
return; % debug breakpoint
% Display information about element(s)
function dumpElement(docElements)
%return;
numElements = length(docElements);
cmdWinDoc = docElements(1).getDocument;
for elementIdx = 1 : numElements
if numElements > 1, fprintf('Element #%d:\n',elementIdx); end
docElement = docElements(elementIdx);
if ~isjava(docElement), docElement = docElement.java; end
%docElement.dump(java.lang.System.out,1)
disp(' ');
disp(docElement)
tokens = docElement.getAttribute('SyntaxTokens');
if isempty(tokens), continue; end
links = docElement.getAttribute('LinkStartTokens');
urls = docElement.getAttribute('HtmlLink');
try bolds = docElement.getAttribute('BoldStartTokens'); catch, bolds = []; end
txt = {};
tokenLengths = tokens(1);
for tokenIdx = 1 : length(tokenLengths)-1
tokenLength = diff(tokenLengths(tokenIdx+[0,1]));
if (tokenLength < 0)
tokenLength = docElement.getEndOffset - docElement.getStartOffset - tokenLengths(tokenIdx);
end
txt{tokenIdx} = cmdWinDoc.getText(docElement.getStartOffset+tokenLengths(tokenIdx),tokenLength).char; %#ok
end
lastTokenStartOffset = docElement.getStartOffset + tokenLengths(end);
txt{end+1} = cmdWinDoc.getText(lastTokenStartOffset, docElement.getEndOffset-lastTokenStartOffset).char; %#ok
%cmdWinDoc.uiinspect
%docElement.uiinspect
txt = strrep(txt',sprintf('\n'),'\n');
try
data = [tokens(2).cell m2c(tokens(1)) m2c(links) m2c(urls(1)) cell(urls(2)) m2c(bolds) txt];
if elementIdx==1
disp(' SyntaxTokens(2,1) - LinkStartTokens - HtmlLink(1,2) - BoldStartTokens - txt');
disp(' ==============================================================================');
end
catch
try
data = [tokens(2).cell m2c(tokens(1)) m2c(links) txt];
catch
disp([tokens(2).cell m2c(tokens(1)) txt]);
try
data = [m2c(links) m2c(urls(1)) cell(urls(2))];
catch
% Mtlab 7.1 only has urls(1)...
data = [m2c(links) urls.cell];
end
end
end
disp(data)
end
% Utility function to convert matrix => cell
function cells = m2c(data)
%datasize = size(data); cells = mat2cell(data,ones(1,datasize(1)),ones(1,datasize(2)));
cells = num2cell(data);
% Display the help and demo
function showDemo(majorVersion,minorVersion)
fprintf('cprintf displays formatted text in the Command Window.\n\n');
fprintf('Syntax: count = cprintf(style,format,...); click here for details.\n\n');
url = 'http://UndocumentedMatlab.com/blog/cprintf/';
fprintf(['Technical description: ' url '\n\n']);
fprintf('Demo:\n\n');
boldFlag = majorVersion>7 || (majorVersion==7 && minorVersion>=13);
s = ['cprintf(''text'', ''regular black text'');' 10 ...
'cprintf(''hyper'', ''followed %s'',''by'');' 10 ...
'cprintf(''key'', ''%d colored'',' num2str(4+boldFlag) ');' 10 ...
'cprintf(''-comment'',''& underlined'');' 10 ...
'cprintf(''err'', ''elements:\n'');' 10 ...
'cprintf(''cyan'', ''cyan'');' 10 ...
'cprintf(''_green'', ''underlined green'');' 10 ...
'cprintf(-[1,0,1], ''underlined magenta'');' 10 ...
'cprintf([1,0.5,0], ''and multi-\nline orange\n'');' 10];
if boldFlag
% In R2011b+ the internal bug that causes the need for an extra space
% is apparently fixed, so we must insert the sparator spaces manually...
% On the other hand, 2011b enables *bold* format
s = [s 'cprintf(''*blue'', ''and *bold* (R2011b+ only)\n'');' 10];
s = strrep(s, ''')',' '')');
s = strrep(s, ''',5)',' '',5)');
s = strrep(s, '\n ','\n');
end
disp(s);
eval(s);
%%%%%%%%%%%%%%%%%%%%%%%%%% TODO %%%%%%%%%%%%%%%%%%%%%%%%%
% - Fix: Remove leading space char (hidden underline '_')
% - Fix: Find workaround for multi-line quirks/limitations
% - Fix: Non-\n-terminated segments are displayed as black
% - Fix: Check whether the hyperlink fix for 7.1 is also needed on 7.2 etc.
% - Enh: Add font support